pax_global_header00006660000000000000000000000064142704452670014525gustar00rootroot0000000000000052 comment=2b629e572c8c430f399f5cf9ef49362efc8f52b0 camping-2.3/000077500000000000000000000000001427044526700130075ustar00rootroot00000000000000camping-2.3/.github/000077500000000000000000000000001427044526700143475ustar00rootroot00000000000000camping-2.3/.github/workflows/000077500000000000000000000000001427044526700164045ustar00rootroot00000000000000camping-2.3/.github/workflows/ruby.yml000066400000000000000000000016471427044526700201200ustar00rootroot00000000000000# This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby name: Ruby on: [push, pull_request] jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ 'ubuntu-latest', 'macos-latest'] # 'windows-latest' ruby: [ '3.0', 2.7, 2.6, 2.5, ruby-head ] exclude: - os: windows-latest ruby: jruby-head steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - name: Install dependencies run: bundle install - name: Run tests run: bundle exec rake camping-2.3/.gitignore000066400000000000000000000000251427044526700147740ustar00rootroot00000000000000pkg doc Gemfile.lock camping-2.3/.travis.yml000066400000000000000000000004731427044526700151240ustar00rootroot00000000000000rvm: - 1.8.7 - 1.9.3 - 2.2.0 sudo: false env: - "RACK='~>1.5.0'" - "RACK='~>1.4.0'" - "RACK='~>1.3.0'" - "AR='~>2.3'" - "AR='~>3.2.0'" - "AR='~>4.1'" install: bundle install --without development script: bundle exec rake test notifications: email: false irc: - "irc.freenode.org#camping" camping-2.3/CHANGELOG000066400000000000000000000144751427044526700142340ustar00rootroot00000000000000= 2.3 === 28th July, 2022 * New routes command line helper command. * plugin support added via the gear method. * Restored support for mounting multiple apps. * Add url_prefix option for mounting apps at different urls. * Increase camping test limit to 5120 bytes. We needed just a bit more space. * Add a book stub about Models. * Add a chapter about middleware and how it works. * Camping a '/' is now forcibly terminating each route. May revert this back if we run into trouble. = 2.2 === Never Released * Updated ActiveRecord migrations class to reference version 6.1 * Removed deprecated gemfile definitions * Rename deprecated methods * Get tests to pass = 2.1 === 19th Aug, 2010 (whyday) * Helpers#R now calls to_param on any object it passes in * Fix route generation issue with routes including "." (#22) * Improved tests * Improved 1.9 support * Camping::Server is now built upon Rack::Server * Add support for ERB, Haml etc through Tilt * Introducing Camping.options and Camping#set * Camping::Server only loads ActiveRecord when needed = 2.0 === 9th Apr, 2010 * Speed-up of Camping::Mab (thanks zimbatm!) * @state is now an alias of @env['rack.session'] * Camping.use injects a Rack middleware. * Update Flipbook to RDoc 2.4 * Removed old examples. * Updated examples/blog.rb * Camping::Apps returns! * Session-cookies now timeout naturally (thanks jenna!) * You can now `throw :halt` to halt the response in a helper. * Camping::H#u is gone (was an alias to merge!) * Camping::Session now uses session-cookies. The AR-backend is gone for now. * camping/db.rb has been renamed to camping/ar.rb. * Camping now uses Rack internally. Every app responds to #call. = 1.6 === Never released * Camping::Apps removed, it wasn't reliable. * bin/camping server kinds splitted in various files. * NotFound and ServerError controllers changed to methods : r404 : called when a controller was not found r500 : called on uncaught exception r501 : called on undefined method All of those can be overridden at your taste. * Markaby no longer required. Like AR, is it autoloaded on (Mab) usage. * Camping::H is now inheriting from Hash instead of HashWithIndifferentAccess. * Which made possible to remove the last strict dependency : active_support * #errors_for removed, it wasn't really used * Bug fixes ! = 1.5 === 3rd Oct, 2006 * Camping::Apps stores an array of classes for all loaded apps. * bin/camping can be given a directory. Like: camping examples/ * Console mode -- thank zimbatm. Use: camping -C yourapp.rb * Call controllers with Camping.method_missing. Tepee.get(:Index) #=> (Response) Blog.post(:Delete, id) #=> (Response) Blog.post(:Login, :input => {'username' => 'admin', 'password' => 'camping'}) #=> # Blog.get(:Info, :env => {:HTTP_HOST => 'wagon'}) #=> #'wagon'} ...> * Using \r\n instead of \n on output. FastCGI has these needs. * ActiveRecord no longer required or installed. * If you refer to Models::Base, however, ActiveRecord will be loaded with autoload. (see lib/camping/db.rb) * new Camping::FastCGI.serve which will serve a whole directory of apps (see http://code.whytheluckystiff.net/camping/wiki/TheCampingServer) * ~/.campingrc can contain database connection info if you want your default to be something other than SQLite. database: adapter: mysql username: camping socket: /tmp/mysql.sock password: NOFORESTFIRES database: camping * controllers are now *ordered*. uses the inherited hook to keep track of all classes created with R. those classes are scanned, in order, when a request is made. any other controllers are handled first. so if you plan on overriding the urls method, be sure to subclass from R(). * Console mode will load .irbrc in the working directory, if present. (for example, in my ~/git/balloon directory, i have this in the .irbrc: include Balloon::Models when camping -C balloon.rb gets run, the models all get included in main.) * And, of course, many other bugfixes from myself and the loyal+kind zimbatm... * Markaby updated to 0.5. (See its CHANGELOG.) = 1.4.2 === 18th May, 2006 * Efficient file uploads for multipart/form-data POSTs. * Camping tool now uses Mongrel, if available. If not, sticks with WEBrick. * Multiple apps can be loaded with the camping tool, each mounted according to their file name. = 1.4.1 === 3rd May, 2006 * Streaming HTTP support. If body is IO, will simply pass to the controller. Mongrel, in particular, supports this nicely. = 1.4 === 11th April, 2006 * Moved Camping::Controllers::Base to Camping::Base. * Moved Camping::Controllers::R to Camping::R. * New session library (lib/camping/session.rb). * WEBrick handler (lib/camping/webrick.rb) and Mongrel handler (lib/camping/mongrel.rb). * Helpers#URL, builds a complete URL for a route. Returns a URI object. This way relative links could just return self.URL.path. * Base#initialize takes over some of Base#service's duties. * ENV now available as @env in controllers and views. * Beautiful multi-page docs without frames! = 1.3 === 28th January, 2006 * bin/camping: an application launcher. * Camping.run(request, response) now changed to controller = Camping.run(request, env) * This means outputting the response is the wrapper/server's job. See bin/camping, you can do a controller.to_s at the least. * Controllers::Base.env is the new thread-safe home for ENV. * The input hash now works more like Rails params. You can call keys like methods or with symbols or strings. * Queries are now parsed more like PHP/Rails, in that you can denote structure with brackets: post[user]=_why;post[id]=2 * Auto-prefix table names, to help prevent name clash. * Helpers.errors_for simple validation. * Lots of empty :href and :action attributes, a bug. * New single-page flipbook RDoc template. = 1.2 === 23rd January, 2006 * Camping.goes allows fresh modules build from all Camping parts. * File uploads now supported (multipart/form-data). * Helpers.R can rebuild routes. * Helpers./ for tracing paths from the root. = 1.1 === 19th January, 2006 * Allowed request and response streams to be passed in, to allow WEBrick and FastCGI support. = 1.0 === 17th January, 2006 * Initial checkin, see announcement at http://redhanded.hobix.com/bits/campingAMicroframework.html. camping-2.3/COPYING000066400000000000000000000020301427044526700140350ustar00rootroot00000000000000Copyright (c) 2006 why the lucky stiff Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. camping-2.3/Gemfile000066400000000000000000000005271427044526700143060ustar00rootroot00000000000000source 'https://rubygems.org' gem 'rake' gem 'rack' gem 'mab' group :extras do gem 'tilt' if ENV['AR'] gem 'activerecord', ENV['AR'] gem 'sqlite3' end end group :development do gem 'parser' gem 'unparser' end group :test do gem 'minitest', '~> 5.0' gem 'minitest-reporters' gem 'rack-test' gem 'ruby_parser' end camping-2.3/README.md000066400000000000000000000061541427044526700142740ustar00rootroot00000000000000![Build Status](https://github.com/camping/camping/actions/workflows/ruby.yml/badge.svg) # Camping, a Microframework Camping is a web framework which consistently stays at less than 4kB of code. You can probably view the complete source code on a single page. But, you know, it's so small that, if you think about it, what can it really do? The idea here is to store a complete fledgling web application in a single file like many small CGIs. But to organize it as a Model-View-Controller application like Rails does. You can then easily move it to Rails once you've got it going. ## A Camping Skeleton A skeletal Camping blog could look like this: ```ruby require 'camping' Camping.goes :Blog module Blog::Models class Post < Base; belongs_to :user; end class Comment < Base; belongs_to :user; end class User < Base; end end module Blog::Controllers class Index def get @posts = Post.find :all render :index end end end module Blog::Views def layout html do head { title "My Blog" } body do h1 "My Blog" self << yield end end end def index @posts.each do |post| h1 post.title end end end ``` ## Installation Interested yet? Luckily it's quite easy to install Camping. We'll be using a tool called RubyGems, so if you don't have that installed yet, go grab it! Once that's sorted out, open up a Terminal or Command Line and enter: ``` gem install camping ``` Even better, install the Camping Omnibus, a full package of recommended libs: ``` gem install camping-omnibus ``` If not, you should be aware of that Camping itself only depends on [Rack](https://github.com/rack/rack), and if you're going to use the views you also need to install **[markaby](https://github.com/markaby/markaby)**, and if you're going to use the database you need **activerecord** as well. ``` gem install markaby gem install activerecord ``` ## Learning First of all, you should read [the first chapters](/book/01_introduction.md) of The Camping Book. It should hopefully get you started pretty quick. While you're doing that, you should be aware of the _reference_ which contains documentation for all the different parts of Camping. [The wiki](https://github.com/camping/camping/wiki) is the place for all tiny, useful tricks that we've collected over the years. Don't be afraid to share your own discoveries; the more, the better! And if there's anything you're wondering about, don't be shy, but rather subscribe to [the mailing list](http://rubyforge.org/mailman/listinfo/camping-list) and ask there. We also have an IRC channel over at Freenode, so if you feel like chatting with us, you should join [#camping @ irc.freenode.net](http://java.freenode.net/?channel=camping). ## Authors Camping was originally crafted by [why the lucky stiff](http://en.wikipedia.org/wiki/Why_the_lucky_stiff), but is now maintained by the _community_. This simply means that if we like your patch, it will be applied. Everything is managed through [the mailing list](http://rubyforge.org/mailman/listinfo/camping-list), so just subscribe and you can instantly take part in shaping Camping. camping-2.3/Rakefile000066400000000000000000000067431427044526700144660ustar00rootroot00000000000000$:.unshift 'extras' begin require 'rake/dsl_definition' require 'rake/alt_system' rescue LoadError else begin if defined?(Rake::DeprecatedObjectDSL) Rake::DeprecatedObjectDSL.class_eval do private_instance_methods(false).each do |meth| remove_method meth end end end rescue Exception end end require 'rake' require 'rake/clean' require 'rake/testtask' require 'tempfile' require 'open3' require File.expand_path('../constants', __FILE__) CLEAN.include ['**/.*.sw?', '*.gem', '.config', 'test/test.log', '.*.pt'] task :default => :check ## RDoc begin gem 'rdoc', '~>3.9.0' rescue LoadError task :docs do puts "** Camping needs RDoc 3.9 in order to use the Flipbook template." end else require 'rdoc/task' RDoc::Task.new(:docs) do |rdoc| # We have a recent version of RDoc, so let's use flipbook. require 'rdoc/generator/singledarkfish' rdoc.options += ['-f', 'singledarkfish', *RDOC_OPTS] rdoc.template = "flipbook" rdoc.rdoc_dir = 'doc' rdoc.title = "Camping, a Microframework" rdoc.rdoc_files.add ['README', 'lib/camping-unabridged.rb', 'lib/camping/**/*.rb', 'book/*'] end end task :rubygems_docs do require 'rubygems/doc_manager' def spec.installation_path; '.' end def spec.full_gem_path; '.' end manager = Gem::DocManager.new(spec) manager.generate_rdoc end desc "Packages Camping." task :package => :clean ## Tests Rake::TestTask.new(:test) do |t| t.libs << "test" t.test_files = FileList['test/app_*.rb'] end ## Diff desc "Compare camping and camping-unabridged" task :diff do require 'parser/current' require 'unparser' require 'pp' u = Tempfile.new('unabridged') m = Tempfile.new('mural') usexp = Parser::CurrentRuby.parse(File.read("lib/camping-unabridged.rb")) msexp = Parser::CurrentRuby.parse(File.read("lib/camping.rb")) u << Unparser.unparse(usexp) m << Unparser.unparse(msexp) u.flush m.flush sh "diff -u #{u.path} #{m.path} | less" u.delete m.delete end error = false ## Check task :check => ["test", "check:valid", "check:equal", "check:size", "check:lines", "check:exit"] namespace :check do desc "Check source code validity" task :valid do sh "ruby -c lib/camping.rb" end desc "Check equality between mural and unabridged" task :equal do require 'ruby_parser' u = RubyParser.new.parse(File.read("lib/camping-unabridged.rb")) m = RubyParser.new.parse(File.read("lib/camping.rb")) u.reject! do |sexp| sexp.is_a?(Sexp) and sexp[1] == s(:gvar, :$LOADED_FEATURES) end unless u == m STDERR.puts "camping.rb and camping-unabridged.rb are not synchronized." error = true else puts "✅ synchronized....." end end SIZE_LIMIT = 5120 desc "Compare camping sizes to unabridged" task :size do FileList["lib/camping*.rb"].each do |path| s = File.size(path) puts "%21s : % 6d % 4d%%" % [File.basename(path), s, (100 * s / SIZE_LIMIT)] end if File.size("lib/camping.rb") > SIZE_LIMIT STDERR.puts "lib/camping.rb: file is too big (> #{SIZE_LIMIT})" error = true end end desc "Verify that line length doesn't exceed 80 (120) chars for camping.rb" task :lines do i = 1 File.open("lib/camping.rb").each_line do |line| if line.size > 121 # 1 added for \n error = true STDERR.puts "lib/camping.rb:#{i}: line too long (#{line[-10..-1].inspect})" end i += 1 end end task :exit do exit 1 if error end end camping-2.3/bin/000077500000000000000000000000001427044526700135575ustar00rootroot00000000000000camping-2.3/bin/camping000077500000000000000000000006611427044526700151260ustar00rootroot00000000000000#!/usr/bin/env ruby $:.unshift File.dirname(__FILE__) + "/../lib" require 'camping' # require 'camping-unabridged' # comment out the above line and uncomment this line to run camping unabridged with all sorts of stuff in it. require 'camping/server' begin Camping::Server.start rescue OptionParser::ParseError => ex STDERR.puts "!! #{ex.message}" puts "** use `#{File.basename($0)} --help` for more details..." exit 1 end camping-2.3/book/000077500000000000000000000000001427044526700137415ustar00rootroot00000000000000camping-2.3/book/01_introduction.md000066400000000000000000000016241427044526700173070ustar00rootroot00000000000000# Introduction Camping is a small web framework, less than 4k, a little white blood cell in the vein of Rails. This little book will start with a tutorial which takes about fifteen minutes - by the end you should have a little Camping site up. The following chapters will eventually go deeper into how Camping, HTTP and Rack work. ("Eventually", because these chapters are not written yet. This book is currently a very much work in progress, and we'll be very grateful if you want to help out.) If you at any moment need some help or have any questions or comments, we highly recommend [the mailing list](http://rubyforge.org/mailman/listinfo/camping-list) which got plenty of nice people willing to help. We also have an IRC-channel at [#camping @ irc.freenode.net](http://java.freenode.net/?channel=camping) if you're into that sort of thing. Enough talk. Ready? Let's ["get started"](02_getting_started.md). camping-2.3/book/02_getting_started.md000066400000000000000000000305251427044526700177600ustar00rootroot00000000000000# Getting Started Start a new text file called nuts.rb. Here's what you put inside: ```ruby Camping.goes :Nuts ``` Save it. Then, open a command prompt in the same directory. You'll want to run: ```ruby $ camping nuts.rb ``` And you should get a message which reads: ```ruby ** Camping running on 0.0.0.0:3301. ``` This means that right now The Camping Server is running on port 3301 on your machine. Open your browser and visit http://localhost:3301/. Your browser window should show: ``` Camping Problem! / Not found ``` No problem with that. The Camping Server is running, but it doesn't know what to show. Let's tell him. ## Hello clock So, you've got Camping installed and it's running. Keep it running. You can edit files and The Camping Server will reload automatically. When you need to stop the server, press Control-C. Let's show something. At the bottom of nuts.rb add: ```ruby module Nuts::Controllers class Index < R '/' def get Time.now.to_s end end end ``` Save the file and refresh the browser window. Your browser window should show the time, e.g. ``` Sun Jul 15 12:56:15 +0200 2007 ``` ## Enjoying the view The Camping microframework allows us to separate our code using the MVC (Model-View-Controller) design pattern. Let's add a view to our Nuts application. Replace the module Nuts::Controllers with: ```ruby module Nuts::Controllers class Index < R '/' def get @time = Time.now render :sundial end end end ``` ```ruby module Nuts::Views def layout html do head do title { "Nuts And GORP" } end body { self << yield } end end def sundial p "The current time is: #{@time}" end end ``` Save the file, refresh your browser window and it should show a message like: ``` The current time is: Sun Jul 15 13:05:41 +0200 2013 ``` And the window title reads "Nuts And GORP". Here you can see we call render :sundial from our controller. This does exactly what it says, and renders our sundial method. We've also added a special method called layout which Camping will automatically wrap our sundial output in. If you're familiar with HTML, you'll see that our view contains what looks HTML tag names. This is Markaby, which is like writing HTML using Ruby! Soon enough, you'll find that you can return anything from the controller, and it will be sent to the browser. But let's keep that for later and start investigating the routes. ## Routes You probably noticed the weird R '/' syntax in the previous page. This is an uncommon feature of Ruby that is used in our favorite microframework, to describe the routes which the controller can be accessed on. These routes can be very powerful, but we're going to have look at the simplest ones first. ```ruby module Nuts::Controllers class Words < R '/welcome/to/my/site' def get "You got here by: /welcome/to/my/site" end end class Digits < R '/nuts/(\d+)' def get(number) "You got here by: /nuts/#{number}" end end class Segment < R '/gorp/([^/]+)' def get(everything_else_than_a_slash) "You got here by: /gorp/#{everything_else_than_a_slash}" end end class DigitsAndEverything < R '/nuts/(\d+)/([^/]+)' def get(number, everything) "You got here by: /nuts/#{number}/#{everything}" end end end ``` Add this to `nuts.rb` and try if you can hit all of the controllers. Also notice how everything inside a parenthesis gets passed into the method, and is ready at your disposal. ### Simpler routes This just in: ```ruby module Nuts::Controllers class Index def get "You got here by: /" end end class WelcomeToMySite def get "You got here by: /welcome/to/my/site" end end class NutsN def get(number) "You got here by: /nuts/#{number}" end end class GorpX def get(everything_else_than_a_slash) "You got here by: /gorp/#{everything_else_than_a_slash}" end end class NutsNX def get(number, everything) "You got here by: /nuts/#{number}/#{everything}" end end end ``` Drop the < R-part and it attemps to read your mind. It won't always succeed, but it can simplify your application once in a while. ## Modeling the world You can get pretty far with what you've learned now, and hopefully you've been playing a bit off-book, but it's time to take the next step: Storing data. Let's start over again. ```ruby Camping.goes :Nuts module Nuts::Models class Page < Base end end ``` Obviously, this won't do anything, since we don't have any controllers, but let's rather have a look at what we _do_ have. We have a model named Page. This means we now can store wiki pages and retrieve them later. In fact, we can have as many models as we want. Need one for your users and one for your blog posts? Well, I think you already know how to do it. However, our model is missing something essential: a skeleton. ```ruby Camping.goes :Nuts module Nuts::Models class Page < Base end class BasicFields < V 1.0 def self.up create_table Page.table_name do |t| t.string :title t.text :content # This gives us created_at and updated_at t.timestamps end end def self.down drop_table Page.table_name end end end ``` Now we have our first version of our model. It says: ``` If you want to migrate up to version one, create the skeleton for the Page model, which should be able to store, "title" which is a string, "content" which is a larger text, "created_at" which is the time it was created, "updated_at" which is the previous time it was updated. If you want to migrate down from version one, remove the skeleton for the Page model. ``` This is called a [migration](http://api.rubyonrails.org/classes/ActiveRecord/Migration.html). Whenever you want to change or add new models you simply add a new migration below, where you increase the version number. All of these migrations builds upon each other like LEGO blocks. Each new Migrations must have different class's names, is a good idea name migration's explicit. For example: ```ruby class AddTagColumn < V 1.1 def self.change add_column Page.table_name, :tag, :string Page.reset_column_information end end ``` Now we just need to tell Camping to use our migration. Write this at the bottom of nuts.rb ```ruby def Nuts.create Nuts::Models.create_schema end ``` When The Camping Server boots up, it will automatically call Nuts.create. You can put all kind of startup-code here, but right now we only want to create our skeleton (or upgrade if needed). Start The Camping Server again and observe: ```bash $ camping nuts.rb ** Starting Mongrel on 0.0.0.0:3301 -- create_table("nuts_schema_infos") -> 0.1035s == Nuts::Models::BasicFields: migrating =================================== -- create_table(:nuts_pages) -> 0.0033s == Nuts::Models::BasicFields: migrated (0.0038s) ========================== ``` Restart it, and enjoy the silence. There's no point of re-creating the skeleton this time. Before we go on, there's one rule you must know: Always place your models before your migrations. ## Using our model Let's explore how our model works by going into the _console_. The console is good way to familiarize with your models. Test your models adding some data by bare hand before addin it to the application. ```bash $ camping -C nuts.rb ** Starting console >> ``` Now it's waiting for your input, and will give you the answer when you press Enter. Here's what I did, leaving out the boring answers. You should add your own pages. ```bash >> Page = Nuts::Models::Page >> hiking = Page.new(:title => "Hiking") >> hiking.content = "You can also set the values like this." >> hiking.save >> page = Page.find_by_title("Hiking") => # >> page = Page.find(1) => # >> page.title >> page.content >> page.created_at >> page.updated_at >> Page.find_by_title("Fishing") => nil ## Page.create automatically saves the page for you. >> Page.create(:title => "Fishing", :content => "Go fish!") >> Page.count => 2 ``` Now I have two pages: One about hiking and one about fishing. ## Wrapping it up Wouldn't it be nice if we could show this wonderful our pages in a browser? Update nuts.rb so it also contains something like this: ```ruby module Nuts::Controllers class Pages def get # Only fetch the titles of the pages. @pages = Page.all(:select => "title") render :list end end class PageX def get(title) @page = Page.find_by_title(title) render :view end end end module Nuts::Views def list h1 "All pages" ul do @pages.each do |page| li do a page.title, :href => R(PageX, page.title) end end end end def view h1 @page.title self << @page.content end end ``` Here we meet our first _helper_: ```ruby R(PageX, page.title) ``` This is the reversed router and it generates a URL based on a controller. R takes the controller you want to link to, followed by the router parameters. . Instead of typing: ```ruby :href=>'/welcome/to/my/site' ``` You can let Camping do the hard work for you. ```ruby :href=>R(Words) ``` If the route would have some parameter, you shall write like this: ```ruby :href=>R(WordsX,'someword') ``` Camping ships with a few, but very useful, helpers, and you can easily add your owns. Have a look at Camping::Helpers for how you use these. There's a lot of improvements you could do here. Let me suggest: * Show when the page was created and last updated. * What happens when the page doesn't exist? * What should the front page show? * Allow or disallow authenticated users. * Add a layout. * Jazz it up a bit. Helpers can work generating Markaby's code. You could write a helper for show some kind of data and call it from your views (Add a layout). ## The last touch We have one major flaw in our little application. You can't edit or add new pages. Let's see if we can fix that: ```ruby module Nuts::Controllers class PageX def get(title) if @page = Page.find_by_title(title) render :view else redirect PageXEdit, title end end def post(title) # If it doesn't exist, initialize it: @page = Page.find_or_initialize_by_title(title) # This is the same as: # @page = Page.find_by_title(title) || Page.new(:title => title) @page.content = @input.content @page.save redirect PageX, title end end class PageXEdit def get(title) @page = Page.find_or_initialize_by_title(title) render :edit end end end ``` The core of this code lies in the new post method in the PageX controller. When someone types an address or follows a link, they'll end up at the get method, but you can easily create a form which rather sends you to the post when submitted. There are other names you can use, but they won't always work. So for now, don't be fancy and just stick to get and post. We'll show you how this really works later. You might also notice that we use @input.content. The @input-hash contains any extra parameters sent, like those in the forms and those in the URL (/posts?page=50). Here's an edit-view, but you can probably do better. See if you can integrate all of this with what you already have. ```ruby module Nuts::Views def edit h1 @page.title form :action => R(PageX, @page.title), :method => :post do textarea @page.content, :name => :content, :rows => 10, :cols => 50 br input :type => :submit, :value => "Submit!" end end end ``` ## Phew. You've taken quite a few steps in the last minutes. You deserve a break. But let's recap for a moment: * Always place Camping.goes :App at the top of your file. * Every route ends at a controller, but ... * ... the controller only delegates the work. * @input contains the extra parameters. * The views are HTML disguised as Ruby. * They can access the instances variables (those that starts with a single at-sign) from the controller. * The models allows you to store all kinds of data. * Place your models before your migrations. * Helpers are helpful. If you wanna dive deep, stay reading for a [more extended tutorial](03_more_about_controllers.md) step by step... Enjoy the code camping-2.3/book/03_more_about_controllers.md000066400000000000000000000100231427044526700213430ustar00rootroot00000000000000# Controllers What are these _controllers_? This is a good question for a Camping newb. In the [MVC paradigm](http://en.wikipedia.org/wiki/Model%E2%80%93View%E2%80%93Controller#Overview) a Models could be described as a very weird and hard to understand thing. When you look in the browser's navigation bar, you will see something like: `http://localhost:3301/welcome/to/my/site` The Universal Resource Locator (URL) is showing you a "structured" web site running inside a server that listening in the `3301` port. The site's internal routes are "drawing" the path: Inside the `root_dir/` is the directory `/welcome/` and it recursively adds the names of the deeper directories: "to", "my", and "site" (`./to/my/site`). But that is virtual, the site doesn't really have a directory structure like that... That would be useless. The site uses a "route drawing system" to get _control_ of that route; this is what *the controller* does. ## Camping Routes and Controllers In camping, each _capitalized_ word in a camel-cased contoller declaration is like the words between each slash in a URL. For example: ```ruby WelcomeToMySite ``` will draw the route: ``` /welcome/to/my/site ``` Or you could instead use the weird R helper to get more specific control of your routes: ```ruby Welcome < R '/welcome/to/my/site' ``` All of this will be declared inside the Nuts::Controllers module. ## Controller Parameters Controllers can also handle your application's parameters. For example, when the client asks for a route like `/post/1`, a static web server would look out for the directory named "1" and serve the content in that directory. It would need a lot of "number-named" directories to do that simple job. But in our case, the controller draws a dynamic path for every asked post. We just need to tell him about the size of the flock. In camping, adding `N` or `X` to a controller's declaration tells it to expect a parameter. The `N` suffix, which will match a numbered route, is declared like this: ```ruby class PostN ``` With this controller, adding a number to the simple `/post` route in your browser will trigger this route. For example, either of these will work: ``` /post/1 /post/99 ``` But this `N` route will not match against a word. For example, a request for `/pots/mypost` will return 404 (Not Found). Because the `PostN` declaration will only match _Numeric_ parameters. If you would like to match something other than a number, you should use the `X` suffix: ```ruby class PostX ``` The _X_ tells the controller to match anything, including number and words. For example, it will match: ``` /post/1 /post/99 /post/mypost ``` But it will NOT match: `/post/mypost/1` (or anything that has "/" in the name). Since slashes signify deeper directories, you would need to tell the controller to recognize the deeper directory before using a parameter. You can do this using camel case, followed by the "X" or "N": ```ruby class PostMypostX ``` ## Getting parameters from the controller Ok, we have the controller that match parameters; and now what? Say that you want to show the post number N requested in the controller. You'll need the number. ```ruby class PostN def get number p "the parameter was: #{number}" end end ``` Please, do not try that at home. It's very dirty to use a _view_ inside the controller (more on that soon). The method `get`, for the `/post/N route`, will take a parameter. That number will by inside the "number parameter". From now, if you want to route something to your post route, you can write a link 100% pragmatically like this: ```ruby @post=rand 1..9 a "See the post number: #{@post}",:href=>R(PostN,@post) ``` For that example, we just choose a random post and then displayed a link to its path. > but you told me that I shouldn't write that in the controller... Yep...I said that. These things are called "views" because they will be in the user's face. Our client will not see the controllers; they will be hidden from them when they visit our site. Our client will only see the [views](04_more_about_views.md). camping-2.3/book/04_more_about_views.md000066400000000000000000000065021427044526700201420ustar00rootroot00000000000000# Views The view is the scene for our show. The user is sitting in his chair (the browser) and see on screen actors (the view). Enjoy the show without think that behind the scenes there is a whole team. The team behind the cameras is our controller but the user don't care about that. The user only see their browser and our application is just and HTML document. ## Camping Views Inside the Nut::Views module, we will write methods. That method shall called with the render sentence. The views do not use class. ```ruby module Nust::Views def post_number p "you asked the post number @postn" end end ``` Well, well, that was a views, but now: How we show it to the user? We will call the view from the controller. And we pass to the view all the parameters that we want to show. ```ruby module Nuts::Controller class PostN def get number @postn=number render :post_number end end end ``` We just declared a controller for the route /post/(number here). When the browser ask for the route /post/1 the controller will be trigged and the get method defined inside the class, will respond to the "get" request in the web server. The first instruction in our controller, will by write the number in the @postn variable and then "render" But what is `render`? This sentence is not from ruby, this is a camping's sentence and mean: -show now the view named (:symbol). It take a symbol as parameter, and the symbol's name, shall be one of these methods declared in the views. Now we have only a view named post_number. You could "associate" it MENTALLY as a hash like this: ```ruby def my_view => render :my_view ``` But that will happen in your mind. In camping these will be happening in the modules, not in a hash, therefore, their are very associated too. Imagine your applications as a big building. The controller as the corridors and the views as the offices. Where are the offices and we do in each office? ## Views and Controllers Model View and Controller, are joined but not scrambled. The views use R(ControllerName) for call the controllers and "move". The controller will use "render" for call the view. -And now... what do you think we have behind the curtains? ```ruby module Why::Controllers class CircusCourtains def get require 'endertromb' @monkey=Endertromb::Animals::StartMonkey.new :frog=>true,:place=>:hand render :behind_curtain end end end ``` -OMG! It's a monkey with a start in the hand! -yes, ladies and gentleman, we have it just here for you ```ruby module Views def behind_curtain p @monkey end end ``` No, we don't have it behind the curtains, but the user believe it. There is the view, enjoy the show. ## Template engine We spoke about the views, and HTML, but we are not using html's tags for our view... How happening this? The rubyist have not necessary to write HTML code. Exists some options named template. That the "p" before the `@monkey` in the view. A template engine is a kind of "pseudo-language" for handle HTML code. In some template engines you will also write HTML in a more easy way. Their also handle ruby data inside HTML. Templates engine are the wheels of the views. In camping, you will write views using a template engine named [Markaby](05_more_about_markaby.md) and you will write HTML pragmatically. camping-2.3/book/05_more_about_markaby.md000066400000000000000000000121311427044526700204270ustar00rootroot00000000000000## Where Markaby's come from A great musician, writer and programmer (and a bit crazy) named [_why](http://en.wikipedia.org/wiki/Why_the_lucky_stiff) wrote camping. Before banish himself to the dimension from where him come (a place named "the keyhole"); hi also wanted, that the developers, should have not write pure HTML code. Him dream with a world were the programmer write html in their programming language. Rails users are very hard guys. Their eat the soup using a fork and generate HTML views in a template engine named Erb. Their use a long set of weird tools for generate that Embedded Ruby (erb), their call it "scaffolding". The basic idea is: "let me rails write html code for you" Many framework have in the README a line that say: "support more populars template engines" but normally peoples uses the default in the framework. "Mab" is the template engine used by default in camping. _why Wrote the first implementation named Markaby and then, judofyr and Jenna, power-up markaby writing a more compact version of it. While you are writing with mab, will see ruby code front you are eyes, but your mind, will be seen pure HTML without tags. We will be "metaprograming HTML code". In order to show a Header-1 tag, we just call a method named h1 and send the content as parameter. It will make the dirty work writing all the tags just like this: ```ruby h1 'This is a header one' ``` "Write HTML pragmatically" mean: -write html using not html tags. Markaby is a way for write Hyper Text Markup Language (HTML) using Ruby. That do not mean "html knowledge unneeded" But that is only the beginning, we can do more wild things. ## Writing HTML pragmatically For example: if we want show a table for show users and their real names: ```ruby # call me using render :users # from a get in the controllers def users table do th 'User' th 'Realname' @users.each do |user,realname| tr do td user td realname end # tr end # each end # table end # def ``` Take a look better [here](https://github.com/camping/mab/blob/master/README.md) ## Markaby and the layout There is a special view named "layout". The layout, is view that will be rendered each time any other view are rendered. In fact, the layout will take the other views for compose himself. The layout is rendered before anything. Each time you use the "render" sentence, you will be rendering the layout and the desired view. Because that, wee need some "special" tweaks in the layout. It must have a door with a poster that say: "other view will enter using this door" The layout will be rendered, and in some place you will put the other view. This will be done with the "yield" sentence. Put the yield sentence whenever you want rendering all the other views. This is very useful, for example: We wan to write a footer in ALL the pages that we are rendering. You can write the footer in every views definition, or just write the footer in the layout. Without a layout, if you render the table's views. Camping will drop out the table just like that to the browser's render. It will not draw any body or head tag. Writing body and head in every page could be a very bored task. But do not worry, the layout is here. Lets write our layout, with all the HTML shape and including a footer ```ruby def layout html do head do title 'My Blog' end body do div.wrapper! do self << yield end p.footer! do text 'Powered by Camping' end end end end # layout ``` Well, that was wild. Let's see: First we have everything inside a block, the html's block. At the next level, the first block is head, that is rendering something like: ```html My Blog ``` If you look at the HTML's source of camping, you will see a VERY LOOOONG line with every the HTML code, better do not look it. Then, come the more weird thing. A div with a estrange sentence: ```ruby self << yield ``` That mean: -put just right here, the other view called. In that place, in the center of the div.wrapper, will be placed all our views called using render's sentence. When you call: ```ruby render :someview ``` Camping will rendering before, the layout view, and put all the content of "someview" inside div.wrapper! You shall not write a lot of tag like head or title. Finally, it will be rendering a "p" named footer. That will be the footer in all our pages. ## Tip What would happen? If you do this in the layout: ```ruby head do title "#{@title}" end ``` Hummm... You could "rewrite" the titles of each pages. In the controller, you just need to declare the variable @title, and that will be the title for that page. Remember: * Views take @variables from the controller * Layout is rendered before any called view. * Markaby is ruby code and it can be embedded * View's modules use not `class` declarations In the table example, we used a hash named @users. But. Where come from all thats data? It come from the controller, but the controller took it from the [model](06_more_about_models.md). The M of the MVC, the layer who stare the whole bunch of persistent data. camping-2.3/book/06_more_about_models.md000066400000000000000000000036431427044526700202750ustar00rootroot00000000000000## More about Models Models are used to persist data. That data could be anything. The balance in a bank account, A list of your favorite restaurants, your blog posts, you name it. Camping uses the *ActiveRecord* Gem, an ORM (object-relational mapper), that maps Database tables to objects. We define models by inheriting from a base model named Base: ```ruby class User < Base end ``` Very creative. Base is really just an alias for ActiveRecord, nothing fancy. We put our models into a namespaced module named after our App: ```ruby Camping.goes :Nuts module Nuts::Models class User < Base end end ``` Remember from earlier that Models need to be defined before our controllers, otherwise we can't use em. So keep them close to the top. The new User model we've defined has a small problem, it's completely empty, it doesn't have any data that can be stored in it. Camping models map Database tables to objects automatically, but this model doesn't have a database table yet. To fix that we'll create a migration: ```ruby Camping.goes :Nuts module Nuts::Models class User < Base; end # Define a migration to add users class AddUser < V 1.2 def self.up create_table User.table_name do |t| t.string :username t.string :email t.timestamps end end def self.down drop_table User.table_name end end end ``` Databases, like birds, migrate. Migrations move our database from one configuration to another. In our case we're adding users. So cool. User's should be able to log in, sign up, maybe make some pages. We could make the next myspace. To get our database to make a users table we need force our app to create the schema. ```ruby def Nuts.create Nuts::Models.create_schema end ``` Now that puppy will migrate when we launch our app. Our Users now have greater hope to survive. So great. I love it. camping-2.3/book/06_rules_of_thumb.md000066400000000000000000000074771427044526700176240ustar00rootroot00000000000000# Keep it in one file Generally, the idea is keep your app small and store in a single file. Your app will end up with four sections: 1. Camping setup ```ruby do require 'rubygems' require 'camping' Camping.goes :Blog end ``` 2. Models ```ruby do module Blog::Models class User < Base; end class Post < Base; belongs_to :user end end end ``` 3. Controllers ```ruby do module Blog::Controllers class Index < R '/' def get; render :index end end end end ``` 4. Views ```ruby do module Blog::Views def layout html { body { self << yield } } end def index div.page "Welcome!" end end end ``` # What if things get out of hand? If you’re piling up models and controllers, your file may begin to exceed 200 lines, which means lots of paging up and down. Go ahead and store your models, controllers and views in three separate files. Your directory structure should end up like this: ``` blog.rb blog/ models.rb controllers.rb views.rb ``` (Note, for the development reloading to work, your required files (models.rb etc.) must be in a subdirectory named after your app.) Your blog.rb would still contain the setup (No. 1): ```ruby do require 'rubygems' require 'camping' Camping.goes :Blog require 'blog/helpers' # if needed require 'blog/models' require 'blog/views' require 'blog/controllers' end ``` # Small apps, many mounts Rather than building huge Camping apps, the idea here is to write small apps which can each be mounted at directories on your web server. One restriction: these apps will share a database. However, this allows applications to access each other’s tables and simplifies setup and configuration. To mount multiple camping apps, you can `require` the app files. When you mount more than one app they won't be mounted according to their parent directory, routing is explicit. If you'd like to give one of your apps a prefix, set the `url_prefix` option in your app: ```ruby # camp.rb require 'camping' Camping.goes :Blog require 'blog.rb' Camping.goes :Wiki require 'wiki.rb' Wiki.set :url_prefix, 'tepee/' Camping.goes :Charty require 'charty.rb' Charty.set :url_prefix, 'charts/' ``` By default Camping will look for a file called `camp.rb` in the root where Camping is executed. You can also supply a ruby filename to load your apps from there. You’ll end up with: ``` http://localhost:3301/blog, a blogging app. http://localhost:3301/tepee, a wiki app. http://localhost:3301/charts, a charting app. ``` In your app, if you’re using the R() method to build your links, Camping will make sure the mount is added properly to links. For example, if R(View, 1) is used in the blogging app mounted at /blog, the link will be written as /blog/view/1. If later you mount the blog at /articles instead, Camping will write the link as /articles/view/1. # Give us a create method ```ruby do def Blog.create # Code in here will run when the app starts, or reloads, but not when requests happen. # You can use it to create database bits and pieces, files, folders, and so on. end end ``` # The camping server The Camping Server is basically a set of rules. At the very least, The Camping Server must: - Load all Camping apps in a directory. - Load new apps that appear in that directory. - Mount those apps according to their filename. (e.g. blog.rb is mounted at /blog.) - Run each app’s create method upon startup. - Reload the app if its modification time changes. - Reload the app if it requires any files under the same directory and one of their modification times changes. - Support the X-Sendfile header. # Bin/camping Camping comes with a very simple version of The Camping Server. bin/camping uses either WEBrick or Mongrel (if you have it installed.) Run it like this: `camping /var/www/camping/*.` It will follow all of the rules above. camping-2.3/book/07_philosophy.md000066400000000000000000000060621427044526700167730ustar00rootroot00000000000000# Why's origin story The philosophy of Camping is a long and winding tail of origin, met by the ideals for the future of many mad hackers. The story of camping begins in 2006 on a cool winters night in Pittsburgh, where we find a hacker hunched over his computer, typing ruby code with his right hand and playing a laser theremin with his left. To the spooky sounds of his own left hand, he hacked through the night. This night, camping was born. Lets step back though time for a minute though, to the origins of this man. Once an upstanding PHP developer, he grew weary and tired of his day job, writing endless login pages and checkouts. He dreamt of a world free of his C-flavoured prison. Tales of promised lands, where snake powered ponies run wild, dancing around campfires full of rubies glowing red as blood. They worked him to the bone, until one day his bones just up and left. He couldn't do it any longer! He was on a mission to find that fire which fueled his dreams. In seclusion, there isn't much known about this odd man's life. Some say he went crazy. Others say he became a well respected professor. Still others suggest both of these are true. But what we do know, is that it is here, that he developed his love of chunky bacon, foxes, and children shaped like keyholes. And so he went on, crafting his mad writings, scribblings of foxes explaining ruby symbols, and making strange music. Soon this man found himself concerned that children had no good way to make their own eBay competitors. For this reason, he created Camping. The End. # Actual philosophy Why The Lucky Stiff is no longer around, so those of us who contributed early on to Camping have since become its caretakers. We continue to push the framework foward, to be more compact, to be faster, easier, more fun. These are our guiding principals: - Camping is for everyone. It sure is great to turn an idea in to a single ruby file which creates a website. It's also great to organise an app in to several files sometimes. You can plug bits together all in a row, and grow your evil monkey in to an entire army of evil circus animals! - Camping isn't for making money. You can make money using camping. Nobody will stop you. But we don't have any buzzwords to offer, we won't make your unit tests easier, nor help you do market research. Our main contributors certainly aren't using camping in large scale deployments, and while camping is blazingly fast, we have no idea how well it would work if you ran it on lots of servers! - Camping is really simple. You don't need to know much, to make nifty things with it, and you can really easily add more detailed bits as needed. Your brain will thank you. - Camping apps are easy to automatically reload. Because most of them are just one ruby file. - Camping encourages experimentation. The whole thing is an experiment. - If you're new to ruby, there are heaps of quirky hacks in here which will teach you all sorts of obscure, nifty, and outright strange things about the Ruby language. - Camping doesn't take itself too seriously. We're a fun bunch, living on the edge of zany! camping-2.3/book/08_publishing_an_app.md000066400000000000000000000153731427044526700202650ustar00rootroot00000000000000Once you've built your Camping app, you'll almost certainly want to get it online some place, so others can get at it! There are tons of ways to do this, some easy, some hard, some free, and some expensive. Some of these techniques also come with limitations. # Using a spare computer Cost: Usually free; Limitations: None really; Extra Requirements: Need to have a computer you can leave on all the time. This is probably the easiest way. Just find a computer you can leave on all the time, and use The Camping Server on Port 80, by running something like this in the Command Prompt or Terminal: ``` camping --port 80 myapp.rb ``` Then check to make sure it's working by visiting http://localhost:80/ on that computer. Next, you'll need to find out if your internet provider gives you a Static IP address, or a Dynamic IP. Most home internet providers give you a Dynamic IP, where most business accounts come with a Static IP. If you don't know, you can find out by phoning your internet provider. # If you have a static IP address This is really a great situation to be in. You'll next want to get a domain name of some sort. You can register one on a Domain Name Registrar, but this will generally cost about $12 per year to keep. You can also get a free subdomain name from services like afraid.org. Regardless how you do it, you'll want to create an A Record, and for it's value, provide your IP address. You can usually find out your IP address by visiting an IP Lookup Website, or by asking your Internet Provider. # If you have a dynamic IP address Your IP Address will change from time to time, so you'll need to use a Dynamic DNS service. Popular DDNS services include Afraid.org, DynDNS, and No-IP. These will either require you to run a little program on your computer to watch when your IP changes and update the domain name, or have you enter a special username and password in to certain home routers. # Troubleshooting If you find others can't access your website through the domain name, and you have a dynamic IP, your Internet Provider might be applying Port Blocking. If you phone them, they might be able to remove the blocks. Otherwise, you'll need to run the camping server on a different port. 8080 is a common alternative. Of note, you'll need to include :8080 (or whatever) after the domain name and before the slash in your web address for this to work. For example: http://funkytown.afraid.org:8080/ - otherwise, make sure the DDNS updating software is running and working correctly. # Using Dreamhost Cost: About $5 per month; Limitations: None really; Extra Requirements: SFTP or FTP software, and an SSH client like PuTTY on windows, or the Terminal on Linuxes and Mac OS. Dreamhost have some really cheap hosting plans, and are quite easy to use. They include support for Rack on their shared hosting plans, and are fairly reliable and fast. Enable the "Passenger (Ruby/Python apps only):" option in a domain's settings, from the Manage Domains page within the Dreamhost Panel. Ensure the "Shell account - allows SFTP/FTP plus ssh access." option is enabled on your main user, from the Manage Users page. Use your SSH software to login, and if you haven't already, set up rubygems. Once that's done, it's time to install stuff! ``` gem install camping-omnibus ``` Install any other gems you might need for your app, then use your SFTP or FTP software to upload the file to the folder labeled with your domain's name. Lastly, you'll need to add a few things: A folder called 'tmp', containing a file called 'restart.txt', which you'll need to change whenever you need to make Dreamhost reload your app's source code (while you're trying things out, you can change this filename to 'always_restart.txt' to reload the app with each request). A file called config.ru, containing something like: ```ruby require 'rubygems' ENV['GEM_PATH'] = File.join(File.dirname(__FILE__), '..', '.gems') if File.exist?('/dh') Gem.clear_paths require 'rack' require 'blog.rb' use Rack::ShowExceptions # optional, but helpful if there's any errors Blog.create if Blog.respond_to? :create run Blog ``` Make sure it's all uploaded, and your app should be online! Yay! # Using Heroku Cost: None for small apps; Limitations: Cannot change files in the filesystem, must use database; Extra Requirements: Need to install and use git to upload your app to the Heroku servers. Someone should really add stuff to this section. For now, see How to run Camping 2.0 apps on Heroku or Heroku's own guide for Rack-based apps (scroll down for Camping). # Using Google App Engine Cost: None for small apps; Limitations: Cannot change files in the filesystem, must use the google database; Extra Requirements: ???. Someone should also add information to this section. It's possible using jRuby somehow. I imagine this guide would help a lot! # Using a CGI Webhost Firstly, make sure your webhost can run ruby apps. Most can, thankfully. If you have shell access, you can check by entering 'which ruby' and seeing if it finds anything. Unfortunately, different webhosts work differently, so we can't provide specific instructions for installing rubygems on your webhost. If all else fails, you can extract the files from the lib folder within each gem, put the files in a folder, and add the big folder to your load path. Once you've done this, you'll just need to make sure your camping app file starts with: ```ruby #!/usr/bin/ruby ``` And then either the simple postamble, or the complex postamble: ```ruby # Plug it in to CGI Rack::Handler::CGI.run(Blog)) if __FILE__ == $0 ``` Then upload it to your server, and change the file's permissions to have all the executable bits set. You should now be able to visit the file using your web browser (for example, http://example.com/blog.rb). In fact, you don't even need the .rb. the #!/usr/bin/ruby line lets your server know what kind of file it is. :) # Using a Rack Compatible Web Host Follow your Webhost's instructions, and in the config.ru file, add a little something like this: ```ruby ... require 'blog' run Blog end ``` To set up Passenger and Rack for really easy deployment under Apache or Nginx see the screencasts on the Passenger site. If you have (e.g.) Apache on your computer, this is also good for local testing too. You can also use Rack::URLMap to plug a whole bunch of different apps in to one folder. A camping app here, a rails project there, a sinatra doodad over there in the corner messing up the whole global namespace. The possibilities are severely limited! # And Also Some Luck We wish you good luck! Publishing your app to a server can be an annoying experience the first time you do it. Often little bugs will appear you never noticed before on your computer, or things just might not work for mysterious reasons. Stick to it, and don't be afraid to ask for help from those more battle hardened. camping-2.3/book/09_upgrade_notes.md000066400000000000000000000056351427044526700174430ustar00rootroot00000000000000# Upgrade notes This document includes everything needed in order to upgrade your applications. If you’re looking for all the new features in a version, please have a look at the CHANGELOG in the Camping source. # From 2.0 to 2.1 ## Options In Camping 2.1 there is now a built-in way to store options and settings. If you use cookie session, it means that you’ll now have to change to: ```ruby module Nuts set :secret, "Very secret text, which no-one else should know!" include Camping::Session end ``` # From 1.5 to 2.0 ## Rack The biggest change in 2.0 is that it now uses Rack internally. This means that you’ll now have to deploy Camping differently, but hopefully more easily too. Now every Camping application is also a Rack application, so simply check out the documentation to the server of your choice. ## Require 'camping/db' In earlier versions of Camping, you loaded database support by: ```ruby require 'camping/db' ``` Actually, this loaded a very thin layer on top of ActiveRecord, and in the future we want to experiment with other libraries. Therefore you should now simply remove the line, and instead just inherit from Base right away. This also means you’ll have to place your migrations after the models. We also encourage you to use `Model.table_name` instead of `:appname_model`, just to make sure it’s named correctly. ```ruby ## Don't require anything: # require 'camping/db' module Nuts::Models ## Just inherit Base: class Page < Base; end ## Migrations have to come *after* the models: class CreateTheBasics < V 0.1 def self.up create_table Page.table_name do |t| ... end end def self.down drop_table Page.table_name end end end ``` ## Cookie Sessions Camping 2.0 now uses a cookie-based session system, which means you no longer need a database in order to use sessions. The disadvantage of this is that you are restricted to only around 4k of data. See below for the changes required, and see Camping::Session more details. ```ruby module Nuts ## Include Camping::Session as before: include Camping::Session ## But also define a secret: secret "Very secret text, which no-one else should know!" end def Nuts.create ## And remove the following line: # Camping::Models::Session.create_schema end ``` ## Error Handling Camping now uses three methods in order to handle errors. These replaces the old classes NotFound and ServerError. r404 is called when a route can’t be found. r501 is called when a route is found, but doesn’t respond to the method. r500 is called when an error happens. You can override these in your application: ```ruby module Nuts def r404(path) "Sorry, but I can't find #{path}." end def r501(method) "Sorry, but I can't respond to #{method}." end def r500(klass, method, ex) "Sorry, but #{klass}##{method} failed with #{ex}." end end ``` It should be noted that this might change in the future. camping-2.3/book/10_middleware.md000066400000000000000000000037541427044526700167110ustar00rootroot00000000000000Middleware in Camping is just Rack Middleware, and if you're familiar with Rack middleware then you'll be right at home. I'm going to assume that you have no idea what Rack or Rack middleware *is*, Let's learn about it. # The HTTP Request The web works in a request -> response pattern. When we *GO* to a webpage our browser is making a *request* to a web server. That request is processed and a *response* is sent. We have already covered how a response is mapped to our ruby code through controllers in Chapter 3. Those responses could be any of a number of things, HTML, an image, JSON, CSS, Web servers are pretty capable. When using Camping you'll generally respond with HTML, CSS, or Javascript, maybe some images. As that request passes through your application you may need to make some decisions about it. Does the request try to get something with restricted access? Is it a request to a dynamic or cached file? Whatever it is we can write some Ruby code to make some decisions about that request before passing it along to Camping's router. That's what Middleware is for. Middleware is Rack based and follows Rack Middleware conventions. Write a class, name it whatever you want but it must have an `#initialize` and a `#call` method. ```ruby class MyMiddleware def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) [status, headers, body] end end ``` The `#initialize` method must store a reference to an `app` object that is passed in as a parameter. The `#call` method accepts an environment parameter that we call `env` and returns an array with 3 values: `status`, `headers`, and `body`. Now, when I first saw this I was confused, why do we immediately Call ### notes: * really good railscast about it that's like... 13 years old: http://railscasts.com/episodes/151-rack-middleware?autoplay=true * An extremely old article about it: https://web.archive.org/web/20150105094611/https://www.amberbit.com/blog/2011/07/13/introduction-to-rack-middleware/ camping-2.3/book/11_gear.md000066400000000000000000000020231427044526700154770ustar00rootroot00000000000000# Pack Your Gear Camping provides a way to include and expand Camping without messing with it's innards too much, we call these plugins gear. To use gear you need to *pack* it into camping: ```ruby Camping.goes :Blog module Blog pack Camping::Gear::CSRF end # or Blog.pack Camping::Gear::CSRF ``` Define your gear by opening a module: ```ruby module Royalty def queens @queens ||= [ "Beyonce", "Niki", "Doja"] end end ``` Gear define methods and helpers that are included in your app. Define a `ClassMethods` module to have class methods included: ```ruby module Royalty module ClassMethods def secret_sauce @_secret_sauce ||= SecureRandom.base64(32) end end # /... end ``` You can also supply a setup callback method that runs after your gear is packed: ```ruby module Royalty # Run a setup routine with this Gear. def self.setup(app) @app = app @app.set :saucy_secret, "top_secret_sauce" end end ``` We'll be adding some really great gear soon. In the meantime, try making your own gear. camping-2.3/camping-omnibus.gemspec000066400000000000000000000001001427044526700174330ustar00rootroot00000000000000require File.expand_path('../constants', __FILE__) camping_omni camping-2.3/camping.gemspec000066400000000000000000000001001427044526700157610ustar00rootroot00000000000000require File.expand_path('../constants', __FILE__) camping_spec camping-2.3/constants.rb000066400000000000000000000031401427044526700153460ustar00rootroot00000000000000require 'rake' NAME = "camping" BRANCH = "2.3" GIT = ENV['GIT'] || "git" REV = `#{GIT} rev-list HEAD`.strip.split.length VERS = ENV['VERSION'] || (REV.zero? ? BRANCH : [BRANCH, REV] * '.') RDOC_OPTS = ["--line-numbers", "--quiet", "--main", "README"] def camping_spec @spec ||= Gem::Specification.new do |s| s.name = NAME s.version = VERS s.platform = Gem::Platform::RUBY # s.extra_rdoc_files = FileList["README.md", "CHANGELOG", "COPYING", "book/*"].to_a s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)\/', '--exclude', 'lib/camping.rb'] s.summary = "miniature rails for anyone" s.author = "why the lucky stiff" s.email = 'why@ruby-lang.org' s.homepage = 'http://camping.rubyforge.org/' s.executables = ['camping'] s.add_dependency('rack', '>=1.0') s.add_dependency('mab', '>=0.0.3') s.required_ruby_version = '>= 1.8.2' s.files = %w(COPYING README.md Rakefile) + Dir.glob("{bin,doc,test,lib,extras,book}/**/*") + Dir.glob("ext/**/*.{h,c,rb}") + Dir.glob("examples/**/*.rb") + Dir.glob("tools/*.rb") s.require_path = "lib" s.bindir = "bin" end end def camping_omni @omni ||= Gem::Specification.new do |s| s.name = "camping-omnibus" s.version = VERS s.platform = Gem::Platform::RUBY s.summary = "the camping meta-package for updating ActiveRecord, and SQLite3 bindings" %w[author email homepage].each { |x| s.__send__("#{x}=", camping_spec.__send__(x)) } s.add_dependency('camping', ">=#{BRANCH}") s.add_dependency('activerecord') s.add_dependency('sqlite3', '>=1.1.0.1') end end camping-2.3/examples/000077500000000000000000000000001427044526700146255ustar00rootroot00000000000000camping-2.3/examples/README000066400000000000000000000003571427044526700155120ustar00rootroot00000000000000If you've got camping installed, why not run all these examples using TheCampingServer? From this directory, run: `camping blog.rb` for example. NOTE: You'll need the active_record and acts_as_versioned gems installed for some of these. camping-2.3/examples/blog.rb000077500000000000000000000164621427044526700161110ustar00rootroot00000000000000#!/usr/bin/env ruby $:.unshift File.dirname(__FILE__) + '/../lib' require 'camping' require 'camping/ar' require 'camping/session' require 'redcloth' Camping.goes :Blog module Blog include Camping::Session module Models class Post < Base belongs_to :user before_save do |record| cloth = RedCloth.new(record.body) cloth.hard_breaks = false record.html_body = cloth.to_html end end class Comment < Base; belongs_to :user; end class User < Base; end class BasicFields < V 1.1 def self.up create_table :blog_posts, :force => true do |t| t.integer :user_id, :null => false t.string :title, :limit => 255 t.text :body, :html_body t.timestamps end create_table :blog_users, :force => true do |t| t.string :username, :password end create_table :blog_comments, :force => true do |t| t.integer :post_id, :null => false t.string :username t.text :body, :html_body t.timestamps end User.create :username => 'admin', :password => 'camping' end def self.down drop_table :blog_posts drop_table :blog_users drop_table :blog_comments end end end module Controllers class Index def get @posts = Post.all(:order => 'updated_at DESC') render :index end end class PostNew def get require_login! @post = Post.new render :add end def post require_login! post = Post.create(:title => input.post_title, :body => input.post_body, :user_id => @state.user_id) redirect PostN, post end end class PostN def get(post_id) @post = Post.find(post_id) render :view end end class Edit < R '/post/(\d+)/edit' def get(post_id) require_login! @post = Post.find(post_id) render :edit end def post(post_id) require_login! @post = Post.find(post_id) @post.update :title => input.post_title, :body => input.post_body redirect PostN, @post end end class Login def get render :login end def post @user = User.find_by_username_and_password(input.username, input.password) if @user @state.user_id = @user.id redirect R(Index) else @info = 'Wrong username or password.' end render :login end end class Logout def get @state.user_id = nil redirect Index end end class Style < R '/styles\.css' STYLE = File.read(__FILE__).gsub(/.*__END__/m, '') def get @headers['Content-Type'] = 'text/css; charset=utf-8' STYLE end end end module Helpers def logged_in? !!@state.user_id end def require_login! unless logged_in? redirect Controllers::Login throw :halt end end end module Views def layout html do head do title 'My Blog' link :rel => 'stylesheet', :type => 'text/css', :href => '/styles.css', :media => 'screen' end body do h1 { a 'My Blog', :href => R(Index) } div.wrapper! do self << yield end hr p.footer! do if logged_in? _admin_menu else a 'Login', :href => R(Login) text ' to the adminpanel' end text! ' – Powered by ' a 'Camping', :href => 'http://camping.rubyforge.org/' end end end end def index if @posts.empty? h2 'No posts' p do text 'Could not find any posts. Feel free to ' a 'add one', :href => R(PostNew) text ' yourself. ' end else @posts.each do |post| _post(post) end end end def login h2 'Login' p.info @info if @info form :action => R(Login), :method => 'post' do input :name => 'to', :type => 'hidden', :value => @to if @to label 'Username', :for => 'username' input :name => 'username', :id => 'username', :type => 'text' label 'Password', :for => 'password' input :name => 'password', :id => 'password', :type => 'password' input :type => 'submit', :class => 'submit', :value => 'Login' end end def add _form(@post, :action => R(PostNew)) end def edit _form(@post, :action => R(Edit, @post)) end def view _post(@post) end # partials def _admin_menu text! [['Log out', R(Logout)], ['New', R(PostNew)]].map { |name, to| mab { a name, :href => to} }.join(' – ') end def _post(post) h2 { a post.title, :href => R(PostN, post) } p.info do text! "Written by #{post.user.username} " text post.updated_at.strftime('%B %M, %Y @ %H:%M ') _post_menu(post) end text! post.html_body end def _post_menu(post) if logged_in? a '(edit)', :href => R(Edit, post) end end def _form(post, opts) form({:method => 'post'}.merge(opts)) do label 'Title', :for => 'post_title' input :name => 'post_title', :id => 'post_title', :type => 'text', :value => post.title label 'Body', :for => 'post_body' textarea post.body, :name => 'post_body', :id => 'post_body' input :type => 'hidden', :name => 'post_id', :value => post.id input :type => 'submit', :class => 'submit', :value => 'Submit' end end end end def Blog.create Blog::Models.create_schema :assume => (Blog::Models::Post.table_exists? ? 1.0 : 0.0) end __END__ * { margin: 0; padding: 0; } body { font: normal 14px Arial, 'Bitstream Vera Sans', Helvetica, sans-serif; line-height: 1.5; } h1, h2, h3, h4 { font-family: Georgia, serif; font-weight: normal; } h1 { background-color: #EEE; border-bottom: 5px solid #6F812D; outline: 5px solid #9CB441; font-weight: normal; font-size: 3em; padding: 0.5em 0; text-align: center; } h2 { margin-top: 1em; font-size: 2em; } h1 a { color: #143D55; text-decoration: none } h1 a:hover { color: #143D55; text-decoration: underline } h2 a { color: #287AA9; text-decoration: none } h2 a:hover { color: #287AA9; text-decoration: underline } #wrapper { margin: 3em auto; width: 700px; } p { margin-bottom: 1em; } p.info, p#footer { color: #999; margin-left: 1em; } p.info a, p#footer a { color: #999; } p.info a:hover, p#footer a:hover { text-decoration: none; } a { color: #6F812D; } a:hover { color: #9CB441; } hr { border-width: 5px 0; border-style: solid; border-color: #9CB441; border-bottom-color: #6F812D; height: 0; } p#footer { font-size: 0.9em; margin: 0; padding: 1em; text-align: center; } label { display: block; width: 100%; } input, textarea { padding: 5px; margin-bottom: 1em; margin-right: 490px; width: 200px; } input.submit { width: auto; } textarea { font: normal 14px Arial, 'Bitstream Vera Sans', Helvetica, sans-serif; height: 300px; width: 400px; } camping-2.3/extras/000077500000000000000000000000001427044526700143155ustar00rootroot00000000000000camping-2.3/extras/images/000077500000000000000000000000001427044526700155625ustar00rootroot00000000000000camping-2.3/extras/images/badge.gif000066400000000000000000000247411427044526700173230ustar00rootroot00000000000000GIF89a3f3333f333ff3fffff3f3f̙3f3333f3333333333f3333333f3f33ff3f3f3f3333f3333333f3̙333333f333ff3ffffff3f33f3ff3f3f3ffff3fffffffffff3fffffff3fff̙ffff3fffff3f̙3333f33̙3ff3ffff̙f3f̙3f̙̙3f̙3f3333f333ff3fffff̙̙3̙f̙̙̙3f̙3f3f3333f333ff3fffff3f3f̙3fM!, H*\ȰÇ#JHŋ3jȱǏ? TG$L@\2:{`~g+5|BIM, H = d ўR 4ui =4yB2L3ZaʊCΚ Ī"lʓO)RLMbJV R 1TM*K{3?eRn-hN m[hp@Y-&\4ĩ"=Zkd%fpQ^ر%{7VȝG[57y·֭t]g=U!Wy֜'x%('\pvU^T_,&X{x\3`7`%X"ȕZbjDvVy65,8)W\rX%qФVuML |\) +g\Msz]eZF,b^j`p2:5W d M [[{<Te_iy'䊄h̀Ҝ يuWUkY9"˽9%4Zq=byN:nRɊJحRYa]ZԲG|Ι颜!VLzSIxXضT+b+&ն)cECUr( dYY{@wp{ rma RWx |tqX<k Mʘ/iLR=zwc^l+2A]-0C3u 4D1,Wl=),_ \)}ԹGmrl{E3 R4М}^\1tWHxX}dsJxyy[y .M 4 ^,93D92'2Mr0ⴰwUOHka^Qx1]_9 dK9 8ʆ?ͬfWV\P1ݮTEYL 2MZp_Pb#w6,4laZ\9ax`=4=R)(X! 9|ADSu@,S[v<غVr_$ 1<-Ӹ3(TUCE1Myz. 30 ix?%kC~^[d5agH3GIx/b\J2f'<p Ke$׶2RE" :q6t*AP'1aA\<~kVo:Otu/"\_m IAIK:q20l*Y({" kyTekd̨m.s)/%E Fh%*JT~gY/|Dmebqn 1UIu Y*[1CΠ6|7+8@ݰ/lj:xs5C5bg4SS-Dw_KIXSD{4NٙZfx7?9ռԌP͙ zh!Mp3E$pPNM+xIӞ. qی8E~ӤiydB 'k e uy=BWW8gh꾉O Kw 2PS 1}b/"u:Z v S _mf6D11"P'k@ 3 d&5׃ȲQ%>1q;E" Pe~U'=ߢ06@]S-<1*w;%R +qS!b>tc&#"{XDoh}Q}+/!,Wp XW%Ȳ~!r lStrMN!?EGP1"kq xw! 7,pNӐ Ry7 1UyqȦ/"B [.I:+%}"i!W C)'#q+P+MvVUBʐw)lsVӎB&PQJɱt!W!7H A$k}[Y ҃zɓ!G)D j ywǕjl`+1, ;=ezUWY9!FqֱW@VcDOyA K8<ɆB9} E2'XO &DͶriU!ҕjz4=hR),]\]]`1Y}UrQaw9}_w;5bvQd1%w=hEtnT-vh7xq^wLwyC2\0٢+O`[#S " >T/rVڄpCJQv~8hWRфh!yaé/1i21/| vM1($΂ʡ0B. d oF  "q;@!eaUИp\90\ I-!,P`P0TA' gau%)"J P!gTJiD$,y!%b61ࢋR!+P5%2{UyELאؘV,4AWQxNd5|]VOeda N$UǮ\m<܍ܩpI˴Zfm+,X$ЩVӨ RynTrc*WhT*]\  I R<,Kř,ɍy<";qv p+2bY}ʝ:lí3ZV!\ǩk0j[l7XK1];|1DI!"ݫR Jtu- q}Y1UNP1<X|$2ɟ]l=lmq5C #cUV JՃ,cڏ[-Kzgz=ɱ ĻΏYL]ØÂx)\+eP1)`]{,@L՜>dmNVinZZMnPP:>ɝ>ɾ>M~ͬu+U".QT=,ɻ^+$pI&=,+@vZ=Φlp4;0m+Z,O Q! QJ^)<Ėlӎ;eDm>ʟE( H߿߭LY2ݔ-+P$PIn q!r!'G 5O@o+T S~/1CoT{Y<%n5 a,!"pm+RwiM %|N_hM/ ckJl^5 nN;{1OP$O1. } )`2PØ~ANU'/+N bJA+2p  A++ Xd0FD+XX +(qࡂ81"'1|Rb*K,kXat^|إ v G+,d!PrYU^e.e<&jY (|C-`Mv;MӢ&Lۉ?T('=@(zD9b" (V,%2 ;p),4V:(%) ChZ7Vj" :zС$K=ZJDQJӯ"V@YM.`.0RaHУ=#'*z(kR*($vB" 2q& (22!SNQ=dH@T/Qղh*i'+B*PEVRSM{&O {H .b,`9 TFز4h1l=*z)1T+V)Nk7L0hRkJK+X9v-$hAl* bZ /sIH9:¾hp'= 8dͪ@&PD*hfA@VLXLY֎\Į@ܒ,FGl k""SE!B 4i pbU@*%K15۬{:H$Wg 8i3Pb4o 踘x*֣@c(wdžokBd(h"Vb(FkA^C4=M+2I{&(%$! ÚApXaENB:"͂Pp֖!ѰBnR(y觕V+~UA"3M;j!ka$F0 Q#†ltT*K% O#^c7<_lcC\!h8vY`P"P M;jb:eS3a8≌X!c{C)&z@,1(E 2J#, dZujEE2Ce/yip2c\36ǂVr$Qr*V|OI QL D4䰀WO,X eA<d"p1NJDYA9ل $#hP: j$B&~*A"sHldW5bVXFD ˏ4X%@䲊* ˾hd$j:R){PV̢*}M30cvI&k*(<$aˋh Mh]iV$>&&RPj2Ft8WQqh8S.H rtLȝ,2bg($9*V(@t" a 9\GV RRQ~WlAfE͊VBOjAL¼(!y\(#ǫV qD[U!d@50"gM0d6:r 5/T2ѠgxRxN Q[)yҢjT61X8Gn:O|W? Ԟ^h_<,$-7J#1j7+!…'+deRC_qg 'jkega-Y*IZ3 * E0 )qrB؂ Ѐ 4D)N:Nh@#ǂFX Ys`aXt 2B;|I\CV( eLߧ9yE\'D=3JѐFo~JZa )NV./ ސ&$*Jºc)H=LE'vTPz*Jq)tnؚ:MdN֭.O A?"U\d)..-3&wpbwU[vK zݩkU5!RgE {]VA {/IGX'^"]m>7QN ua b`~mIQ%(`Mɓqp>Ai[жxBV06DNjE(/ZΊP,,ejdz1Ƚe^ ~TPs~pC-+4cW{>lck3m>896p.IyX x+⃂< hSC֨@l! 0 (˾! ¸+?q (@49`/:pMb<A)كQj =Ïh8ؗ!@5e5$$:#@ 8*9=@2۔Z<1U5,2tY< \ȉwZ!鉠=2 E##y|A8zQRH;Y4cDȄ Ӽ,يCAF\'Pq) PE^ ۉ#2(L "PA؍1:@dA*;љP U,+>4YL9((G U܏B>qpDEZ39_dܙ+>%ix 8V $ DE'xX#+ bMA O9QI+@2F+=.P2I+z;SPS"xA=Њ4>ͻUBJ =@G -UF|?H!1,JbbM6kݝO>۸<$X12Ph; \EX+VxSM :V׀<) SX ۛ?#@'֡K 2R⓶М*5R YPMxA=Lx'Vİ̡\K#! Ra )\[A3<LU0&j4 ya͐aD+`:TkHpqCQ ndP_AO ƿILt |V`=xъk܍żx@婇~<&H8O 6Gm0 S8SӐՊ( [ٸ@՟U X|[y VHXU Q MҒW,) =@ůLH$鳍ZsƑ4S;+ħE$yNI5[x]-sSg-`ҌÝ#zOL\SA_y[M)f!@Uf:}AUU[߫URA܅QŒX#tVL糘+ɒZC ߠjcm:_T~/p.+Rɡ (jj>::ʫЋnk`LLȄkNl^lnlR;camping-2.3/extras/images/boys-life.png000066400000000000000000000107711427044526700201670ustar00rootroot00000000000000PNG  IHDRYIJ]gAMA7tEXtSoftwareAdobe ImageReadyqe<PLTEUUUPPPPQ<mIDATx읉 @y&qM[g5Ҧ"PJVUJVUJVUJVUJVUJ m ͹ж%4eyNgq+"M6h=1lcFwC6Y>Yag&d1y&ߡo_D<"'\ YYdv)Ȣ_YC5f(JgG@t{6,BpfQQ6:onjd-lItdqf ؎΢LD&odgY|~ Z{E\my#mbOpsg,8WUJVUJVUJVUJVUJVK})&:R*W%UAUV%/ʬ:5Mi'0WFn"_Il*\BqI0Jdgm-cjgek^|fZ֟LA*_NbṞdq5O"ыn?X0q6W9[,v/ vY5l}ŽJ #'k *0vyk ٫mF!dQW1X7ZKP>,}0_A֖̬gi1do'U CNlr Kg .=IТ3aC;, %r)d- ]H(cPBqA2AF-YJ5ŭX1t , QM*ӥ]ۍ1XI)2okO隑)%).#.K&q{ Fb%~׭K kdrY1\".-SvC;n'hDmn5 φ)X!VMmY95Ff ߍH܁ 4er)ƅ,p Tc@ onT3wkUUZUZ-k_h,@V d-l0 S{-? `{mBP.Wմ.x%+ׄMB+D AO<7PJ p4lىzoDk?A.Ďiv7E5$hBIU1e 6GŵMlwX'vڊ,$d蜯mȂ G0VYVוog JԄl}(7d5Rc+%y%;gqJ,906H{ƛSA6]xc aj1ژd :ȮƋu#.Dݾ"Bd Z:m̐-\ 3U!}A5Z3HQBB`5UZm:`ndaH0^sV&űdD{Mk"!ҀV(.: Ɵ4 Jed/2Fətvn5,^ n#)<f3ȃLjjQJV9dԜɂ,4J6^foLdsdi9˜eܘF:4)L"}"IUZUZUZUZUZUZ?`aw17IENDB`camping-2.3/extras/images/deerputer.png000066400000000000000000000114001427044526700202630ustar00rootroot00000000000000PNG  IHDR[f gAMA7tEXtSoftwareAdobe ImageReadyqe<PLTE U2t+Z3ɨ1$Z-fس^k3g&p. W/'b*a22pR\qX.[1^-.ƌXx$lz$ķ~@m%ί¨Y.Ks#uV+)E|XĞ,٩ }oO4+W-~xV3Ty5,$1.ZGt4ӵ< 4h8ɥ Y%&d9ޱ:a#["ɳT22Uh]8\7\/]02~.ŝT% Y+`2T%ҬǰGe+TX*U;2^)b$5q&P+ ^*9qDK6W8 Q,۸ ɫ]{ [1Y/ߓIDATx[ضhqb?(FTi۱)Sj-vNŃL_kht̜]5ksBppp%C__>S;L0dftzO^<1>`<><<8?@oeNÃə?`y^|s8_?:awunzEnG*,stt{tqso|o7_MmF3A4Λ4rLٔ$M$k櫑/37cX ̳uhm" (z癞i^W07GG4fl\.EIo@4Eϧþf>BגsYC,>F)dc!u0EDQ.q|qË>ǛG%Ҿ?FLht'yz5Poڠe6-1%ITjB"<z<}:D]iQGc_z-[iv- $OtSXQ1ǞF{.^?͞ u;li/^\"*ǎ o`!_åLO8-*7m`kZLb7 'q`>s rJ5]huE򎭝'{%0Y-XEX.@h|Eq0v{%Z̀ѬeN[KW?>f{~naۇ%7~40ۖ,+`l)~O\^Ů~ew&ٍC8< }<_Ỳ-5ܲXa,ÜlegК9SGz\ph{/=ٯj JSgc?IlBK*4TCr陫c@ۅa#`'D!q}ab)uEzĊSy]ܓ=2=Y ̆&9]ί{S5J,\؇Ǎ qq@pK'0pV [6b` W%\|;f>T"]|]/<ړD 2 D0ʒ$[u,Şir= cvOKBDZR>ڗg0s)K\|ff$D>N\fxzy*f8k`* e"s9;B\bX[MIg{$6ƞfwl9A {ؿĜbg e8np^̞铽e̶q( Vtq>}|'KK[*θ  \췷+mj=.+*1fi)]b%rikLհP]0@o c9ڥI p-9ᮖ:TvxяډOB8}CYd ɩb ̞21L?G釤k N#/E5қ=uO}7Rؘ%s2o`䄙GEKyU|?޵ݴkykZ%.q v:3 WqHB㰞75p1L Ыu_~fl9y,\!{ru5oB6Rw|O|źakv%f 21вZ7^7/C,MN@XZ6pnXT+v]W~;gyW>t,+iF=ߚ^ ZMԵ*27CuR>wKl¯ f]NJ tg.%%\r\,YM[{"6dx.u^#k;;|lhߏ=C4mǃI\2r%S|(quX !D$X.>|4Wn7(_HmO}+(`Z!48AI4m)`-Yc5ivUfc?O_\-؎ ;Bd ZnP+a$˗>7LU.<ޭCMw!": kZ xO9/;<l3f1g+yPudT(Z7UgK}ړ==7hZӦ)-~Ŕʬ!A=B"<"b;ۄ稺аtEXw#ŽR{6B]w؆M3*C]=UBnͳFt($ח:<;x~u24eՏGgoz ;]ϜVɑy{egݼ Rs=ݹxջ^. {lC99ƻv`ǃ2m5mTLo Ш5$0AC;ȑ( GH$|gExE {aEP24saϣ !azcw9R2Z hz Gx ܓ|{ J2Ix*lz0[.'Up/ژ_Up;ai% E5y&p߮')_Q6Sj1Mk>drñD|i#IX͞ sCd.1U2X%\z'ʥb2a4DH"tϨJzV ri1µ5}WU/pQ8U[lpI)PU%(*0!*Wr Uɦdpt!9b f$RIj:t J&P2  Ոك} s>+ڙD+JTM)Rj}9nНIaD" `D(,wTpG>j_mfQ8\OsT2ICL"r+ !=Cq-NDB!t+нj4U 9K=Fdq u9˨B R VelTw (x8\f b-D`Б5DN칕b¾ [@ɵc-7$ b+eHZl"? ܲ2@޺)A1pk;p LT6 Su')-HXo_ 1ab"Tj "ɦ4L|RT'nC/[̀(0+ 5(j(m Ѝݣ3t[^Y(C;kgbDnW[ńΨY2m@J`{z!.TP\WdabyJ!E7|c&&G90:9p[h#WS *`WIENDB`camping-2.3/extras/images/diagram.png000066400000000000000000001056621427044526700177060ustar00rootroot00000000000000PNG  IHDRgAMA7tEXtSoftwareAdobe ImageReadyqe<PLTE""Tvh,)&&rrrs@$MKQQQqldžȲoIF%ұٯY4L㸸Г4 {W4Ɩ`;o=;S-Q,`[)qJ&ϛL-9'?݈{d>[4xݺ K}˥ $a܏]N_艧ݕv؏YzVF]!^݂VGڳ1+ o_[$1\ n?nz3<Yyxi^ :_$ gwV YB罅 :;.vw6 ]plyމy~9/FLv3}o+QO;=?BwX3~/- -zminwΖˇ zr ?KqsT=4|>!jƿ9St=o&3򍩲8zIy=_do1%?ٺ x??g\ynj\qv띙mhM{"qxxj )2;x9p;#; /ΘONuM6ukj~͍wCԟ]q膠> q[i^" Yׅs^]wy%6n w'Е Yt}ya5CБŝD/&\$x5Nb}Dw6v+;',\h3b𬹐DegX1g 0x>wnYA΁!醞qN{6/t8|sZf(Lu>h DL gg ;XP .iB^ӱai.r{vdTHvwO$!UNDHǗ %/ wxQnyA>xaʎ͞Bbc t K/z d6 ›Gyiܧz_Wo$;ٵJtttq, z[;,Vz>M;F~aVc/X<39TU}V{ly&\1aݑCA喕wv zۍ~G /Mm 2wh?W4Vxf\"4zƙѫݕͻg;uCؿ0m8#OHA84.uwYλ]O_sa ldo& x'g+n@wp6vK4wVv+gf ?+ osmBo߿Hl{ڵx.wu1sFߞf,Dad{|}gnH8Xx/`W;3%*/=]E vz13p#/Ox>IXţUgy$ [7XK<=4l ?n$6J'$lodlz}gY'r v$ۄ&+.-U߳[㜟IO,/lB6^]yxyҞ}:ހ;/̦.lHO|,4W&Mm^gx UoK+6Ͽ;aoޛf)0lo8 OjW{r(.uپ/gHk]tqL/Ϡm7g'7z"ʹb2PɯW(m:@<5l:>u z0NY'6l4߁/e›ؗm?yy'ۯ]^=8;Mk^*QG6$ZX/pcr6%^rv{r9n;?'(pf^vec uY7~wK{ߏڞ~)Ý$'Y%~i>.7UK^s/]N=qii~Rsc-?o?6ҷghҗZ9;c ]csxeu͝'Om~{O RCo45{o -S_#/ w;EM_ml ve5arsu[|saug7ymB _\1vjo?Lx!ow瘭!ZbV^#?ǖ;A7ɗhɤ񕦗ZɤLFUZ˺&ӗh*˪x|n5YǃϧdM'x6scQN_}XmcKB@4#i%$ 5KA-1wNO͋uzc>l7ނ_ R$~sgCf1j:}p'! I;:(3Pcl:cGt F b3.揢}F`eN/σ?Qt_pIӳHViIѻ?[;|n绕~Ϡg~lO %n#юqE{ 8~4u 3W=o[&kv h膓'j=ېww?po_ B1>ξrFxiA.o- .v4%{p L=\:FG({F?~+v?XstO#.RLTzfmg G'o vNF8YcI5Oڞ"=SM-nI q~//uo_Q`%]KRWNLJN>\3`DzY7sIヴx2gcIT{2mPdt>LxiMx}^ i gyfm~XC>j%~{m ēWդJ¨k F]R5ԴAbt9]SwC+܋cXy >Bks3;]i=ӓs'o_Nø+r6^x\B}:J|>" 5z+ff\PEfl:)3fUFJ\/@~ν|&Px/b5MOav90~mFwt"Ƣu Xu& n_S GλO_ !U+#{&!Id0gd912O &F^0OI5ljQ\|nR` <_l|foMO}n|ΛM;xF"GӪ1<; :vPV*TUJZe2)|-{uC_ ّ0b}"?rPKfX+̯҅Xڤ26.+f(?y'<($4Qx)+FYC7I_W&#%Aȉ8Aݦ(#!1sb*؋yz2r͚#>"_Bo|Ҟ}tNqp bOy\# MkIx};嚘N[#')2 sa0_֍NգX m!%1Ad^W9WL&|쯦&ԫcPI!j\خ)IuƧц؛^]fJO8caIA e02IO0aRIp Iu aXȪN|^j-WC0v=d^?]6U/Ƴ])4Fo- $_Lӳ襆^v ;1,g7![ޅ:'xC.. ?,6]9btɒ8$pVO%L2[b|k! "몛s)gӊ b5$lluc=4#=$N4rP6I"ozY/}aMzjp#? /Qk' ܪ _$f*}).`1oWHL4TcgI؇Л~LS;N:djN(IFV+tD6I\2(zV’sjWo 5|Xw"1`7^TI?$ۀ){+)d39Y4dL*RMUHHa| [!XȴjccŸ`R5X+HL!mp<ܙ]慙Gtt39玬,yEQe$n16#s X 50dKe̘R M6FR(c6|FΔeKHQa EζY+D#sg']E5Cc/[,,/jtAHbhf?=ĽM+gr,E[ YYI؂[Y@=;;}k|.Տ}+ٵ]oHÆ:4/ K`> &nq#!+dDPs(*_-p&c$p17F/1-k$$#Ve,ޠTby!4s7  ^II׊^Jzx{m7 ~Zȶ6KjFv؟`0RweEQe(R.g*e؞S!d(CĂՔ2.V(~%VFtHl\Ve:. ȭ7TI&rу^ۏv=)?m{xtXG8+frsW27%$Cےx<مooK펰MϬ `"x\ЕZ!VI JZ͔>A@)fTAUTJR,:9Rρ":g-|BVGM!~޶`߫?{?y/OHbD/z+?Bu&䆭o8e&xW:[e ;r39wsx(K e.e?;%'(sMcL@5 _ f 3H)T]DEqO31W[/|G6}>xO~Yxo\"xlje'-q7Jn4^5ӵ{<$KظzmtZJ4~WJ:P9[༬s5bND$`O^.@/"^q`QTK l+9&/?!{?ȵ'p{70btq#RrIl߲^ZqEN``K&S5K>!ϖlZ~W傯HzsY۟}B* id\@*@uAOe &c;mub Q#; iC瓯t&{ǕV0@o5.GT0R8+QLDD^aeAW<Ų٘I:m@ &- 'ϟPd2OY7c]X.?9}t7,i0A tAbKpXl=b΀?W1.A&MQeEm?6 ^1x}l|m<ٟL_ |4ΩUsI}<(Pw?>xt8*K8N)Bh1U-ѓnBmm\TSq.ovlEPX dS7ۻrQpozNEl-eU1anTɗdiLE-~x@N;9$tsWݝMʤ)@g=̃!WVҨxB8(_mo@~:.~({7Z<}`tϯɳs\7P`\]vܖ+?T5P {P沁@*>06ڍ0y6˅Du`NtsaR Xˎ6ݧi&@aF"8S*#c]d-S9L $A/PS^wˊ[v25=j4g0rXA/%^-GqթT"[7P*ciM}WfʲVu{I఍=Y7<'< r{-;XӻаdgV ;>!zWsQRYz*E׳ ' SGx~V9"r~wcdZA6T@'FNr+dޛШXڶ}ԢeLL4ږ*[k-]d:ϰȳ{Oa<%dƃs,RDknfԈGȥGd 7Tfꈎ⯨ۻhwLѹ.\F(jБ Fx;IVքx獌2cB?jڈqs9*yFq^L%ayzuN~'l%S?2gY秤;=(tzXr#&>OLG݄_ެomzn w soz~ VT UK*TkiMܱBM :D '5,B: MP(r6F~fz|NMv_8Jx̹޶_~aINcWyjIwnaS)yaO,!%MJ;YBS\]!fjl|j}fdooHOWWC, T5TR q UCMtNpLP V 䮫W; 9 O:)=,KtZKJҤ2seQ a;sp~})r^ט3p.i1?AH dsBGGsGg)+Qv.}j7oW8 qJsp~oC5ݧ":2* ~mIt5`n$#) U67ϣI/F߹#TTQD4eL'ĬRhxmbTxZG'sF+uf VOpOuL Tx54:͑W-8D6 V͂[!1"ɯGr8yt^= =c6*X"A)cľ<ϵJd }!WF^ rC]$Pǥ4UԿLӫOf~x33vp14{rϏ 冴#v֫{yڻGYGFnv߾ýܝې8&CǐNHf25EԥUT+%K3j9;tRR9$u%i՗nšH!E_Øjsskg"$]?ؙ)㥏&Zl~ <2@ 57B]d+p3l*LMgbWZKU*䆃F-I K9L _P'qbݱ$r9eU&S|*"4"2=݄Gg^&M^dŷlnrR!C:ηmrXzEyvIs|UP4Sű8rbYht\#hFF3bh@3d]IH tk2ؓ *+2ڤ ;?.T ݍU7|0HJޒ#W^t Z74,,T_df*玎oۻͭox/DNNG4}5֣0B.td__ r;bu^vTzɤʓ \~,MnD] *Pݲ|d&`q~ܲOyIb#PkU2V!OT=t{$~ާĒJ69DbwY񏥯WW 0!o<7ƟϟG*3-EFEI>MGaK)Zxu>^K"C L)%yV\җTJ ]*d+ 谛aFgrhm[LڑfxgWYIc-;.H H sKq6:PG墓s;*M>L>ʘf)!&Q'_t\eEk5%'^}!AvHsFlyYdPx»“a0fDMދIF6ڧ`;_KǧQ8kN)/=_;=]nb`8 MO^[Qh:+HgPgogWKh.TtF^)heϢZ W VކSd|4jq?E??";JRvlM?fW_i#YO_5Yn7d0&^4E &;8wHXR^5rUɂ\FU*7'_f 6ǖz 5H+`lF>k rrmďyejﳡfmGof|ekooK$ڬ#GB6dGY"K~1FC9_ͫ?6W˒Tܗ,NQ\Z DчR aT*$^[\gl*DJ-S)ܩꓱ,S<'#hLv^Fwϙy__ R,juHSwx)۝wJ`\UQ5 +,&Nr*t&eĪ/]AܴZhHީpbubERP 2J0*R@Jb2kZ|?UI (@qZHgC1gaΥ7o(_A6t* _#cʺ[?%~ 'p(k1Q #Rt(5, IBFC@)'Ǎf5nޕLGqD JF)v*G ^BQDB2zmɁЏi臱 ۫o7or}º+x|4 >W]XW [ɻ V0[*$h^1eIX+'Ҙ2ii-K=: f[zӜ/P(yms\]Z|=&6ߎB>ݖəA Y*w֗޶j#KEXJ$<-HT{ǫ#|;ʖAmL&HJcxQBLVdt F1۞L2Rø|:Ǡ+j-Ã}\=@k)zs˱w{LGEbomޞɹ!i[ ){+I߼"Ib]̋sW79 t1br&V)QlZU4rH3}A:𥆉ر Gu>=JQ { uxKQ 7Xhr`( D4/mOP O>ə9sO?q1 ;)h*pWBj7j _9`?&v$r;Hk:_ ̨Vy 1*eld˔[xPRt=`0$ j r$<;MNƳl5|sz^kc=Q'}}wo'^_9Ǯ7Squº&)HxSD#Qh)N oۢ$o(:^Qڹܻ$p(CeXYA)z;"tPjQ ˡ]7m.d0:,5Tq[[066*8$29z8rj Ң3mG&]g›J gb)=m{3y1'g|pwy'~~ۈ6D4WƗovUj 5h%z#4>+٥{[wB&&p$3Z*) d P6j&+t"c †`4$j_"9N55d8?vfcL/\79ls_ZJ,%Nh.5]J4ֵZ 'ĭŨk8w&x+f8k~4`%<.2&PbE&LC!JL$3FM+~Ti>ʣ<ƶ%5:xH>C5[x j<@>~ @s3er=};)dn4HɅsc |lH[-oOR^oO r% $/ғb<"wOe\*ԥOEA-sԅGQ5p0K+5]=Nنw#*\0WCaZ= y6@f~dr29K%[8l7m-`ƨeCܸb+f1djFZ-5%{m1uUwsHu$X $Jiz F) 6LAJݩbؓ D07~l3{xoZKBaZ!X4RWAB 83WoថY298.یh>xU~ 2 Uw&rKDӛvT!Oԣ-܊=O\¸Vv/Ƌw),L" Ik12JAk5_1zL+԰"F&fn<1ˤ U\: !d72^r5ə!3)ox/~lc ԪT'FFa|&gs̐&¯\jo$>xq+* j$zMvF⌭De3\+1zPQ ;UDNޚȹ*͊NQnb&'F&'RCٕJ/v8Oc39d1q:125^6G9[Kҋ"T =QtVb7w^E xx\= LʨrϋgȗQ uq{j˰dsz!F5oZ{+Q[!ag2*fb.Vq9w#X~BTx&gG.+:B*P!qMUs~ ձn6^e_DWxgs%{**DqZT`!vfE:.dL:dvdwAw/V~= {w,;Ud @cBx䬝Fs;=Y_^?&d Ωh{PY\ kW(hjxK^[x 2ۄY)+$;SH_S,VK>]N'UyAlb1Tgͳ$DZL ,<75a۞4<]Puf 8-kc渘GWCAM,\s=ZOˑ!"{-ơyz&:>F)OpEV`*'}dŰʫ7E^6p8Qc{vw^bfrA .Ι oS)>vN&fa+A aUj\.}때 j^y ͏5r_]= $pɱf:6P T.IHpa;$E[Wx̺6$e"՝VnUTGI 2QK(n=-)>ô+t+1+y3 Fy%Vb{_ÚrV"\ԦÑM\-y[Z-sք{~?h_󓋶:9rɅsJ`IlVorUB4[D>50t[p~N/6B.~~K^t*}TyՆNz 냵R@"k< rըQ"צ]+Ra6܅I<_L}_$v7.ۚIm{*+X3Da" xep|o^a9`XC⺾!櫾s='Z h6 @<xsepQdD3j\Y?ƩiW1;VϾ*7nW\ 8UgfS[Z弌yT\pl~ ؘi睊&ӕmU=rj< Y\CF`e_8%`TGS-Ë_{Kyܐ ܩbhLCp"E{X;r[Cdq"wpnh[[Vlq5+f<4M|64VCEQ;KV{Y޺G:oSgGwLg׫zj蜋av5%Gp8c:f?hrVv~O1O}lHO,Z0uJ.q 7g^lt|nnp[ 'q?{RzsSO40+ JRC&)({~Yyp'tY¹zCk y3LTGi1+KzR^ _+Bx]=v~ {VhI5]YN"FaRkxV_wI _'GIit1ItLQwg]S_,@0F&<I)=?>;tr<:;SW~#5\$O)SĹ _<[ݜ.>qz=R }uugN$\w=}ywOLܧkFFj,=|փI\rsƹ |φ ׾C471Sq? ^w ~t .H}N^y|3YFsq>d皒ilKx_r֝aOoܼໍExJRB~YRt$yK\JP~!NIqzѡfuxZ?u#axouG;S5%Yr :NK~: Oa7x;tKR ;fsK.iMCO'sIj]zm_g2DN+ NUwkӣ&'LУ goC^o9<9/N)E6;vk|:]qmRCj9{ow2xkoZxM{ːNMRT=]I 'ɶ7ț9=[S$mM8wm%>pZrz\:ܖ$g o8ۍvSwoCϟ#߇we -t2d;F}#OsRK&0#p:Hu tyvLzѪC;F]s\ѵFޏxd[3.oKNȷkH Nzwb +~KLD;1'}_lޝ[BpjQ6R`O;*owtK DFC,QX'pڮkXť jPl*Qf<'z,CRt}Ȭސp;:EyO>Y\lISXa_8bf0BjQT^@JED/QxN$do%֍smcې}[w#K c@Ge<2hᏅzxsy VD"wt}iZx4] /ރtD÷R惧R4u~v_Ñ92nK'Rhz1AuIc!m,6)x8B՛=k~d)bA[jZuS=x|!lMrbfxoK,N.x?C^/TۢG܏ W=w%i?ﱫv{{yg=1mF>ҷm%>?D6<6;K>]v0+{|xn9ϠyWrE\Rېni/1/OVJ8x-l`lQ(vzagӞhkkWI_&'*2]?"]n)Hz]n/]˜A]kݒa$N"oG&t|q\7[=ۇ {Vgþw;ݾ_J|votwy:z5u1Fq*9u=|?N_9$wݏ\q;zvkȼz~7̓WYrvq;.)/LS-9vI:OdUw:uo;fgsN}$k,К-1~?w :1oP0mwԿq\~=bfl-ҳp3Vi7ZFBH?RKjoџ;"dO\~k)Ue?)`t Ks65ؽ[ㆃv}萞ZҚt~m;uoxm =a|i똈imʾdon6/u=If{>FkI#%ґ%ꄻK%ajw/ĹVcao#o ~NN?{mQ l!DE !#J)OeDl/>\f&$s9zٙ |o=͈ٺ>hYVC Y2}Zncxle60on h]7Ie:U,KʕrbxlXrxewlwEXu|(ΙϽ(fi}P%CbrvD<ݧԝ,q{[=r19U L:({!sg[' $v{-*!5]gכ~R%Ep'k)o>bSҾ=YW늒mo^D lЂ%bhk-=ݼ3l/X[Z4# <@/IJrgloƋHR b$+/pw!%GϏGHw|S3GGʒIhѢ>nß=č(o<3]۩qU9սyc 7SN>;&~fDbh wqNe @teUp:689B;f^wg x;Kp7xhO+۪vPf.qY<27]XMx#x`EB}PpuktgH,,5n3l ~I%O&m*usޙku >@> `y{^=PBo1pOI\-˪,NL[JiEXW_X{N%j˻ˑa4/dvaoqn}t&p+W{6氭dWHe`%TpEoUNy cIdTæ-ܷwEkJ<Ouwz/K2G\c/xvc}h\7Wa+fntV7:w7ݒw|Nd^ƳCVg,d]9@!Ŝڮ|3|ÌwsnHf k>9clO{?H_}_U -*k)&Ef8"KAG_"`\̾f@Sv!ɉ4;/>ճod|=xd\47'&IL6Yfۈ.Q7T[PIb=BbG~%QG\Gz{;Sm$ gj[qD-,EO1`oQfPSڵƏjK}oӾFDP<xڄ<ї))?y_߲WҘEq& # eo"~>&آZgP47;艎:NDUA8y>Xs#i^d{zڤNV >y30,WS hn 2~>2rV_Vo˸w<̐IpΎ7-`EGM] lmUm5;.$+itoǣt+=w+wEw ɟ~HЙڮrnUsXk,͊j[ TD4kXH!ܞ`;l+yVW8O<Ȝ3kWyHV߯mĎ}S]+g*mEqE4nrMY7 Z4ОY-o"bj*i L 0?WN#.:_aqG[k+p' n-^c*}B{77m YN΄؟!OL/(`mWp|=ݘdN(j4cϹJ=jbb'.I>^y^AUdeUYI1)l^7Y]Ğsϯb+SMU &!RVOcM}fgX}[UUxme[vUѶ6S~LaҌ%,khvU0[ \cm6ifkR!DI.Y[sB2e|mQbP_Cñ/K_X}!ߟ]߯\,0,V2ז Uī<&U$el%ٺl8]S~S?!?wݬo(- l ܮf"2(vKEnsYv}B[ߔoΑkz 2kKt]$CNF#ޖkZTe`)3IuB*h^;xIѣR7A o[!5хN#%5U(516 W2I#%)*wgYW~s\l=)#l^Hj56щX\+)h՞-H/uV4-͢׬/¥GM.ߦQ_^}-NfA&idmoc F6mβ¦ F?ZK0Խ:IߺW\!덶9w&|14궫'j+QtRzzI뵧::?~U:4{Kë{A0 p;!Ξ٧toF_hG^A()2Dۍ40vKі\ʤqIwkliwymiL'm ,p'Bӕؙ] $F?D<6-9\|4^^;Iݭ[9?{ .1!<󋼶W{4bEol~c*.;(w ucf,sB~sB:ܘY%,=,egzQ~?nPX:jf2yL`DNXyW<זP{:oΑp'b & :'ihs}@K}a& RrGH_[dہ) Ft*  f<?fXw)=1>-I㖦@ NiNўtP܀' $|y as| ?xrOny y_1{#¦>l0={^A0+#d ͡PNM, Hȓ㨎ԣ3y+f-l &t"<3 6J^Z(s| m.9,4yLmFR9(~?Â\q7mߢ)?䉝<`&@i.6j.$3&ʎ P6ltX>i9!?8mZv |߁Csj7)qttip6ay ME a)loIq{myۤv\_~yG7R{OӋɯtk Wj`n#~j=dhf dBswYij>C |mu3LW{tmUH,+9b 7<8x;=])V^#QWag$16#wa&2y4A1k!=>;G+qS(W Dꟿ$6 1zuVA { V#HO]NX[FVo{Rc 9v=ڽ?M&Y˩ID{CrZZ*&JQe ΜNL-\e9u}8nTA_A+<ƭ:[FL)|,;s X Գ$j0 6Dj^N8(wYw1})r/ݣ`_śJQʧ貭*΍ʜXL;^*T;ےgo'yCKo €!1=QDw^?{Y'0}w\鯏p5fڧpΙviFSzg]Vºpğ2Eti&p>p#1>sߛa XorT1yǘ $rh"0 xl~ hz4[X\gs]3r5r9zAA7ɜO~%>w 6&0A"OfY}c\J _>-'4,{yE.[**$[ ̬-1(8̷ËRL}rř]$(cv1qPGw[G$P? ӂgTӅQRΌ>ǠQ$ c}UE}^'r|彬|JZ qn3^ 8)[39j[HǴ{@0 h \߀q'AG1Ҹ1^2Œ{ϲȚۻ~n.WҍI15?#gR@4=gWhu:[Wiљ!'t٤anաpJs9?FXcfڷSo!Ih"8;jRN b.5fEʪW ^:MP8F^l>Dx'^@>ltj3ّ'90=aцCC]D !N{ Z%L]o|m6#9]O5^:m IbCmJA8{ -}t:AQx'sBGuuk Ow^-rs = '] 3Pv`` `# <|t, P {b_'p`T [)F #49(\>':sT݌}oMZ< c.d|އ'IwP0^o.09 Le <"PyD8 F8NNG,19.=RV AA2af`l6I_ΉPEm< ÆzZ=ϣ"$eeAox4ޚ\vJĚ:{|5;47v%yl|X6P'}:]U14}[xdj dܡu.l}mGx'qrjm\03a M;n2?U 6 ۰Inos̃MV!99w;C,; \.݂EВj|V.2U/X0G + `:Wb@ * Yb^`wqط@>%q +sv.>u]T1lKwj}ћ\WBXܶl3bctMy%|;PDNȲI)=;l]svBZ@O0O[,RNob17ayyo(>ĵf&4 yW yLĐ1 ב]%OmLoB3R:Xʜ;A6vHl 'yQ`&D?<6om7]P*ĶsjZiotn9uR)gR2NKy'.yFAc[ ,2%Hzح\``ϰu7x5V .hEr!U/9T=Xȷ̒8jwAiCM:O^Jscς`y}|h gm;ZD Gљ zo!\;~E',ς>7òWym=G9mk/_}3hy(`c w:ë '+ޅfyO}xuR)U'CKxG" 2*t?=m|}w?Dԏ@F<2l̃]ɿEM#m̔1M0+d g$9Y/UK'! "eWb2$&}b wCdjὦyO/Sr89̇ÒaVjȢՑXw܃浝`nb֥`.%f9fe?R?xdG IՓeE=u^B:i3 `kn*CF|H c ] 6'ƴ6nItQtk0tG{ɾc/BDzTՈV+ ΥJ ɱVrO5DX`/[ Q; о`%ŋk9씜c„ 8<r'H̪w䚩.`>+ 6`JF4QDo~ǯ/?HQ:ty^x%t3bElM'9 [yrKǩ߱<˸E\R@Q`B8CN$ȈNB~Pss%˃K ̬@yL_UЍHߏ |O1<4a\<;i%<17^q% _ Y$lv5IAl~9'̖RD3Qs} ԶʄhV喎8Y(K,LgS0@c?=+KK*>VJ;\e f 0M56` bÏsݛff3RsOt[x8Ulͳ.ٹذ`m(/)H?ՋHi\o{+ j5^s5PDn@{^sP"1^P(@ZcmLY'[l15>К\zk ѓ^AjVCk.y$T-,BceT"Kax2 1tng&;]"=w5 |)PDod7T,G3vQyb)G?-_v?,K 48נY#bSvZD?l$襭^z±*B.L|nc"΂UN1hQS4ko5C P(P.j]84x~BOY)+pKi8U]N oA[z]G&芖,8GB'j0% [-G_ [ 2yh_o=xXN`aoQR Tj09^Z #1]KHzK %u^[$WW[IQS9mzc`Ci7OS05U/~A񒗢s;&,?uPl-aK|QѪ<t-3V]c<$h^ۣ{&_rYȵ1gZXM΀=ZN zc(\4z'{Ymn[qΘp} cU9:k&@1(<UcnϤʤ+5:ҁ1Ө"ȑxk{[_:ٙm&1h{og,sjW!mvo3T°zӠYu T&|lIM]Z] s;jEPilx"kף)=iZ Q<2ZHҧe&۹DIA!Te% 7-WVo(}V+Ոt'Q臡#:7<MPmw@vYE\{ ^K`XŤ[ X-\njIP:QuA we{w`6ץKaY--i_58><cA5<@Hvɾ4kf_%jG &\6|4(~y ۡVžF|?gʼZ>`x;z\,+m }ЌKvͨsnwQƚC~3сz(gOuD7Go;]d%aͨfpqT ֤_+ ?ՐQTqvMO59>)c}Ν;K ;r@ALzz۷v2D6$ѣULÇ3lqF///U9+W<~1c_# !]Md;%׫W13!nnn:}ѣGt]4iwAPa\ie˖ƩSKE׬Y97d|DH$&&޸q*e޼y{500]낵-Z#F  >"$\]]ǎ0y5-Z$;DU@$gXXX(J=]3~Ȥl$*tLtI~~h#,Ѥ(̽{Cφ Fe.FŋVzʕEi)pc}zllg͚իWpwvvvLwr?GNя'=ed<4ݺuS)0FI= #Vi&xX:KȖUDuJn~)w #ٳgFܹsT[.**JonӚa+EIE_V*jsh)콠+M6_r%a- .=w73j(]K_i:dY~S7,y۲=rc`rQ~lL雍>55ՕU6ab߻wU꺤عs']p]֭[)]4Vl>O;X"OHH6ȕRq|lu¡C؝hooϫy:}4M䈈?ߴi9Ű.$ZnApϥKy\lQOO `g 'ܕgNN?oD6}H)2 )>H3#E(sI`9.]b74f͚d5!҈O>A( V}+LiԿY]7nܠ9jkk[lY-'Ipŋϴ]#ȑ#iii" VjժuDm259KH!HsV3Y 믿nܸIȱc24iRR%'''+++J~Eܗ{RE6 Ƚ\n4k ЬO H\]HlYC\P/!$XU100Wׯ߾}CQP۰aæMhzyy;vAp#ٹs'&K. J Wq2Yʑ֗HE"RbHnb fe ۄۛ7oe$>T50ϲ`'sqFA;"/cG{FDFFF\\W QG6 _~E8Ꚉ5&Zn/[\ Xڔ'3O&F!0ދUyK-((?yyyT冁***4ǭPc(mR1~HP5͵kN:6]} g?l]^ׯ_{zzJ;xS?~ Wp_>_H:"NHP5͛7`tj90j(a r}ٲex wQidȐw[))]ܖ, PAVڒ%K ru~UF(ɇtW^2Kl0C0D 1{lw |xk@Ⱦ|&L9`)3(|NN31X(}Y_W)PI⶛ 딚)SFkѣ)SHtss8q)Ç?_dITGu Ϟ=|*Ut9s`h Q)/_8Nג3RFJ3ѸMvioodKO:/^J&CCCϟ?mZf|aЄ'@W >HAM[.]4sL5E ̘1#55ɓ?V3Bm۶i.d$! !c}||:*d?s/AGĉhY-[⬨(}'PF͛O>v׮];w n݂7oԽyɐ[^Ç W$%B6T_O|$<{D60Jn*58`<*;9?WTw^-bZ ~HtS޽suuZ>|Sm+++cc6m۷OPFFƍ7`U/{IIIR;aΝ;vž۷o뇙}v0'>}ZZ"Dy~3ԚԖʜp2%VVz˜DSP͛7:99RJR;alo!]vzw͚5‚B*1|rAQ-^S>BZнweތ9gVUC꺪fë0L朌o=De謀\x%5kM(}(5jЏL^n,aaaX^B%fKyIwelh"HC.M0 d}9d d˗|e󍜜9//`ʕrCJ1ߟ c*UZn$<ܻJ$(**R4i&u'&vbP[[[;;;$/^7o߾żLvhT_|Y/&. |E$D3gرCr[6R-8 &ćulXXN+Е+W?z~CxR~4J? ߅Yϯlٲ pGG)u5[uz+UjtEGԥJ*L:T?ޮ];sׯʕ 8 rO$G*_U};t0zhEX1cK&$CNӈ˶6+mv Վ!@V(}^=rh1{P)X=z?+ o߾AI:111YnH!ooorTѹ'??ϒ;*Wɔ)SP( (QQQRBbb"Emذ$9;;ZCg>,G;K?W%3gŋIG.ٓ}޽?EAɓ'Ç sќ cJ\%VjkDQgPr{֭`JAl}E@@vA.FJݼUA4H+j:$yN#5GMFGK,$ trr*L566*Xڐ=p]3e(#定B"Bw#Rzeƌ׮]17Ǐ/]$H~ч!C+Wjժ:\uv߾} о}{&QF(qI4u7[-'`СٰC%CuLɂxb1 _iFX@+Z܇HC mF :1f ߥKudv~\\\_N`2%os):$d7^_NA( Ǝ{-\.))Z$ V-X/>_ *PӶ_'C0SQ2e@_~I<#֭KiBJJ^B><xj:ҏ @UN:Y,Cptt$]փG0C0PD:H*W^e}.Id233S(,,SYGG=ǧ^z?>zݛ5oݺ,R"8HwjjjTT|gB J?=ʕzF.@J_.[ϨV乕-$^U /Y#bddD:f[hd O>HIϞ=֭K:6F;$$$HN"î^z%70d &\@G}`2  XıұI.k.}g0ɩ%KrCe1{K'IZZWQ/_5iΜ9[n%۷C$yyy\IN@+ 7_tiss_ΆXd*:TBYfGm[ZZcRRRRSSmmm]6֭/}XXdѣG'N0`ÇWx9ۇOϟ4hN:Q>} 9%8կ.r{1|SNJIe{… 5x: lC q1bB>rHsɩSB9Yx/_ʖ-v)A g苸k_`/dooo8qu`bx^| Sz. )f L`9R|Ԃ"a7f!> ?VsN'#i`&2>>ѣG M!ifm'@_7 S4FEuovҼys^`҉Y{nAXjj*fvw*(EØ'NOOOII Sϫ41xڴiKaĠA}6u_bbbex񂷷"ۆC}rG{dORp6!C$?K9 wj5j ǀE݄U0b)SZSKf>B4A$)677?{,jGDD(H+,,\v-7N:a^!GСCLMMNdK:Q3'^sܷc'o߾]pm}-&@ȕ~.=d_ *@}vڿDUZxċot"/_|ذaԺB>z@ 'mڴgݿ?>>H?$; $;1;;͛7*E 9I~ NիNt'-ZJ;$$$11Q$n›5kҥKUQ,8:D6BCCCM9̀'= 6l D9|P2ATL̪UR "2y/\vUG1x`s___%.5kL 57oޔ/_,7#uL1Gi۶mǎ=<<4h" Vx=hK.- LJKTAJfZZZ6?{l>LL`N~~~aa!ww'O>~'/ ׿ 'R!^B{QAvȫA*:Km6}*~@۵k'HkUuC_D 7y;7hNj o˖-dmZ{WBСCP{]QfggS;㍌519ٳg4-EEE_~@m~^$4XY"H{qY۷ϟ?Q,RL MSL]jXK߿_n)R#!,_$=vX)|QQQqqqAAAJݨۙ|d%C 0%@>}&:Ξ= 1l4lؐ8B 0O?}4`M65m33Сԭ~=DGG'''szH9P VԘ!K*Ezd0gLhf$[^|٩S'ɪܘ/_,_~!k[:ȑ#]GIKKK[5%Dw<==a@?~̤t辤,Yя =*G/ѣ߯R4@O?AcuL٤IhNFZs׮]Gf,H%<<|GG1cDyCgmP3}i?dP{rrr$.]z7n$e~ȑ3fX|#rG K?Mׯ߷o_EG]\\+͛`Çm3D}=tD[`nn^zu}vvvGMMMMCihԈ9ssC7";8TR+WVtJLLSOt6A20hmM)2`/ C#u_vZ={v>}dRU)aRECK, Bu>s]tN䠼܎X%lٲJ=z46w"zgdda ױ^:77[bڵk;_ZP=zЇy /jbuH  ijM PUٳ˶rb6ip/ܽ{wuuuu}^Hy)oܸq߾}aڵJü~ZvbPPPy漼> 0pGR]abbYfMկ_vׯcƌz@RRRɒ%GI%C}Dݻ۷ooʔ)+W0XCjFX7f߾}Tmzn:^%dTG-\0$$|Smڴ)[iv>":DWZUo^;:88JFDDd39DBRɠ<QTCEYf3gH֭[5j$1VVV'NСCP(4X"M0A̱{h >^a/*T0z@9ydh(%s$?-aH- e!!!NNNXD;P@߿v H<Ȓ~5jJVWs"NuJN ,,,ݠA|Ј MHHؽ{7+W` ?uޗ/_ ee{}ead|J_ >3PK.ڵGh8/^iӦZja b޸qc O>J@aa!m>&_2 h޼͉'ʔ)CIOOə0aT+ԩkH?s=>Lj`א7G@+++}555mԨ>9{  cǎa6Ϝ9l󤤤(~bĜ9sdɒJ233d>G[\imm}vEsHoݺ+C5g2XjԚvJC0 ުU#go!˗ǏvUT 3gμvf)ӥK0V0+P9)SFjO z%O4tmll0?͛7}}}\bllydxxxFF|dn߾kNt~NbϡC^jݻРJ?=޽{Ok׮BI+Znf|(#ԩ~Fdǎr O>h#L#~_XX"-Ç}||0KHLL ?Wa{N,YK;#ѴiSNsR̜9S^+9#1|sssRE Tg/Y)))MyRRիGQaÆ>7//Pp8qlٲsp͛ɓ'1 J?ˎ &l2??=KIiڴif3f1YVAG@RSS)Wnՠ|<'ψ]v.]P yڷo:88Pu7>!(8Zj%Fo;Nccc,YsCGԥQFfffO/_<~sR>|ŋ{ʖ-ȷtB&L0sL~n8q"8s 󳼽Сۘ,m۶I6|e̘1SLy  $-Z{Ԑ4iwD=.\@YJz *Owر4a0'yKfff`` 5555,,Zj߿_tCุ8 KÔC2!"(йsgN8?ӝ;wClܽ{IT_kԨ+6rOJ~k׮}ńʕ+=>|1b]voD)ڽ{7R C~nX"۔ăF >4תU 6@VZӃrrr0K7}b_dddDEEAC2e.\%U'O{l4lؐ& p0M4o 0'%%|΃Trk-垞8KA vj߾=~Pu"O~MR)و' ؘ6 ˋ& RNn\ewH@%&&ROx-}|nn.è?_v ˖-CG8'N$xz{{{رc\MS 'Nسg"hkk566fqzF K Kh䀀jժCP/N d(Z[=uR{СSCPGތoo${yy) j(SN4iB_4nܘ4^cǎ\z2 ߾}c!ر~eJ (@`"s]L>pҥ/_?ӧOXj>2_g^&4wW=#33[|yVVֆ ʔ) A f"x-xݩSի}vLD)ׯ7008rfүK9I&G焅z֡C>}N:5..!( wP}ٵ3888`fꐢ"O@;η}||ʕ+wA44iӦGGG8^f椀 ؕ+Wb#@G#HJqPu,?YYYZ[JŐ!C3V? O<܂!ѱiӦdgDDR OCgGRRҜ9sOp~~ѿ0Yct"L|C M_eȑ3g3AYMd%J=ta1a&Qeddc"PΝ;n8},d>A$ ?`/Yٳ3/<үm8`jj %4lo??9K<==Ɇ 011X"RrƍUVرv\~֭}C?ea dCA|4E>}R 4po/ii߾өzy>Lќeܻwoܹ`#('?A1.*vvÀA[hAƤUgO}ԌaܸqzF5,Yڵk===_AuTGGG ?z(f;~ff&|b9_)gϞ-Zhذ!Yb̘1ZzȀ{`D%&&bfjA~*J*YJ"w@fff!%r%̼U ˗c#(mXXX۶m?~lKmf48>˖-<үKN\qk%qtҡCRݻUFRCDGFFB>S*OmTTy#(ŝ5k. 61 4;s…^z5oޜoV?333ڨݻwA>үϘvժNt钰+RCBB+4IՊ~@탃;e}tI_K.R{BCC&J266fXUŧ8}7J1ONN5k:^ZX7Nz=:qD&аlٲ_n'eu!W`;w?NIIaT@?ަM8Y$&&tr<\r6<,~~~|Ço۶ 6w3fpppz4)钓j;ߩǍ +Zo߾fff켸8::ϋJLLw^TTTzzz /_>dȐcBcAAA--- xRB(ŚY.\Hm9r?%KS~.]D9s@ᯛ<СC6jHRmmmWXE]W:I<:6n^`jj Fqtt4S 0Y}dd,ٳgϟ?=S|/zxx4iƍaZPkٳFQPQ 55؞2eH( ŋ;w\V-.ZPOA;6-{u!N%޽0} !}du)zQ|y,ųzA]!] (:`ŊժUӧ-R#?qСHݨs"={yRBɉڮ[lf͚&e->y$ Fvvvm7#??K||("##߇F>}D豷R pSX>X*կJ.mnn<#E~UpzjQFF7%11q oZϜ9ESR%=z4 ~q2eʐmC1Tk 5FIrvvֽPREzOjj[_KK 888i&** +W8YԉZnA=bT:E2$#F7of ps/Ԝ * f)5mthc7nׯ_ӧO%Pt:;;8p )) ,ƍ׮]{̙˗/[ѣ_zEW;QG3av'aaae߻wo͚57nܸrJ2+[N3E٭,bر#(,,]K/-n^ݻw߲e_թS'sŊPws8iKᶵGn$ZkAs̡)QƍWT w͚5 Œ ֧L4[nŸ,_Arg? E:w\FLcrH?zԨQqqqnɔX8ŒP`EU4i$11_0K5Ǘ/_\";rM6Æ Sߋ S~};w(4uTOQ@7o,?55LFih`MIIR 2< R@QQ'$C 'NHT l=z`c&_~̌/_y @(Y m7o"-{ %ݻw'VR'O޺uK9/ 0rHS?g(:f-Z3<%++래 P%|all|~->>~W.]4U=Sbvȑ#Db_/^=ٳʕ+cNrd&|\O8Y^~<CC0?~QI%{gzn߾!D722[~ׅ b'GP`?&hfϞݴiS~rriibSԟy~`bbbiiҸq㨨( ӧvڵ0e",՛[TT5]dpP9880r_Ν;~-|H~QR7nLd+1"dQFk׮VZ֭[Ík׮$?t-Ȇ (f͚`)=GN|rѣG"9 @Ք++4ؐ @ ߰aH<|te_~}֭X8pe˖ 'N ҟI=I u^^?'Po@jj*ՌC.JFK!ꊐర0!D~4i(5\v[/@(SR*QZl޼~ϟ/)԰aiiLvN>׮]SꇢL -m|a 4>ay9sV^M!RҬE8*NR),`G]]V8~ץTɢFbN:k֬yz~֭nܸL^:L]#G̜9Sͷ\O!PBNJIE #5j899qu+V dKD%ɓkXljկA>}J6Zjfr\\K" YǼyH`>ej^v rIREs:*,ebrX 1+W| gꊺ\eȓy%JUeJƠedx9 Jn# W繰Kʕ;y䴴4j'&2Dʑ\jkkD h޼9_jU 8p"߿_PngllWN,-888>>*m< R.%r7)wCCw![(T,Vv'`r)4iwһwK.0%d%_wޱ^zj>_f{K/O֭ၪWm۾#sT@L.?WQR> :(.ëg}0F dAV?ihԨsݳgim׮^bәX,Ϗ5jԩhūi2kӖ҆}.|7o8p`˖-" a њ>nݺN:֭06m/]ٳ,Nk=""GohhvRWXdI~~ʕ+MMMQYEH5&@wfjG >3gP:66Id-Z;d`Ν-AГ=NNN߾}c0 ?m> 7д(mRR M a,70[v3\ > TYDT^|֭wu֭J*MO'1:E 5kְ5##g0{ezj0Fz^4nzٲePP43}sRJI ˗/[|hsXX8yfuw8f᜙Ofw—G\%~iBU5WԵ y!E K. 2XiJ߾}i߾}{ƍUx>̄/; &hzsq*j맷8/${-ɝ7nܰc݇g'/)Y$#y(;;{֭߶mJn~ J4ʕ޽{wPPp<^5H,ZHiErROAgD_jz).O>2ky|aҥ pŰ׭[gϞa޽{wA<}@ܹ('^~M5D }PP|۶m u#;;q;hefCuriu2ǁDDD,\p׮]eʔ)W\1}}}FV͡&7k,{uOOO?~8!!֭[үh_TT5' +gDw`mm 0o޼@x**URڌenTŶq-Zx9ŋo޼133޽ٳ9cĐmx]t WzXϟ?_xQ$vNJJJNN&Yl2tP###0*TX83`2#A*%Ă%&]r6}~}]hsss ɝΝc5k$;v2A> FAbV6 n[tKBL x@@"m?fO>={v֬YժU#;MO-hrΒ%K޿?sL[ű&x~" dKLBrڣ~ “~!QQQ/HMMz?cgg m.ɼUArRWK-1obxܑN(}.D.EI-[PӅYӤ>|xРA999GQsufh9<&Ni* L9LiMËzH_3®b16gNNNJ:wĉ---1ODC[?B&8"""bbbJ,_\A ;wtqq -Z?{vo!BE"={>͛7ǂ!^^^ܹsݺu1[D􇇇_z6wԩ9 +v[3nի!>E2dرcϟ?5 )D;__M環uS4]D)< >J 7 .r׃J||B1۷b8eP%ètT$C 4H)u mʍSmiږJ\QJM:귳 G֤@)==}ԩ(m `ZY$ Q<40tH~VRɿ1C/941vް-'NNNu z FsJP) Kdgq}“~UPPPvvvZի1<;w7+W1bfauDUR f>HVVkXX۷oɞ01`QDD1C-WAOa?5y- .z*h~S;wã}6l@K_A*&&xnR٢Nj&ҽN:Dz//>r{{΂tcǎElYV֜ܨ._EpxS=!k::yV&@5PpG?}TJ|Yp%KZZZh}o`ދ+W(C_*y_ҕoU\vaN '] 0`ӦMM6ŒC,|KaOСCϝ;usss㏷oFGG'''شmVp+Dnnnf } 2ԔIH4ܻw1>9ڹsgaO0!$$d۶mرΝ;8p:k陙 999wޅ=O>-_SN% vv/_1 ~<++>Lxx Ο?ʕjqr娿?.**sTA/·uֵlWEDߡCGEFFrsPPЙ3gn߾ 1K7nܸtҋ/&ϟO011ٱclt…ؑ >HJ֭{],ճׯd"޾}-|\y>5))ĉ߇/YAA j>\`φ޽{8p&ߟ;w۷oo\\`+V TPbyED?<<|4O4DDCf͚mbIEDݻw~eBBBzz`bԹivZlQ1Ϟ=ԁ6mvZ,ϛ7ʕ+?~d]B*U@K.dɒ 4i҄#00pʕ7o߿?NAM>>7o|'hٳg]tV^:o޼UbYDD;**ׯ_[a[pŋq  /6 Hsrr233wڅ6).?|ϟ?wuu\ !+S$^]fw~,,, h:pnAP9۷0>|o  E矓'Oٺu p. Am7oh;ٳgyɳfͺy&/_~ٔ}yՕ+W ƫl={vaa%׀Dˊ+"##5w5=9s'OD?3!/;w͛79_qWvQyiP| bBIǏAw Vt~rrn;?i\el8 ,sYaVCH@-$ ,!2# hh-A5¬VfECc޻n{۽9s>{7n_ K+W]t: rFdWFx?z׸߿-Y'f=j}LLLvv7=ZT:u|Juӎ?600p &MZ`Ajj@Fu9***?N=t#K?QX"߹'NXd^g@dΜ92 ɚ}@Rݹs `VBwؑظgׯ_T3ooo?? џ9sc Å ~+|͛f{iTyٳgGe?p@||۷u!$][[N3k&sᵵ=jxYYYd$@HH 333.]:|Y?]U*/]%ŅYYY~~~|mZt:]WW׽{0P7[[[LuVÇ@+F Ųe${WJBBB||͛7G.p?l^{u_V,^x޽}~ٳ߿/..Bm AbjGyx{=* &={F>u;jԨM6peDRZZ@/0/_TTl\@ϟ߹sȑ#~ #FJ䓳oBk]իӦMٳuuuD"000%%۷oOYZ)$aAGZ'22244A2O<٪CZ]YYIA_[ '"%x,wtn X+Ю82k׺m222ӧΝ6b:50+mUPӪw|ѪM\\P477Ϝ9pO}8 ҂Fu?KR̊F}e~?07C2s0q$7mrt dأ~lܸ"&L`t:u W+#,]91bk FgU?~&:::,,S }5E]O<ݻ AAAB777@hX>'KKtX XhQK?A3gѣ&''oذ{ĐZc[ uPgl|SHRñ)$vG؊X.3&..ϏiTm۶AD.[UA$O,h`)Grcc)&f-MثR Y>ij= `4%H5 ێM> ,Za ]]]EEE3f̀kH?X `\mܹf[K5K 5Vٚ! tn O>MhOoDQgU>&MIt C1y :Cf[6id >8ۛT]]ڊaoӴP]Zc3^b5;,O DaweeeqK[RR.1 q3k>񧞩9Zb^ P }㴆2T Ys^Y!<Ӟ17߾}?qE bG Z. K m}"n"Sfg~0s:f~ǡ+w Jڵ -c03iͲ^Z ۦn-V5p_@áe xV!744])(R*= 6>^6$+Td` vaj8@9x^Y_Ii`΍OdqKՙG.cMte4 /jaăt RAcgЃ!?V'! ۿ {qh*LCS$ ˿<ʋp $~P3JGe#~"~ n(yg  $DBHvo ~H8 xL`^~"~ّBhuD^CP91 Ro‚%Oss0<9PJy"ͬ&ADDXDs qB[XCO`P3qI%x -"삨=\B]2~p B t&XQGaAmT]ka~p {F b /"Jj}4OhH_ %~~qFF_r:_v: @C1H\@ۢC[?/3 @|ЩdE%` h ;,F5!IENDB`camping-2.3/extras/images/latl.png000066400000000000000000001171161427044526700172330ustar00rootroot00000000000000PNG  IHDR3tgAMA7tEXtSoftwareAdobe ImageReadyqe<PLTEqqquuuiii++*mmmfffZZZaaa]]]MMMIIIQQQ999AAA===444ð111 շ---!!!%%%TTTFFF{~~~ͦzzzxxx|||WWWDDDktѯddc⍍Ȟr}ٯĖcl`TIDATx]SLWDrX$r؂vy)`E)〥wMy[*9HX Ǭj>s c|19s c|19s c|19s c|19s c|19s :?|gCk=Lcf~͇|g Z! #,egO|/,ώ<}geTX2nsfG/m1g><8̈mH[c`GbRr,p̀}x1 p$D #'ѹasvTǗx tٰ}PtW)׆S4W]Icrr@SrMkܚ ѺǃZH*9'WHhs$gCE-_6w^~uެ9 {JBEгJb,[J4s G껒@'b=(t{w=o1쏆jlS l!s ghTON}g@dZ7pfFrٗfw gW^]ḧۃ,*b˖ÙP>;Gs gf%e@RE/4s gd֗SX11a3<;G5pFFZq\L`$ZΪ ]3pvFàD7"P(˹ֈkEk944Ag0D}ŇU ?:Ls gilhz(wEQ0?eHdSɊ=acG!0kdg@,0Z\_r67/Gz[G\Mu@4ļ[md3ÿ< W;`bNih9'H*1em3 ڰ5$9yD ׮ceXJ뫎SW/l#&PNwG3h * aDfc{(IM 1YQ"[/9Dn5bOm_~F.HpȔ؊F<3,?V>^ } ˯f]}*_#w7ܨ|ukj'#f> ?>cx( 3f 17똅/f 2ĦQlWvV_VcxXcv y5~d`pҲLgޛ3Uq)ZVMi4>˖5b#]_s{c14>Ҍv:j ꚩ}*̋Р) =KWIGL£h͉5(~OGD=܅ czQ"s&6uchy*WNa,:?$HP(߆#}qA0k,+AL0pMDqI{_i 9XEXyظIlA0v-iCuw!NW | E>+pW>/:|Թ?W@_g"J@> fG 0L/L"L :sJFۥI-!FVaDJ;n; 0hJw7`K0l^FW0z\2ҷr7rBR=ċ̱Iy8cIu_w7AZ))4'0S=ƭh7 \Sc8k,W)~Gs gmTiE4pF5I&z԰U+wa%ٮ(a߃PpT=1o48lye^fؠ'`IKR ْSTQwVBN)%F Q Jԁ8S[ ^e C@Hts'wQH6qGVg*g ͆PPb5.nwRD>cP"D7j!{cP7!G$TbSBH:WL$%<"i^z W&2%A=) MtfgbPoPQE) eaWQ3 l1T<& ;/0K?ŰpEn;~48 rnŰcP"|TPfF !*$,G*w? Æz=ŰKFSaاCwBnp5ҡ[V;zh%4B thpFНUaܱ@=CdT3b%XW1TA:H o bE%MGFC9.䈇:!Zr4C:`aP%Q9DJ3yrG"Izy10?Bs+ha7NYݍN4hJRy,'} ,fP0JԁJ jC=M=F#9mFIiZ'g?O'!p97 1E9381pKg#97b~_»!Ե]ly\ ̀[pRCNKg9n!UՁVCP 0lpW` ahs(;a ;?`EA~?G0H"{^`hw :z '1\PKwDIV:obhZ;T c@(CĀ^hE Z en"7t_6q xHȄ96UFkግp -'Ùc8SMCXJګ91p@ttۃ}}, ;wa2E6¥j2X11j+oO:GQ?N7b)@::՚->;c8|%ڎȦmba ҡi~}VokdBʙrNᨃHwGx ; &eaQ_rۑaT alL Q^N7m|æVwQ1ܛéAiOԜv%@B841: A@v@b-I as ;9 rέH_>bwta5Vx=+BP]|yZ:i@RO{ }X\ڌ`% P/ &: !YI6[- hǹ@L1Tls&0IJ 8fC=/qB $?o$7V_yEgCƋx.ǞG_pJ+e9=?-[:j3@rv xߢߚ9 [$|Ǹ{8fߺ1xFFߞz MЏ5DYZ7֐Kt41NaO="pڇEp#~~ytA17GP2E^+2_ JS|n0lDTYsGul2E^. %+-uߩ& I@ll'r}W N>Z f8WMKk0~84aU7?vw/%d}I (gI(QBiсlk0$c<*S]n(ІZQofeۈw)Q5ZqU*7C#UR+5e@ns}0<1O z?FF!S`L+>@Wke HX`9,k$h<@f=nɤUİ1LH;}3! BYC!)M16Ձ`m1K"&[.ΐv K "LQ؞^3M5oJOdGEsSZRh +i HsqF3db޵"wj>QǬv̗Z#3$WOmQHk5}V9@yLWߒظʁ6/:Vf%񗶗2ٜw܎AWx+KbS'(ٺVŮ5H[M;0(ʏnl {bE15{@dI\jYgˣ&8z~gab|J&:U䔓@C\d`[?kY5k$/b" 0QinRt,.3MX;1u,u)0)@lJ]>toFZk! ȘU,|'w[ S\qV`SՄUs,*V^:"U) h0<+Ş;I!:H %8DX@z,ĪCXZsFsp2U"sH.Q8TφcNJؠ2*!AJ~V*Kpz Zc w&@Rtn(;t-]! 1tab/~map;eCINX2S-NůTgBz m.yj$*_\ܑ7Ə][$95~ %&S'u|q;q(C + %4 f@^i L! J_nqDlVEKⲞhݖtbXs$mƆ؜=n24>xT(ږϕ?0t653 A -%D}cG쨊tjT)2gXGaX:=|0:; #~T۱?f ?ò ^J]\^-T-,c! 9Ka%ˑ2xX !7N)K6ز5zݯqJبˤBz+h[s/ojZ!d2$Ctk*"7o''@8v*h`bGtWQYf6pyɅ:ƾ e("[.k|R+U鬕 6QJtLcT- 'b\r@&r`^8< G?72Jz4wV3P}년oYs=Kӝ&oՒ+b~MŦBNE2cPHKE}d:1hL@鰳@L☕p^ZXII&Q1il( nPR_n2A2CRxoƒd:;Abd[̛4uн=7?Y1zC!MӜE:}o9LI'yIK2;m:2)eJ^bg?;Y>cŪuqdy)Tsӯ@ / ߏ9_{Yd]ۿ[@Q/e35eXpE(n\Hamp>26'~65cHaPj0+0l>%O$y]831 4> ^@ЊE'*٘|K]^+@lv1HY  U? \iĕjӅ!m]0pC[Y '+~d|?%B ɣ.'kJ @MVS Ep; mhBs,BdDy^BPөPǼAN;O05`fozw9 *{,D 4>AZpUl"sf#'y1]+zxXˢt5d(ԕh[2$L 9y\/^"x(ZVPEZ /!}hd G @Vca^ yPI?`3|qEb[#r*+=t~U Zhѫ`xI+,i.nӃ⹳k*gF\Qa8c5 iM_!bQlqt9:K0lf,-[ZIRe,S _,*\  RGy]j6gwGյ9m1 s)zF՗ϯW'aPh4v{9${,&uiS nD ( ahЦNk&P0P4 9:o,-$peW:* ?{aH +=<Ǹ)<7Hi.U5 D 3n^aBT|knLin@J6tXд#vDѥh+!$1S=2'DTөG=:#,cȲXiy |ػZMN/P LP]BCȄ)PP0a#na Y}MO}D?i"`C@L Vlq{ӂ>D# ѱqϽ}UƒDS]քe E7$ h:î6ޤ{e?.|D߱EI;ŷh)\ uY;dɺƧȹ$p=+gB"s  nȴxG-vi|U<,p)õu1WϏM8_ddpa_Ie2scIOc$uJ'Ưl"1ҰuɋhzZ ZI5p5Q]14'/CP~}jŶ-ʥڞCte<)ҁ ȯR$Tȑjޠ ;ZlAVD&yʥg*fa%G oDwjR _o_Q7,l:N:ʲf݃;n6d*k JVbT)4ڽ$@Ov_ 7*dq?Ģ'x% af9nyvcu=rb#<{zj_LWEQ_cyք sb!nB*h%EN>bI*J;[ӉJ%H I==@Wm-IN9VXJaQ{1|Ni$Dh^ U3XjuېOq:aխ 1ͤCzGԪN#iZH5Uo0 0MXiC2ݒFn*{?72=M tOCܬi,zENkZV[e M8MgKm!4Y2~^myjт#}?T%nLyUe:NO2U<4ٞ7CvTķ-c(\%;J+%UmS_À(HN3sSIRDRS<8ZHts@`.$ Nu%( JOPDJpM84 Ŏw@ %TtQL0V6ZJa,=kKAÐ>pB35u3i8״ɮ, O:t̏^!JOayS1(Nq6h;!YnK+K@Og XU Rӛ'Ćy6=){Tu3ÒuE<g:d\Zڗ/=(-DrC){CYDf6ȤM)vaub%l'a K@&g7- 5sh` A1)0=򈓋,C~6Ї!-7󧪿r @H<2Rk AoBz0nPkOŵVBwu |E{\󘜧=}^sVGPf/v x@=i͕v1a$'ёPFLFȐˠ,o^L\+xb0,TɯwW defr.0ӇSeQWƦծ{ܙDn0]@\?3 Vu[uCfYH9 >v%⮘(PMǮ ZHj؆h[yV5~ɷԄ{I(uUfz ]J;MCA2F Q<bBނ9+!NP%fŽpC-kQd1/D<7xqpCW,UD?Y !lF 7"o1qG|'Y`S Z\ $EN)<|}QKO rS}(ۤ/5lHj!0^RI6e(%'OLf}y0t`(|WԜ-VEAmׯPS̸[z½LEAlRvA3a( DqT_I8}k6ze0L1|KϿTQޙ~ R}&RfaG,i!1f!% ~wfĽ <ȰҽKsĦ zI 2+mέpF6 V /Zn!g*+$W?wɆ^/d`e+VL*X^Hœ{u*ݫ'Jë)܆4'C `#}@Z=St3Ke -HR0.8ޗM>TӜޏ2T sZ*w^+26LMՀo6SF^Zi0l~Ծ3:qI .{4_vlN&n} ܈k<04țɫ^\& Wr~d}x[ >ƥ$WnCo0EUșM#K1rU\fC&W-5ѭYI=ht\4eVu2O]2ip'v Y`f^0+z~AiBLW P#ڔgRaЫ]\iv(ነ,x"2ormX,IVfؚ" Fu#LYY} d&$hpF1WCt6 3b,|Zޛ:mlJ!2{e{9˝2Erzm,d6o=1^'Au"Ȍ+tѰs}nuͨ,2m5'tFFc9J{]sL M3K+0ܓ~l4Sؼ * .Wl +,72ގjc Fm\dKeu}K-oHN)=.V)oW;{L{OKj+KM+UEJP{BHO,Z+Ì9O Ã| I:!4y&~{9 tVgiG򩵬Զ,"~?SJY\AՖESVT"FˑgR2P{b>? IWawV_!iRS1ЧІg"NТ :ݝLvNqþ~`PϏ݂`C4:c{fJ0>)72 Kk&?v.SG8vuU(Vf/6?w'5ZEr-5~wTAJ[2 kOCGר _A%`3V% vyF)`Hd҅?ƺۄ?+|b:0jhQBDdhƲܗzC;W^{5z(؅ IVQ ߵǫnwA}Rm6r NK1Txq0"@dQ.C%#-=  VDW;VoOqqt=5B{ˁ`hvr|#<֔NJ]_0 C:&>mȔDW- ' RQ(9s1)tȠ\ x`R o/#OeI*Y8]=PEݬq%c*h *o2>NYd?*l9 j $;]SEw4tG'^]5 ^su]k"J=D{e&p8? ӟP@l2'ĺhI91-3ꢔ_$b  &k"Qw\&ͭ|ZZ[zq+ >a*MfSX0یR'`.#p'$ jʉBG1۞?5cM-K:!D "x! qtH]7I=yiƧg̕V==eӂ,D؜K2}0qYP֞2C;?՗PCG'c腙mtGk⥍ iH=Z6AjbEVaOd8ߤ)ɑ~IxoKxS^zn(3﷏P\{ a_a9S&YA追o{!^zH@}8<|8rl!)bTMA<&: 1-״Q, zI{_tsu4tNJ_r r~{N?;b3t+>S~<'iD("b7PݠC=ΰ'gS6p`8*GDF5^>[¶ܴEӑxA ~3+j+aaFh˫c+!:, ՘~B "RZGC9 sVEIra@E %a15oz{RL|:#qOɏJPgmx1/p6̶Z <) x|/C`E0Ԉ!4N=0&=,a9^ֲ.$,\ &[.R[ӊoDK6j֣mǻB1LYY '>K*!~t18ab'ٸkԁ BvP,-mf5} 3g{D=# )gnba8v&Ed.]=Y9Snp%0lu5f ֌RzljZX݄TVD554wŮ|);J\դeďf& 9o-Xd~zxkDr|D]_^O2!Zخ+)]o3Nѵfe5XVf=+Y[%H;ld}vʞ"dc c0 /VuI1Oy~FSAjr9dLa(벂~ϩrȑ}EŤKT K<@Na*^CP0qJ ?9dP"\k4k'0})vgq nśBJ f^hb@/Y0Tvi2=/j}atN]W4ž"#n;2/Č2$c+,qZ4ۛ:wɦѶQ ${ܢi7Kfͬ¡'-84\Vt4/!2ˌ<ЎvPcs;PRc4Vmk+r*shC?#!0 d\g!~$$VEcOQ4e.ހC@6eb(Sí]ӹ\4܍*>FDZ1.8uK'$&8gR=EedJtt9yw"M՞rg_?v|}q)tFeb̮rͱ5FDj@R0׶?`"TLmSO?'Һ E|SrJ)^FjC#)Bq 5pA6 JH.Tqu;f\m%ylZ^$u7qr7jVXw9N0\k93˿^X2)B4;FVQSHSZV^UX-$;>>H}#EXivWnEub ЛjȖJon"E)0 qbߙX_`)Fɱ5 GP:|6J0S_3.7e<,J56{kv 06w.r(BMDi׷ dbHX@H(7 N﹠U콝yp$ћV8ϗg?M8C7o7[^Cك8Bk+~Qsp' )'i@a1w!CSHm- @D@cQ'&kA/2 W> #$mx^CzF?X!c0ch/UniBe4( s_dդ|j-"t(7.yL=?gu1 /h WY8J"Q+W)(#;PZ Ch jዤHOwWD;z %N*1vl-aγ.Wj|;eNDL !-  |0TøvOU5bpZ'Jh$bZu:f@R it(0_B0ʢWmFű6-H,væ$QZhK.L-X=7 K/` #+S-{='> 4Ȑ˥NWs JWĭ'bٜЈ(E U~ɾ:8te2C[b4>8L@[{2?(%J%e՘o@óltNXq[jh[}Vy:(T)%u[gAzwTt&ም~'6K]JMQ6? tg牡)77?W4Nn;k~CIٗ1EnXU({ENϤܚ}frŴ溄Lm}\8ռԱUWb 5 BLp#0]Zg}wXgnA5WfrN rGgpJ,,1٬XɫPW]P,KЭ4 a¨.=SDn젷"77A3K SLvp(;(0XI08̎}E"?X=e#v!!s-1D" -E5%۾/{iU9[U= !3qCzw[)֝% |[,N /MLBF?Kuax1XE mȮ}q X!Y3rAjS,J =Z -4OV  dBD)Q>êhygo2OhQՎ[F*uiyAI'Yh!9^z̙ Ӡ%S^+T ="w0l ,#љH~RoLg KU>0SiXR=qi.&>Qm$L~w 09@e᷃q, ggUZY2*g90S<ٓ?䊘a!|mLP `D>Frr<<*L| UYtOm"5cdG7,'Y 8%)4ߺW@Y."Kx'W5S"{tH5d@\? ɾmu0nݯavaŭ8E}p ɋD`ark! 5d!<d1QiNs[jw>a pU;u퉭^a$BԔ `Y_;j r,2Fa:Sp̓Ā3{cw7}-%zҕzfw25%*~&Fs?a+UM֞qnW1ljWxWش<ќC WapJ9zO :l0%Ժ=,$%LG? χ`FʡU%qi@۱9fc6=Q3uhlDiB~YBQ]lt((,I".#MȖyx**A )Äm'k;ɸV*ػM2DI,`S ROJ{wTy=5w5n*jGy b's>Ut9/C16G<ăN,&MFl{z,g ܃zWb;1j<9To D coGE% -SBb*4Mz{7R}]:hBa^fE{%d)Zh~E(Nvyy,@Æ𵍚'#c{0h%/K>jL X7.S2U -# 7Rt4ƭ`4E"uqs;dQwO:@ #qE`atxE5:jzݸyi \i6EOP;ApES Ţsv eP)ullhltF]+ek%}f\mPZ>G3#NU:xdT~U24Y[ A _ \TCSviF?66Tqcxܦ跈<&M ,ob%m(cO*'q饾2"3 a\4_W`sݠ&BVRԵfC"+Glkg=_B5j( ΍ai+\ KaAQ@V`Ц|S9{9ҳ>Ks%BHDM>L{Dzv3a H2i^ id;[=ӻH+hn C{mXϲkIͽG"WWmWmT/H^Z*o3 Be;n 0U@ $610 ;Ԑ)hX96b/5X NEbL \ ޱP2Z@M;9&bBōx1kDY#JXI1{"kK7#CM\(Vk{NQ!nۢሪt ԂKpǶnyFգhdUx2 DT(󱼋Yh99C2@G}D D֣v1$lFζ$ 3X֌du>.0QBR#)=]GT, ;]t y:8soA?&3 ^٦i4u)/؜=Úp7 hL'ILg|lRCMg)ޖ|C]겖*b MIHx|Gҡ/mmiFrDӠ%$+V:Ho$6klVGh-ڎҺ ^ttM,!ߦf i7_.2!hNXGxZ2r5f@(é(e-8Ea|[ "T"_YNQv2u)hNW&+U5I ,M$X\: v,pVU*)"JIi1Fpj'Ƣ 8\JG6K vzGMh318蠞?F|Թvy12GS8Oh?RiÎm+HjxqFiYZG yyH8ה3e:8 _?Ъ'X:0( _|dQ+sa~t]Fb:ݳ":cA:AVO̿tSj6byK^1` X ޓXz3,?ա"]%I@g>@yoi )&ՂZʛEf( ra@|UJ4uqH9Lz+/QoijVAZpC9Z-ۭѮ@3+$KQXadZc'ZyJi3oa6b7G!HQ Ԃ pHm;֖+?lu+?\.q8mkddx:k}FobxtDdAxeYʹE(JRB379~ {+3,-k@&m9/a-s;S%-2kU/h-ޤ!E$UZֶ3ZxPH4ؕvӠwkBPUDNsR"32̦q%ʋM>>n4gt#jQN/aѵvtb(#$)gVu;D&7~ X Afbo8f@[ygxCV2\ Vljn%xojk.#2|֙"X Uj v~,qu}l{nJu~% )c {RE'? !U0(+)(Eړ ,i@!Cv-D}m 6j ss\*jMΜ7R:Eq8+M'2k̔i/UR8m~vVlcf+wjLudhrd/,n28Pk?Hm+󘾕1QZSwHqopx5\D4\}hg7AK=EZhY*x@X@%Y(d:Dtv`H UN5p*[GdʴR[7U;bRCoTtd=㧊LKjgs %+_'JHuNz`VyQ38j$PĮSj> jY [jG~MD%-l{ӛ7$ZFK 2uMLI-8ךHb%WO GI=a(_2 oχ3DA!U%'$e#Ԇ.ܐʅ}@&K4=4E}|ɢm#IJxB9Cy]8^,J szWU/ HW J@⭾'9)1uw5 39xoϺP*Hvj>\Biccm Rʊxe(L]gtW6ifS ~.㎦+ .U $w=P=zk>h4KL٢t "s* ^Gr4"r/ݛ' Aϯhl+*el]ENLEL4.G|o]OC-,iAi=-RlΥgBJq0*r (nJu5+k5޹ @\Afd6`<ɹֳeۉYQb-YxCJd_{Ee-r~`ov3?agKŃr+)#hJ/ j`vǦiN'5ET7e1X reBo`__s@F9qUrh\=Z$JpO R6ME~X3ؑv=ʘJq\y5E7`z8/?Ϩ=ϖtKV+OlNC׆>r%tߛQdNX -FH@xӎM\_#)\8,+X;ڰ b5rqg6*ˇ1JGGo$;?cآq;) ;D_}j/U׆Tqh׽y;\4{\'ϧ \t!j5TnW^.`+$(cN/NuWdMheKǛ&Msg/) 5SKi@2e|( Mz0l OC%*t MTxȇⶾ.S%'d֛ ,yQ tDD9KN$(gkG$MYٳ  (5Q3%n74Y(R^dE>ox J]?g %D "fɇ<>koA% 7̥ V[$/k<EQr *J109 Ñf2h [n{8r,C= vP&uLz[/_" ,ɶ42`캩ԩ pfS U^AlyH\n:MxbۺBH9)\Pfmg @DR I-$kK||Ÿdu͜ܓ ]}al+&:j@R~O.|O(Ȏx 7'h$eZoSdfV.d;3!&x5#宱rB3JCqyFrxE]ĭfKaE=3S'P# ++MD+fOZ̓ "VTkrݮMIuŜ=1H Ѡ>h{rkAR"+'3l`EE5 2OaAþ2[yDwxEZ O8c(DY`ɜsٞMƿU X9Y*Q*fTGɄ1@zIOe#2oM"_[\I>chO_iޓ&XA^iM '羹[MB) )r$6ǝgM悹E٬U '=cX NB;P 'ۮ o`| Xy&!a  <Mix2q3o%c"eBZG:&ϙ ̶#:ƕ7fktR'C̠Ot?w@=K%n 怩gqRWq2* ^_pqѕH@y M5ALM*=ɨΊw!VJV`c%X{x-=ul[ĕQտb^,Ԉ/%!AWR^&K]{͞)ʐ*%!NmRw$b͟<:,X?RѫkmċlSRon[pqZ@[nɣPas)ctψjl@Ȋ>COma@oF/"r7י 7x+ϔR7b; ՆJ@Q ueI/$+Ypx^,gnۥӍ:a>ʊ]meiu|B4l>2Gi`s[6 $$n( T-aðl={E%G<TruڎV,5bB%b8oc֍쨥Nh:̰@'Fe5'ʕJ5xAhYoCN=lW)a hÒW:Ѵ1)} BB,)$;؞,t)i`R^6-ƙ8QB`uy_Knk/bX+;ʮVó4|.-$'{[[F8}#RJX1朔ҵHMI= 9kM[ڪX+_--1)yoaD{o'6=f .>ڤ5o]QY+go%TCyՑFN07 1pj[0rG/J(EVR4c*SٲN:^g3n~Rwަȕ8%>wQؑV/7 NE)GZ ne?L )4oȏvV\8bW*罼o a6;G'uKOxN51 _ mo*U ܱwur>r(o液O'w!)J܏Z szOܢD!H?)C }bu`߯]҅=c(k*oK/C,N;g{&dHּcLmżaxcn ױyZ<ǝs\(dwudm ϯC~[x01dC0r%K h{ǰXHqᎹ|_&׸x‘~y8Qc\yf. ĕr!6D9軥e%z(i[d{ت)L"8#k#Y/2Cn3?kcXq;V ioԛ{ȍ;/YtOzm1)V2">.qoG홎*eW1<AL5FRZ5/{ra5JN=W1 h9vxx D#סf7$'n:"YtA0V/*Y/=?hTݫ7w$C^żb֨{~ĨB2L%s8mBS0O& x{ FM.|_00*ku8LzŞdk+u}GC^QGc>{t{DG070,YT P1;5y/}Fw'C^w;k7WwMDн<*eF&ϰb 24 m5΢h)l-bNQbw2a^':hvTU 1N\Z{*Rv@Cf;0 ?"1TÉh!wl+R9; 񋈝 J<wKaR~NI*Xv'H ]l CLa b#Q_IC{>'E+j,>gjQ LƱ#7?D{?]3,2u^y#21\dԣYjPSe@  D5OBr^oZb]$иHJ͉z`V0Pu\U1(Yk(O cp:cdwRGJrh\fY0%D¼dհ)0.ߓAǪz_M;ZIoP\J k_ǩ" spB^_9*Uh=4WpT`f)OYYhBhsIJ::dxk L _R:si;&T|[b)9r:l!o-r㍳O~qZ+s57(pN7=!@3gbJ̋˯w+ l"SK<,Uwޥ!OecYirll;|(Z隺R:#8&h8<UY-.ro%U|(0jT*RT-@Zr2B^Mn"reT;&BHM{ 6 V1˜V䰿u`ǕKL>L)}rcS*OQ֕_I{6=k,ZUuB\叼7o#kYn! 1օĝlPIN1NlFIL4T:ͳw|EE7ӔiS!3Ov$5LkUfb bg4"dB[VPg77ʭKeP2doGrY(zWyy*%!2 Yh/#Eove*=Ʃ3bmɉ'$l ֔;ڒ/C _\K/C#-:5(I8h{ "Ns12ZU\SPfQkTZ S8STA j`52Z>n(<.3r Ne*K}I{ ; ON8."c<#z8.A(NaNn-bԺB gv;oJ}ZFЂoT'݈ É<ZL;i !q°닣{l):W!~ٵbpHKvN!OߐPMn%0)Nݱw+x?kt֧~*1FHwEʜ,op >(E4[ K2զփNBDU%\NS8F+˱0 }x}k:{=A?EsWt0.g09k;ʘO}>qs=F{yv+l6r=qQV'je=a8^bqYuϨן0ob?{iTO|{0. s<*Є*e:9ɒ*XNu:c8@͹UrvqqCpXtmИ ?}}<W_^G+??bݫ>??}z~ϻ5^1μg~GCB_{ŧO އHB'sbdw˶rLK3q5qf˘cH(ýa=TJl1.זX_ذ.=/Qi JIt7G`x;ÿ?ÿGP>,}Q/@orx}C>gY҅{}}7C|p@[5 Qy$*N)-d(OGk \EB4ɔ|L07"Dj^be Hj8|qOV\Ӵ v۽`zM͢7҇h_"]@WJZ!Nj^·%{_G.//s7WDc!˪9ڸDo=١yRldR NʥBQf,$'}67i|0#\LY hU.ȺIQb@%g+y{Յk\Ͱ~䝛cw_1Nʡ;$_b?:D k/Yr//` v1īY {nt# 52jԾ>54. "֧ P:ڈV*M4XB,<O(:Ì&)OrA4|p i~?+$bחɼW/1|  Y 6G #*m~ti>,ak×H~ )3e\ hH;^# gjʂ4ͺ+  .G$r??Gf;tiG~ƗS;t#cEa>zïxs i U NʅցcN&)嬴D|*2VI(7hB(;n޶@>Y3ܴG_?%om|m}wʗga Fiחz/eYv},!y cMiuۄ`oW`u<VUh9γ-QCC:FFBq _j!?;l t;.9FڽJ0󬽅A:v⤙j2J$$ gU`q9 bՔҥd0'x4 G!Nt QjU1| \/_~~[?TO6{#M<^**Pmm9qLY/~f=wvnTG]sN$k5(_K_{+E08&Fs|IKmo4-nV\^51ǸE\9D)ɥ7~ T P ))DxeeD14>"oԒ{iöhq$\x2,e|6MR SCRJc-({a.4'8k:l 'l"zG `um-m͌MxO$/=;"hڸ@ĭMx//[IJFɐ VW~G11-An660Fqf~g~YISւ1E@<(B~[΄JɻϷ1_&f6'-$@GȆդb&HKjE4NX{ZBZjۜ˶iTDPAϪӉ 0N*UUeȴњ} h`f`R[[bh P#(jϲBL-qD81QUJ*Lm=ʒr26c2)6NX(BQRC?2v{ ߽jSH_q./^8s>{߾}}:<&B{spg;&.{и燳"à )8rQe2Be0?yDD@yatC<\(y(?*.𜞟NAidX̜bOLwQzh_4+k~,}_~bݟc .>~G.R|+_v~\{|^]\pͶe^8M9Q,][{5/O^|G ^D5 խ݅*-y. j![-)ɲSCƛRNI$ͦX rbQƋT=")dHEgZVϒ3ɒvy^WE=tko}H 4} K _ӏkbO;r;cn~A(h,fY@3O G^'BOڨ>$v4ٔj ¬rV~xk0g3B%Dx7q;RW1|f8Vb쇽+>z`Y~ṛ/hmc'͝1BPHf?'B(O.eT/G2FyJm Rhp\fe= ySkiؑBDŮBNS :,>i2T/1g- ~ _ . I/8%F}F4jcqhad Oڶ&9r?Ú|Ve~J¶U-NM[>uaSq'gXVjPiWlSΕZt2LqҀ&bRdo*KY[d@>엥_)K},>`F{# +NO goo **qm1K<6+Y,p©W8-jZBL?4K'yÏ_'Fc6DIWaOٿF"Ϝn!jJ|+Ȣ0ѷ vP眨Y 1\0R5ŨyNS1U)^YfBH1i7D5!e* q^N挗ة7f#ٔcRmg ѝzr鵬@ B֣tva^l@%D61n'za{1w1_8kl_z>|9!k>#ֿx~LO]alUHz]B-RVF-Yܦ;c;ԤVЧq&ڜ2[3~-4m}vI?݋}~A ߟ92v=;4EԺҷJj,L9'#cMr.z#!(!,ǯ~|L w[ڥ/ |^]]2sg&>o]yxx:_H8_Z\W[Y>a" lGy8\>//L1RSxV;lxKʽgpf:*}2 j7X#0rVV}90,U$g╓ Rm2%v ZmoTnuFt0qs&_ Qf6= vo`Un}Rzw~ vCz1 }?^S9`0s? `?? w;_wB=IENDB`camping-2.3/extras/images/little-wheels.png000066400000000000000000000420101427044526700210470ustar00rootroot00000000000000PNG  IHDR3ͨgAMA7tEXtSoftwareAdobe ImageReadyqe<PLTE  fffTTTzzz:::CCC̲T Z^vvZZŘXhӲɦ" _ZXs[qq%%VVvu=1xy|ȳ*EH22󟟟l%%0 ?@w^^^p52;aaﻻSttkyc޺xzhg~BE7b9:+1tttƵW)))ӷ2KI||Ӫ|WWmmmLLLX@P/*5>"ð111u|BBIDATx읋ƑZHBOĶ78رv=gזWrv&r<dsjnI $0B)K,K) M5VEn  'ƅțoWb?}*{Ǟ'*9(W #ne tjדTcȅ=kA5= <;e_TJ%RN,Kтg,KzS-_(K~g¿1l?38e<.).VvV!yb0)>,&qǘ@K8}:Urpb9W9dJU;Oy|AIMgJv:Nf.yR%O,nſ=iy+ g׼4p(9ڠ乏<Q\ſjwlFr9Q\!qO(ebƙW)63 e&ĄXnsMB,/ZyrV,VE踊1Ǣ?Nu`I[A^(WeaA?gze"4E20بm; .t83 Ï Rd<^eVKbPyP,nqywQ}Sz5ʧ~aUktp ܸ:Σ6*t ܐ:+#pȅY$E.]e]9$ 9Z$hT Ozuz]UU_]'I1 Ϧrol9oWl֫5XsQ8dS MU]6 YӬj"U^g^ #Y~M8i<'|18)MX{1x :svh!C~5)F C²}|HV.W*=$6C?? ;GRKQ5<8Q6M"nƼ>_ۡE4E=!Jc QDa*{*o[l(OfvqО 6 N$ƚع ~^S/MgU?Gv*Wئ \edu66EYJ+ItůgĿRՔɴ|?\9LEGL%3,$-9Q%5犢>qtk4j)u]{HrGɖq9+npW7Ȉ=sF'bny\L|RۓϞ}k5ƦyyMߞȽ+pq*Aכw~h+XU=$j&}|Mt$bu9ΣANM#\|D80ՁQ㒪ʩ¢S+6YtĞ$B7wLB96[M椫 e9ej,IStBy|u8l iRrZom)C)у=-;0j>&<*5 ?d tb{;@7@OQc(Z[xh~h'~f#uRCIwǚ=yqIa?siA2z{Jzh b}7Tgt$LLCOQO2n#Oґ"4ҼbM|Wfoh074:S-ܩR1DKT+ҟu#/d2> p$*MagǐlyczrX3[<洏J[x׏f`AJzV=l PV^N๷h b B1Qݟi\# VJ + ϓ·!,[İdm8t:Y 67dԁ00@TeGL.M&Fm pyU܆bG0|qtLvRp ţ3ַ67/,B,+M+RWDu\([>!z/{ d۾&Ɋǧ ~; R#VU_}pJ"-H  VUW!'qNVKrekPc}- yӁi_>Odg{KW<=V=fHIHAͯs4n-ˏqjV2U @ded/SicOFE^X$U$@\gp12>_h&@q|\}qȞI gf+ԑb.s@N"L5`?=41Q ǃ jܷ"]aKJÄk-C-P<']${5Ap)ꓜCplUcx[WCN@hO05}.h&E֎#69P9PTppq]{CIhh@ lW͙lΐ'b~{ L"4\KF6rሱl @^yh:Lf'z.}ui^КRZZ4VG! ß$h FSzدDO<hi. :d}A~N)91܊wF ͓ ph-Lƍ=~c}HMp8 CaS\yRPe^r@˞fo ޛdGAzh0؈?)y44#2Gy+5IbU8?Y .ox+ qsKȁ@:Q̷(hom+=zS%vD˚*o=iw'2d,TAnѼ^Mn8>Df)="iEKJ9xj8Pjb~11[wzVow< z13,GvC~*zz@Xnh{ߒq.6P.~E݊uit5'E"^[+1b߹ zhO r? <35t` ^mk2_ y1b"\ .Kk8u^f.W :äVhpRΙ+ X >Z|ѿ5tAoWN;#$f}DKPA%$z/UQZ 3݅:k=#5iV C?dJ>p3ʿY'0X d/<=åy~ʹ{9T1gY%%yd+gcOΣn>6t;&vʒT+ᒅoWQo׺-ftK$"dg sIڠ]eAT`2G&_nGݎY$˚sTnEL(VF3z6xm{[G; ͿT-!lek:!!"yb)SGs;Mfr1 q[*٣[2ܛ7/'Yz(9\V&m:,MoHVo3xEaSВ'58 8j9udLK;6EQlqT_2g:7F/2 r(b̔VU>SI2pfv+`DHb j$O|q?/Wo~* jHu;p8ǽaxH@:zӷnHn<:m>OV}WVagЫz{xD1ۋk5-rgԌ$tJhOj(sUO荐1,1}S43V!dhGhheQ ƐTň'ϧnO>'0)^/I=x~xIc09ya7&7ߔWZ^nǚP FCh ^mFI^Ukڸ֞CٜESh>w|K¤1yJ[h&xt-Ts_m?v $9z( ?> \=E==G@? !WGݶ+!#BGDA7#|s}pA,eS>}_=*_=Ec"ԕSrI{? ҴB^I )0 ye()eQ\TJ%RJs%q~u<8Ldn g7Ώg"KO8 5<_zsjM(S~2bnm$3 GRG{;NIQv>ӓ,MAq >D69~@>y䲬ָ e0NV8ȹ9͍>=/zm\PmH4WRQoma*^>,V\kF8إ?w6a{5hEUQrdI`v睦]QD"^97)jYmȑHt*b畚քKX%[s 3NJҚw$/}YfH?ucO@חє͙Ne6mq=GIAq)x:/p6 qS{<ę[6Ldx^'i=g@W0AlͿz=0,iOunT;(3orR;3\2$h^2(YB;ykxTnݟ%N3or} u5C;:*|;W_EmeWO>K;vTFAt85Ҹ-QJD){0v~ϬDάV2`Ɵbk¿hӣV7( [idۋ'B"k)޼I]ϱ-8Ⳙtdװ~ Ϧ7l[.U Ϙ'rbi7C2cfB3liے6Gl9g~  'ixS=- GyjAD^6M}]X5GxR^S;N4ۋ\&.7E3$yf@j`dRYx}<7|_uw30u6~},s:21=A"!.VyDz5|bdeKv5}#鱸Q$w%u~ E@+<7=B8Pmsj_ț?e:zU'Ro++ ynxT!$7RxWw_2nFAa# \;>uCs o3T鈖~pZ0NiHSz ]Ϝ\O r_64@8/b`(p"*$ka̫AG z .'SV+2( =^?'J=F~@i]5@ȰlP\v6 lt~ )TM FDжuIrlIcM׌eE4Kቔt [3qZ8td0?~'SAObȓ p=j.Wzs|V\ ϕ~6G(>S\͑`/i|GR`8_y8F0Ra0VKn3 xy\l<.ƭ{j :-(lltô-2 9QyZٵ1TO`s \1els$2)pmb GY,ڶ*H=4;4$>Hh`2b!ږk)1ڤ|. ;=&Hi+P[ez vLh/MbPɷ4t2qO0ը$o`8AEyc^F.z1!:Y{~J7"D fOmu剔Cv >| jXz@OT6g3jSÈgk+0u`"c |ZJɯ_O/y*w%!gi֘}INZoەd@% oryn]Utw2o$'k^AQ 'zē{O,*--/W "͘[U]82Lh|XDדie*Q~_&pvY+oTd(M $9MweJyi d6^ *y~U ^xbYVȨj  A򤱐z5 S ۢ)%EŸFȽeG`8e_'nu{XOpƄ *d!&;e(1<7<0bY&Ca(H3A[&ሾ+TEԷ/v$ ^%C!58{k. ?jzL9F(l˽F ZkGf?#biDxO96),ˆU\MJaT_e/XpOD?e r vUo>3 O*y&yvW,"qBL/çx_,"/lPq !>(fvݾ~v|/w-k:v2'JñOӑ8jy*P$#1Nin~XryɥxD4)q<'q(I![8}C!DYƨLIܲxi3a*tQTYuHfa!*wN<eGGC2[W ƢA'IeJ"Qw 8TYrx)#LŴ2Ra5(y_ϯ3ѯ9Ԫpt"FaX;e$ ^6ONqh`Jh.x//H]#"<IBE4ҳ+B XoOZ>=Ϥa~x(Fϝ>CGK2@MEeϹ#uѡ ?oMZ,R_fL An9t޶Otp~ hē?)\:fҥ: [>eh4> Mؔ=$zFjLtYfU?q;!XiZJޖ *yfk@+n"dTE7zWwTMT>ծ`s.m7z\炥hd~ΜsyFhk/|1f/Sr̃?on]-$pcp&x9 Xk'O'j5f'ָg"(y ZPj`9r:$;{Rζ{v81[dyiM}8Ŧ@>AQts"Է?2+3Q)z島eЉJm;Zdzc쀫&6_3<.3>3cНJ};,]bt0I.{Z$}++jӜo?f;[N3k$9tOi +I0 Z\(O\7ͪoU֚ͩ+)OB_3F[m q0MG1qg=fVs,H /'Als$Ɋt|`üxP't:C`Μ;F(|XsL WN)Sv:Qi` 9|B҆?!a_{{q{kez!JcmwLJLöx뾨^S)=h,uga}'Q.aq ?,ꀫ"Q].kSṗyAT*"3 _Q6ea!H9ZszA0Mk%ɍ9=iZzտ1ÃMKbl#J>K}R <%%mC)gdHN[{4Kxճ{2ӻ7*L`&.O^!g*> EwqL͉\xgܼD 'DXժ%OL$7,P Um/>oo+Z_/xs oJY% yކ?xA :Bc1Q"AD͇B$68}n??1o ~0o[A?7?|}jKa?Ғ-::7kZ~#|8 y~Mfě؅$>y/>mc?0G>jw!+~_z{>b8pޗ:}lK;CH??cIm/ˎ\l~ђ;l;NS,6Xn-$yA-=qL5QfNIcqG]Uנ6Uq[O:f>ٴjcF\P~)2)YJɳg)%ϒg)%ύьEjގ:(myİJ ]Q8VS97$(;S\njnxI;ၢzAf)+Sif͗7pY nS=yWOo{O}xa;O9^ܐ?$Eq߹y"̓ɾ fΏ)۹Rq4˼ڹfp8R6Ff6Tm{C<[lyh/fvEۉgax2x<=ŧ4'̬~CyETܗgBf8,v8`िɮ&ZFňr>Os(i6VPo)'kȻMExz%P7@N'S$" Vk83UdExf"-ɣm̆Jͮ_4N;4sޡ"<[~5yn1t…f{G9ճLRU2'ss ɬ4zxyw''x5('8KNVQ&x+,Ѣs/smnm?s weyk. 7UR6+ gwc3Ij# 2_۠\ X:u8|ç*B6lI^_)Ԗ7-g/O3c3SYs百ڊ\f6l)sᙸ#]og!x22*s百 ìx.OaK$`Qi4(?*:+ˠgU3&uL 4a <7|uKJ_g1H= gf||`yn`D/hiʙxƧ߲0?#axj5㽂ЮDz5-ȧ:U~ڎ&l([I=VR ܨDي *97*gKҙ픻hU2~kqRyns-yeXYO\WX=Vx!@W/{Zٖw-^1z\X>1eSm]_-ή9 ֆsx&D7T̈ DHLDDfcSBBRpDDTԣYMRZ)5 NㄤUWd DK3~RU?>fvՌv] D5|Zk>U2%|"Oc*J)ia5ZK)tADe1ujf]ʹ6)M̥x:^?8sRbvTOںx1niM1e1tꇮ֒Rrs9fVhjz}~93SJ`*Zmj, "\s2s1s>$]S,<&DK4M3G?,֘h9xce\r?&iZu)s2# 3RJ Tr^VjaLQ]^^^v=^)uϭ>Ǟ"TLJnn^<xxϞ>{sBD~ysGȈyV2/TJ !,BUU%&0PU@ b&Ub\}k9D&B"3er*MZڹp8:k!,>Yy9z8PKafRVuUUE5#Zj΋ 3 КA9'"sN>8v HMA ɀ[+ΑhCdmy155v\%jOff9罈DZkg>>XUb)uW__}wkK~`GYdL`u/樈s{˙*CRejʎ漯%j!&S@^2HQDT_ Wf&sY f,{"މ9VD"j1կ7~W_DTɟ'tUU2QU2@MQT$DT͇@/"Dx̜cDj1GB1R٠{Zk"9FBԱcEU !FJ.Uj;;쭵3^\ 9`d0#FfBDk)8N;__߳mwfeJ}?g7gΣJ򗢡#3zo~?/\)hcC]odmD<$s=7BV=fq__3DZV>D LV3%0T4k-"62jnOe2- ˸,b٪" *6Gng4d.s;H1 j}|ٳ+p'?wﵩ 3 CSmFj񗞌ejsQnUJ .Л:Gy}M+W*<~y]nk*[]\}ӆ18/]|ՒbhKYdJtQHР톋^3i*}jeO ȂiݭLE82{ntT+9eM/Yϧ?l(|lǏǽO"=M .1S7 dYҜz-UDkS $7 Ly{~L_PARb\?Ç4SUgavT5i3g~:8}40Go=y.az*,]<9Va=Dt8(Dj780~p2k +\]-xcVpY@pqOxwDxdXtScr~9Kz'YaJRp t== ]]mS846!`]>nn8w6ó>><=߉ԛO|zC?ôFmg_ʗw<=wFΛԚcpw`hwz8o0wTDa94@|+R1*_W{ֵ]G?Ho=MK 8zC]tJ裣. <:Vu>hMǻ6λPk}z+ R7Knus|8b2-.xh?Iq3i"0?n6_·epzGu\V[e Y^|\'m鋗?C>ۏ!w!nCriy~wXxz=ƇϾ|28=(2:ps:MT؅XTn޼e>5hx{:݌rR4r'/-# *Xڥ4^.❗_ܕiy*&u@!Oq^輏{F{٫U$V'l; Ϲ?ؙ7SV*Y"ܴa:3]Jm80n,xwnm9Ou8,MDSƻS=f(0N`~aGS=FPQve.|\Nr<餭yr,gЙS{͙oS==힚>Om.{d:-~|qU۹ugj, O'ǢYۢDZ(Ә[6)Ps[.2ԩa%Y% b,2 0֥JsV4NIQ3BqʇlYƻͲ2801׹-HZjb"N \ER"]__}/O"}":aO[NTM $`Va֥_Swva-3YZ.uDբe<i8 }#j$ \O3qTЯK.㲄[= JԤ x%/S SˍrV/Ux ZJ; +.F•2D$![us(5{_΋*2,uؤvC8x׻ 5U+a{bUZJn{ڙin ˂V 7seܟuS~08pqswK*uz${,N!BEd Uk/RJ~\ POl@M96:z΅Nk1IDpEI~0x5L[z|S-A6Mu8KU~]xqzѓeEqnz=2.iKȌo֯kBթeRѯ(:*pX/rZJC -xE6,qn֣ 5JVä WO)w}jC_q^?If6yEU6C |pZc7tcu_ŞS.ī5Oecxw,B>DEh^s>88tr.[E,>[L:(5{w?/I3«|f6?/)qICKĈA֭{0y6n J@v+!.duV":6Ko,>ZLjɓ+LiKUhC>! O$]2(Kʲt,Ǵ}tE&R쯮(6`,j5yм.RA溈Vb 5npW0yv[7 Pʉ??\ ۂ V2Ax0wc #i[ ۋՓb.v֢۫Jq>ZQߚf(B玧E]\q>nzWc?nVntD%Tߍǣu\ֱ޽#}t25AK!`QeYR~\0J=֬7vXfsKJ} A7Dn/Cŕ ֏j~}v>_/CPwh֫UK4'fBrH,EO R ']@ݔ~^+0x󊃍1hU\y5D\V?܄RveӘvu6mK )}!xJU0z}x̫>/c^>z2OaગSs/U7g;,]>y~gCwNu _#!C{o6abKqw )x/^3V)2Pwi]qZ^;j79T\4q\JMq3>l_qp"RNS׭jQ0vUBKpwFvm8IեR_Dg#ry. Dduw/} 'oGᄐVn>|{w`|s"SZ&ڬ۫Uڮ(y}e1A;:)֦y>7]R jlп<og~]RN7f TNł\זy&7}iy,Knд)@?~7~ >;{5۝SPGZcQ8Ӻ{x7[7“ 1}V`eޤxѹQo&n_&Rr딠1K4:Gj!h9·= R/fñ3 sbhZAa^%3.RJmCSYqNkEmڹ4M V7t,KCp9kpC`ZSu\]҉n^Q,oY'e^ڰ1r8+Rג88>]n`:q^9z~y.4Jv+YEj(͛k!s_-d4mye^~p#>mmb*":w}+ _s?iK -|7/o #Bi+ E~I[7NxiA+vw7 Ap᪷Ӽ߭}MX\ (vP\&K"OsU-s%/)agIȴTp)~|"d)xy)TVA"D&5jHqEeߪ.Kryѻi5P[Ay1F|Ws>ֶlCx86wG69B?%;rutz܅t)$ly2eA-fU `jRQ"·}|W&b By} .e^4_/,EX@ 2nKJH $S IDAT&qr3"Fʥ*#V1-{~/mL~lḨM_I͖y! {}yCHsvnۥ/{/VPFFSfOu::Ҝ-UdXo)!>2-o.VhY Ze ZAE4iͤl~G,cQh"|iY3jCgكw,ma`ad]Jᾯ:GT1#\QE}028b'b59\_mٯ(K{uX S\u[\AMlʨD. }׮.b~u:Xf\9̙ڔKMŁ:WĪ:lVi *!GL ؅k+􃇮5P9Fn1 hrwىGB&G T ` PJw .D|ث4D]_}uTʐb:m [Խ Vn#VRT\Uׯ_E('O J L\?եx]'Xq<0:B6N n@p}~+bمZ.vWp10Jb^S670࢈4-9ê)Ft[rVɡJιO4DDԱ*5BSȎV 4btNRLbށ"[ cC\'sfFðN10dz?z8(0b1UJj,%jv ZZHBѺ 2V8P B)xiԼ+8`#jIYqɠ lm,"U2S/هA \B]_}=?{8޿)UׅO]yØ3ffZCfZ;ڔ.Ld+Ks"ח_}ڻwmy*%3B4q@$:+z0[E.uԻ`&4>51AujTXiR+zQ@-3 8Hj6sjh8d՜k"bBZA(]^[B(ą}isQY "QUgpZMJ"1ZkJ`dqCWH,%jX$9DUI=G XD 10w 7HGm!γALɡU?mw>}z֏>כmMh@ U Z8IDV$ў>t֩R ̦6!je^o3 dM- *9SXa"zj89Ri%ԻA||) ȦKT} iCxGq:̑])[}|H-bF̢1כۗ/nn[~t'=%Ua;S`<7u)mpL]7/9DfU3dg6bZD#w"UsMp.a T"MqX$zdr4&T&SCe^@#`e{21X*Պ4XJ-=xg9g5SfH@TU# QBTР:B-%k fy.I{c4PUJֵͥd1E#[J VPZ[ Pm9)9yq3A"S3@Nsۺ VQ,'G_g!YԁX꼉BqlfВo !ib͂"b&"peL?t|Տ00 &C)lժg@fRD۲";o_?yBM"PudꝧTkEv1Qk-DrL҅ B{Ykkyk&BdPB`]ul1YV1UPf*֒ qT]Gh& :ɇ!  )8ua.KnE =6BHs]H {^IAa5/ 90sC EiC$Qe914|/ONbr.8 AQ<{ fT-W#Zfڮ~UiuV"zآ.o]֑#3R빒9ȯJi̎iYG')g$4`!FbN)!&HU{yZݛw(rMTCd&P3aK>)+Msk|HqUU"65 @Ťy,ad˒'S*(\9 2ܒ*N` DZ6 >:ڪ9ժML0b*)b͸L5IUUKEEbKăas@.E R-&c8Rn[Ecd"Jm[vϾrSǪ؝Zm6<9%UKE&Q2(i)v⬭HV8VCzlP!pnTsKi.DU +lZRA`4͑rj\JS] ѷȑ/ {XåbHvnRfͶduV9jБDYtramU$ ۝fNEY3b5H\sػ.N:-L;(w )ҎSZ8ho;x.ퟳ20 mkޝ.UNMlFJA]0ܝj)M?Y[jV(b$2Lp} uvݗx̛ g~zDŽ$ֺ̙fPuEè@1}9!K,Cn+R*FDA-aɦH1 ¸M;9'X9e9Бu)nX1Î1EìGf1q;-ͿћSYR_<+UzcNVS&1~ǿo>~h anOM\ϱ}6TM}7n>9k*PS\)0b[CrZ~=~;OeYRD,FD\Ϗ!Nջ;E?:$Dq=튀fǾ 扖\ O9ș])W2+KNEXP,uGRCJ yK2ڠ \N}7ݶx_$9%dZ'`"|F")y+$MjΜhߺʼnf8" ^ڞpXK]01DI}jFT^vV9O.aLZ(G)Ipty϶ܺʊN1?\ۇx~ٟ^n}I@Cx!:tnOW H) hzJY{ȩXضzk[;z[ a`Ӓ(Nr6zgx]+spnf&Lc6P؞O>řXtYYg7:){kcQۻ53SE bojh@ԏFMg^1'ޏjJzE/e3@x=?~wo TnGT8`]1 H9Xf] ljT0krbb6j-"NỸ0iqHH8_1)&h:e{`缮"BW{9ю(cOV/ ~o=!ʘ})Xp!K-HBȘ:}>SYm=r*3%KuI~Ǜ SqW sw]!@k[ $zyyq1 &>=ח#n|9܌|48p Jr@ǡ9a&XķJթj K)e5$wOW37@gE $f~:uIkP0zFfL>€Y&*IǘJk*@#8r^xT!L։ YLI OJ0|Y=m#z7+Fx\_|s2'_´<圗j5"aA™TM#3I(.BDBAԄ)ɢW|xy|: 6O S_ B:^[MW{6@\SHv. }LD:t>a P)Y]t?9ɺo]٧{0ETzY)j)5*O//Ooy秒o/3K^L2u):[}0c y;ltp br#>2Co=[M}clVy@HHbT$zt#5cNiLNEnWHCoE7+7?[:Rb1ajew?9&qz]^鳕U}w<1R03!`u@0].{?,a&f-V ;L9✓qN}gVP|?/E a`la bDNC 1<>交-R]h<yUU< Ic0&ȜpDݳ/OW_BURmjʆVj"\AX ה!/F=LIDi,nqOxl$5GnpDj$y6HJ0[Ǻ1%-gz~KWOz<{ c½o3*Q/8WďlSa*&ǔT{ O M% b%%7JWGgP&0y`:?sM;;Քw=cNDZpB07 Ӳj sIaZz1\r.x9qKP gp,joU2mN@"v9Hȑ'04ۏ`@rǑ}_cG42X"%-5ÜR}N @!kYEtLE7{dF4d#`pň5u:FTTpG:Gܭ4[HRq ctRLތϿzvxz9p P̀co>+Z s)p@>ztw7X̽ݚ#q$:wXӕ ?q!.9%\JB桀nZ쀀'}D?)9 ZpְOsUR噐a`z~y!R0G cC'#C"hhjHtrv! ׃1n7my4/'N LI k: юC3!3"y!&!WBr yeI{ f\^)M8xE$=LH+z_巷O>t83Qմv%@ݝ~?7_uC{mS똮~T kbqӄ*!܎P j x]- `Ʊ܀#Bݣ8HLq)"hn9RkpBakm4"RNzǔ51{wYnf=1FL$3:xkt 5gN0xEE y*N+eDRkIy}{>e N|8_A.ՕWĉ!Tjzb[SxŐ!38"䜘{ů0SWXs^:NDs"vŶ]Hǜ SfB8)Bra%j6>f7kBn뚀cO>]oG9o9A=_㷿11LF>=3,bB>U"4F`yZݭ~ө $SM7uk_^F;dY1V}+sɔ?$O͉Q۱Hʩ22M`y97}: yk3r,TΉzC 9IzEVR#0D ̩(ITK.Irk3H ̬R ?k-0\.!!1FD`J„)9O޳1R%"Z)Q9RA6t 4pb.40©ājMRĿ_쯟?|/ܽ)m9z~oGU?Sm;P;fB(i?TU1D)P7)uEӈi3Ht%11aB|N,%鳎^cZj0}2VO0~vGxr­ϮEgpk+f!eɽڄguKke/)ԱͣlxߎsYOfOeɜ aYAl;y~{N,ڻ$Op'1ODv';`2,G] )Tv^˂hb# yAuCmz^^yj1m̩co+<>] \D)v%k9ZRWx#sZ"XEhQsv0w=-Ąq48O_󲤔p(N38!;JLc L^jt}؉.SmDrEο-r6>ȗ2&nC9>~$_xo<|u]>sWR q9hݪvhᾮxYۜTБWh3TSJݺ_[.}$N0ѯt9jm=sByjPs|.M̲$cҽ'M? #p]y%K]H8 t#t4L %Lg4zB}mIB;mL_N7Gm+ɏ8TS {3ߙ1g{pt(<R 1tyn<>_n/ހlZ:|}}y dLh6m1Eѧp8.>37'g`(8uW(|/f(ac`xu# SWRZmף=eJ\O?~i;Z PT 0w=>]Avۆ">正0;5)lfj6hj{EPaU DÔCDoRcKݪ%Vǭq]+(KsR$2M?iAOwF 5"fH SUv Hhc%RiL,'ʔdnfLX.D߬,Jx%rD>17&g?}]N{Sfm\ Y YHbf)"R|y_dp)ӡ‘.HE|ZMJ>;DL99Wޓ8ne$A 8f{|<2ԥ>yx!F=a5Ku$Ǿ*2Q.KԼ39((R N2S9\3h^s 5LZ}ѷba"$:`yiۥlKQ@WJ~MmpE<-s!Lrrc̔0iU9G:Nf,ᖘZ!z<_G?,\t:rY }1KD\LJ۷hqeL: RzWJe!өV15Ĕs{XJnw|!tfArn{ZU2$cE@*Șn6; ֵP iIR6g?ӿН";1('p 5RR"b$* sI,|:UIԒSrI)-՗^$Vr!@/uTZ\6Zo* ޲Kw8x>tC`d34@ΙHU֓ [돷O&02IvTK[y:eL/ @Ĺ#g1v\/[")a,^tp@ Z+؍1b6m}6}܎_%Rᜡ滒O)qm(9Ղit2qٵe(pu`mHJd7HԲTH$99/*Kql[*B=X0Zv1T=)]e9<2'Gk3#s8I8!HFiMKf><Nh]qc#ѥV ?m>/ytM5y8 I tLL) 'I93' ~ͧҲH?Zk[f)ֶclJ%RgxW243p 8ߝKNNu빦$6RS3W1F{d(8nm N]gJ6;(]c?PRR#Cqm.[gyff@#Z?>s}٫Ȱr߼*SECtd`@h9},?^Ә6"mU(SϹ Э5 K@NX0ҧ-@v;2~>%}r3WΗ\z81s_W_/0'\^[9q]@Kk.%0Ra""3Hۏ$ZJ(InvDVYCt(dR-l)nho' P3` H,өZR,QL;;ZLfzai]e[y)_^>ƴ9݀IbG f!Nךr/90#:d!98’hM5h4#$ 3O嘞׼ @Î>j^EՈ%>F*9>ݯ:ˉJx,y]Qˆ6G")1P9)k ºp%f{S0fcD$sDLA< hzxMOĘ6uL9-iA@$,1Usx{[K:m49 LRʐӌ>lh8nuz |m}}ƈr}Bwx:C&awW33Gp|<;v{1s``C &$2pkh/On! }9$?j~[~m{Q\rF؃L9:6r8%,į0| ?!?iIs)8^˘c[m9$R6w,Yjm-*$)iI"9C{oM(ej,EJIМ p"n/?/|4](`dis-']_)$r=.?_SD.:52Q ̈́{ۏ#3"L/߼szX RK9Q<:;iα,Kܭ#2O r5h¹Ǎh'I(r>/ XaoK]z]r>Ē/?xi;,ˆH]͇?u)ϩ%9IYdIעM_x:U46EJ2JYed dDd^! t,~ʩ_<_ZR78ā~ p=%v/sϵ?t!;nā~¨VDXʞ2|]󘑲FoHb\p,,}ڣ7 tҒWpoظpw;6G,Ҳ8)e]T;-,Konu:4c}5jwP,IfgeLP0_\NU/}~ܔP\ڲ\^TؖP=UaغԹ+K#u>{TsN9;E@!yx|Q!zoiXu%פ1m ="W30Fkb??ӿǖ(KBfR蓀 uwmm__vk ns&ϲ%GzOq̼P(PVeL%6f7* Uo7391-tl#Y&-Kp}OIG K"q4Esn#'8]Z;6=iD6%AhmԮG"9ǘfeyM)ܯۻ/}|x[8J.xJsxx:y^NRh>`0}vzw#0pVއHGA3un̔o o]u; <;&) ЉDS؏>ߵyJ)Ao'`ICz1B;*-EO_"m @. iN/ 6y*/ )S [ݾ0qӆFn0IyzDəzv=n"G"wt=RW]2\v49e: LNˌt\uZtd )%TUo@ȓ"$(VfOw}%^?>Vq5uUʙmJ)w9 ,#{i/!\:ⰙZ%&JY#P=DdyN\ӟx\9"8g LZh$#B,(PQedZS% &NyBupo6F.r~8# <_N tr^%Zw_IGYaA<Ӝ sPkk s,OSB I8[ûw#҇%Nn +1Z68D* pΗKqwPbup):T`F`,Exbre޾oN82IFfȜC=426 }60:Zۭj{}~ 0/TNw=)í#>\c0e0ԅqI:jULy^&1vG("+y쨛$zX^i9Bh!Xj  L#;pq5fΥO'8[9QCǨ*9>n14Fnk.)}{~ͻ6m "L{DHJiѮa%M%%@wG)V/?/x,G~q-s9 In:+j3 xnMXxӖ=߿_L>I8I.6<1Kj:'VP:F ,%Jy]^|HLjIDھ 1ym0WZ&(t8LY!10>01^՞S ~>vj)nZJmWL\ASxr~>Y$#@b!OG/x_EᾷD4uy1\ .H8zշ?y&$1 !&U@ !"rn=FS?ݟKtM@ txBbΐz(/i&_ůO 6p :yor:$\iq3 +)~P>Ws4Fcq ZhcAƮfHB]WBYGߍXpG\X +~p>ڮ=vǧG3y眙y?nug]Pǧkɂo߾#0D"^b!Sm4|aӄ`fiQho5N%IUf"""D>cIQ95F]p*k4\L5(ҲDݭf׈PJ#d4\bDHI*כzhmwG#sӼQCL-m8BBXJЮLāĬ9'p>3kZ[OЩ>t)i}C4tZ^1KIkb:yc!%잒q@a9˯bφcrw;2pɜFLA숂(D! q(缚PU5w8Z xm`T.яN)j{1Q IDAT>D4ZM['b"hvD|$ܷR)OwIVFhϥHJI&f$5 Hpp=zEWL!HJɕGo`cױo~7WiU8jØujÎmnvTh,"a̟$<+w26꘧4!<cTBP0> l"Prj[NJZͫ>y,ÿ\U ,rxx>n1?}UKX(e.Ki.~ RӉD4eqb4-Z\$O_byTrNizBZ]D(p3ѵd>K&m(,C\r^ח8FU D,IfI (,Si> $4r"X-"Vdv|?ލO\䝯S g!Eɑ1S@:cdP(nG8զ@˲Pgəskamz`| U^GIt Dd)}oV@n؅ @'!p`RJ%y%ey*p y#b C[kZQhPΩ>[>E59N#|:R &}8 b2wwWSʠP@ U<$B:"܇ ?~xRRGO@bZF~$%71^qLijkBjkVPAPpcp,~ys%N614MjCm(:&FNʂM:O7_ſWښkキk;tt wU-Y@@ݞ9d{9L2w)SN͔ enn2e@L [OU pr<}p YJqzWN%9ՑuH,cO HG4K\.ikQ8KyR:~TBTL\fGf1tJ+! 3i^~Oo& W+>8Ja4 tw(y=(V.pvonjynTx8}jǯ?? Z;8c8:XNA22gӎduih, TdtWo_i6nyFZZ$|-/u)Q%6ևOǣG/I&6$ Ջ-=܇ƾpTÀ1?P'[k1RJrZvwzc;wW6 h{Q1Ȕ XBRZ=54W0<ޯo~ǣOǧ.Q%s~Dbf4P|W˒/ggཏ1KSo:MuD({am=-c k")%H zkHbyӓk}w_giqVw7Oe2ǛRޣ/5܎*@r"FNtyxPR $i'?]"*9FkRzڰ )F)a&00Tӑ{ ok~I04F'u>3}~ޜB4M 0޻z.w 1mi9g?|:|<+Y1xr:-PCXktS.чyx,) xy2YAH3زw!mN" CW=JlU5Kk 撵1z@\y1t\hcJsO3*WO/~Af1q0.izX_L^(,rWqf۶3_v9ej˴^.C6/bNѵ?>Z9aJf}Ҩm Ŷ|1[4n훷OzkP^9K}⩔ڽK  ,={$1FW ~ Wwθ4z}X3jy1ҳ.r ĤUvLl!bo^xq,F"e?W. nmBFD{kNDAQf $)Iyf?Gm6D[e~CY5}is?_m7iӇ[qj恽nY^ͧ"Y:x'y)i'fF ߷Q)%" ˋyy0|a"0uYjG2ͥd&`2C)z*w&a2_߿.-R+K^} "G8 RfwwaLRJD "9gdM'3(i*L-SunTRN%yʀsN99C2c'X/Ym4Lϗf%`ǹGQJ%N:}!СlɎs7ca6WYgOH6gy}o_~ 3 lY׮Q &VLi<=|J:taZN]zWէ#1~ظl?7a1p'm~?HRQ~4z͜'6٧Ym4 -tܮTÛۗuWd0>RJEJZn;8;!H@^(tCկyQ6?Zݎv$-U+ 9鰮CK !Cqs #1ewPb: =ܽԶцHN8qjr9O>j~>jGu91=ÚT}~lf:!12}?XRo: pk7JBˇw\lzl 0ìP;5BМ߾?6,{d5Ü "'sŒai4fZ5 O|@oLJ}ݑk*|ǨPB< L8)ED1N_z0AD9gUC$#)pV5zvћ(T}6$fp g@v/dJ 6v$'9aĄKct$$ C{.׶0sNmݷ}۷BĀ8ͫ"bc/u]ȅx4@2~wT~6,e#&P 'm2Hyr}.$1:#aXGXë#؏oO/|~ЧגĨNhihq}:>1FW$-adDݎ@a*&`tܫ6KSvm SPĜѶ2nvܪ+HȐXȚ,#/^oM| FգE׭҄0 r$CNR)i4M{^kc攒86#M'/?&O-ZN \3k2guq?/tljr*qcif@?ZIzdL aQhB0]LS.f a?]Lpד6PXŚg3`〱Uni= m`^ eJi[fS*]{ITm{圥$$}ߞ̹_aa쳋*qTQ h@? 3x!(S2ݨw'0{&l={-`x~qP2,Aħy6ck} J)yl/ҒdwO)iRvtڏ:υ)GP"I+|`NyXYG|v}$C*D`ZϭΙbqNi^U%Mᠪi@s GMNl-k=LGChRI9ewiJevI `6"fD6w [ /N;B4#q6gnM$4O`j.ugbJ٨㎄!'iMBvռ: 1ƳV-iYZ߶iiP];"Fxw9g~;N39IKq;eE±vC\81 lק-H0_ C9O2h Nݦ64;`r]m,|$Հz<|r_Z2Mzϋ\dHXJi/ HDdfx eJSayԱM_n7kwkYBfS)*b nt0Z2ޝEj潇EX~_;I2C*iov !d$bV8(PZ3{& RyJ} gqy2:wyehZw^93p7t`$x& Bwq-ųUpv1G<ۭxein9A@Pm!<vS#`4pg*~o?~4i1[>V΂f&Y1U3~")6ę~-8iσgfV老,{RH)AW݋Gr^!a8~~ gFVhc$@б ud,q:iג 1.soRk,qi7D e^nU/e~&ԵDL r)hDh1N `*Q@H̷ Hz8Oo޼t9y w u 4rTD ' , R@|w6JKF4@Ԑ`=61Ͼ'?>t#t;ԣHYrM=! 6ͩ ^)G>Ru{~ 6M nic~ZV138LyzF*3s4Q%y]9 F13s$z~lovM}>/M2Q¸Si>r!#1P){Nzu-Y>gOWۮ3=eL4T(S̉j"%=GEuyJ)gx_EJ.j0m9I6p43E߷6nO~߾S9>^lfϖ8"#'D2sIݔ|}p2\oC X,ƒRHSۺ_?OLVYhw &$Bj B'2N0Ed?C<~Av"7% ˤ6h*{_uψ[Jn:(v NDX7߿+v*i$D4$4R[27XdzyNvOwj׷S 43CiTJX-' }v҄0b#huj] ВA ƶM/Ȳ7wvxDyHmz<|N~5#r/kznϗgzK >~|chUFRӔsX2_>}y{󷵟rdBZ'N[%$N"e2L6k([+:j¯^^}6K `ovYzqmZf̛,a7^m{KqԒ%R*mCF@ tfYĢff>P .kAHhDTm=-;yێ -TrI 'zýOO^j| ++"fm?2L@=F÷c 7r]ІCJY#R.3!:g>SV)Pbͺ[;OyJdcs_7øHhmeݗCw^_}|a7ugm b.eo;S Oӄe^Ҕfn`mLuDd ^\msN/י_<< t04K=|j,`"8t?DF6O_hs9ZǗ_'㭊=DƉet,}) }v}nTYՂIGrh ϧL$ű|=?vׄ2g1e!E~zPI{}HBs1 eA0sMpUuwU]s#s+d0֧\,=׬6`v $^y?_}}:t BJ"l0a=׻sIiu>imO7D9!PH)I $ zyyN.k N$ ]c0"@t '}3AOL]pY~&Z>R̝UXh պ{.//^ֽ"nFBM@JOoOH4Mi@ d)멶o޿{jO::Q#8VS:2`>\M#=g#2`DL +W6!fnNZ.KJXkWrǧu@=M6s7-_VZջGLK)z2r#]0:guUy?&uk% ZR`EHu#*LJُ)mbE~1wMQoOC40 j7np +E$z$[Ӯ5o4n㈷& `1Sm!\V@tۇ??<.j1U2rY7%,Zz^RyNC*Z^{ fsyk.X͆i<4G7ԥ|Hi۠Ȁ>9H!)vSĝ#ڄKS(n˽Oχ٫$9w(zHa䴟88X@2qmYBt$ T֕$44l`q/[՗y|7M]B1/Vq48E$(Ҷ( _ ȤHHqg?D"S j:~7lł13O< @ #)WcvR'7YV&ֶ!R7# qܶ-*eQBpR4OAs_+XJmmw5Xk'_?!w?T+s@1"Sw׼PX$D jO5F#>MZ}|h^KϹe ћons~8Cf h wpuBasT5ReC79eE[P[zo<='Q(jĮT>ZY~vD4NӲsl՜U$t ܭi߬˶9­{k@Ch͵ik%攈#߼Euc`rbgUpś-NR>>k/b1zQTK)r;8lus"1uawǽ-v#2P"Jk%53bA 8۵Ow]|igm餠p~0#"y T D`?W!ݮ60a?t CH28Nhxyv854"C 'D A jNܑ_?U="ziX|~`f`R bH?U#03PBމӐ81<{< TZ0NCVP`Д3VmC 9 =?{u7's  |ssΧ)笪9gdq8sW3 aG 7"m0jPHPW߾ [y4rqt~kMu72U,ϔ>O_A5N8xfNhTbTK.A9hאRo>$HQ AkA(E z^J-{kA0^bDAHCwz.u͙F.M"֖/^ ig0u3tbb@ j[Z'Qw`!NøTZ%!" Ⱥ  <nLEnVP.9S@?s/ݏvڹVtNtҡ|(D5R[R00Rջǘ̙x?ns^ur֛^J'>5VZzw.No/v؃aw]1BDuZ7O_?{U(VGP\C@a,5/b!&1=yvU"q+[bYAk%9I*#;Hֻ9bGq۶Z{73G 6 =²l^j-}߼gI[7-RUql~#~)9R\Pl[+Lx[^;MӲ,(Fu@3vW0\_W?z|8]xf%Zr:ڼ^nBYhUǼtB"Ґz^}R3 vYlmn^0Hܵi"Mǹ9V8a4TڧiveiZ"/'ˋRJ>'"m#%8픸Ѵik)uR!bk*u4e0@lO1IVm٧80!19Nc^՛M`09 J@p4öC7uKE{aJDD$"9RD)Mkk% -DH@= %H),B=nfC0z AZv_|7/CqxKCoCVNY Ɇ0#nw/ڗnSEf0귨_d+uz>v$ &UV!tU$f~{~?=ӇG01q[<ώi+ mNe 3pYG!H[gw_r,`JP)u]Wp, C'bbT륔"{&n 2G|x|B0||a[6,w#Cw!^8-\{;wyeϿnSڍa:ZDZBD&wwwkSכ$qsUk0+aH[o];"qpDDʵ+/_j7̓SE㔚.nh-1s>]vǷOqٛG8ĺp#2?ߗZj!H Bc{~(eeԪ!˥u=pZޗS҇5;e {!vۺլ'cٍ.[xCL<r Tkvߖ咷z=U445,TiP!j5DTjA^E 0X֊=r']fbHm(^NPL48 M{x_|aNӹ8z) > :7_7iܴEMFH$BfjZzg1 ZĘiZh9gpUҮwOO{zmJH^rmS`ǔv'ռ-fOo>y)/ÇdESc9 z ഞsGƩ!9Q6N|8:ZR-u5N^sEkG3o?<~놇! nb}KM׳?ˣmm #3Y@c""X;:3)TMX{9Zru33^L=9q#$Aww;WE^O~tNZ)uE7s)m8JmR+8u1ަo ǀ`м6SoHBH{@@^Ume(ڻZPغ7O;OQ{|q~桪n<[wlRI2k4BTȄX!nz" pZFU n$|wx{wW{ؽzGXs,V0RO~E͗m^[98Yhxpúm)9Bg@PUR86A49LL]kq֦04lmw'h9k~=O/+O!yZ{vu2g jk(1D֛u] "s+Z/aB!td˲qi|z΃!uAy<qm\K AdX>lb t5pZ4oem,6j9KG]U5#¿s,+3c MVk9 -0iwo?%K3۲ [o.?o~+-N[YK6σ"mHn %r03y]Wm,SmHA㡔\{ED*hE-v9~ώ=! *//[sa$z\r3cO8Ĩ{Yq杻#8Ws$ w9()}syI~vxΑwO[C BU$[y$7,]5@$!N^Z%dKеg°m: ;U#\;3"\3^[IipĘn#P~䲬` VgǨnOOd77(3W/bbFarY sG7׮;F ?tF[uڿ>1XkZoA42/:Ҳe`Dڶ8R.G~Y50J@iѥe?&ZM?M)Zc {b.l",PJs%- jmֽH0ܨ@wsnS~Λ9:bnŽḟ.E!a5B  cj0J@i4qΧ 8𦷙4 ^_Oq8y). Rc5p l!Hw]V;3-XD$i os1Pan@Cq9tiZkefF8"!UZO~߲E@75DR+@e-ukރltgsGe7MndzJ˭,viֻwU~ $$UBkU$XSwpz9RyU3!iAz<|:ר`"ڄݷ0C^czc b%A#IN!Լq)8߯1H>I$VzoP8On"(g#lOtօVYXk,F К0~rFYZƪu秝丗5bU)9s"RnQOTt#Ĝm12 2)i4bx% 9Bý)'>kQCkE1 o[S`@Uժ1S]mHa6C0,H-u'P*pIZzv4h5)p-D`\cL/پh.9Y>MaH㳽nn9bvt:1&)%kJoʲ^/ZxA,u~SdmzZk+. Zp?{vLʉCݽVxvz91X[j7E ]i(-o5PGLj4g쓹/wSeFS"bS\C-IQ"B/{w#QH3uWnB-.$>a^[v7@i Fk@#ǢQ| IDATd6af`Z աDFڴĠA"! ܎]SW=I4#8,8Ma3~ʡ1 /zٶY::/ԵI8~|]WxUqaMǦJe5Ⱥ>wk}zw(`#N_)t "× bGZy%Gk"Dw#?pɽaTj[JM[62Gϻt<ߟջo~<-Zb [i˿kUM10m )vj{H?{3§qOxz5_iBrTU$z:k^!RRIڦ$Odq)NC'wGW 0w ϠKux|wSD5f k^Nv^gGYߝxԴ9A棽[y\˖?xb,,.bSL)hĿ>;V]vcjLg|[D4"Fh0nkéֱ՘O1wlRr}w[QcMTrqpuGA31ܢ JLlDL`<0ĦM$xLC靚o>;zPvPծESDz](8L%a8aj&QHݮ;jkwòmT&XZSio C߆6]Ka`"ջrLڱ$6hB!>Oqn]"#6q@[H7 ڦi܍;nge>dfo}?|68JDQ,]XO_c5{vw+GN|]h~yhXug~~V{{8qԡݽ|6Ș2Թ]z}?Ff%Y3b)mkݿ:ڛP/z^5ppw0'$D~K,;sOTɖWt40zo޼F ]6^/}_/fሕ!ioh$92 Md'^[M1JL4ȭ RZS(1TkRBλj*7/$gÃ=Ck{lN !2Vj6q$ךjU%M͝bʪM̒жmF;!1ySDP)0T= v'۝~???>T?է|]~gxL__/m7Aї?zj8<{15[;<;\!4-nkMji14OLLȮH0P 1b`@eVR8斅)94\Ծ_ŞywWuбfWdGo_凵Ry+mEfaH8ȒS}d q K Pj3'bP2B:Zzik2+9C- %6t1jbdڝ%(wFo ]49P"AjN!3 Qj tۯ?ޥC @9nwG<}|r޾h6x Rgw}% =%H@G~j :<$Н*xVTL]@L/%2FJG Gr]ikӶ}8k`"VuX֭U(@]?􏿾5,cCfjkL d88A#N2 ݛ#a q&gF9`<}`Yrx*C=;|~|sW/" }@鵣pEütbQ"dbus5@D$2an ݰ4%B5dF fFiDI!tU4`7@65wk4y>-m]Cq篎~ݍ>5zǵ"?9@)yvN'=x/.OӇ]?MXcy~1nO]gCnu$[A.hf۽(q,Tr u~>3;՚yϥtewWCw/Ks}PVFm((:on @ME#ZPZ6Q PDpSlE:"aZ5RВ3̹ tP%DKPEOVdrɻp,aoWmCоYEjwLWYm)+s)[p[^ji~P//{~߮~;暲lnY0cքP ֵ #ZfEtY@RAUVr+gUQc aA?{4)ox-}G{o?} E\?%8<ć㪯h[!Z)M6y 9 㼥☻Jz7VTptU-);PRډJEaȬ ιZ+ik *VD8 drmKfrА 㲞sMl[r~6|qò^Ͽ˿~+ ۷~{ey]|h/0hZ*Dz)im<++c 0xB:@@Qt%2m}:]dK mÉTG:=w<~{{8=0xzp/!w~u#U!珿v M\^˿zÁIz=o?}7?+&a9"P7 2 ZMx > ,BӊDR(#0֪TjA{5tR [\+"QZn3l9ZR-'T J_v!ցy+~&|c ϰ Bɩ1"]yYRן]ZVD!Π7nŰc fRe0@8fv_VԍuD5PAwş3ޑp]㻨RޜӽUȾp%)Yl<:M:rG]ar:E}*ӧ d~x4are_&wqd1Y)rn-0_\]yW^Zab)boR#LR3kzYᡋBwMwLtc|cx4~/ܧ9uNQ{5^)=f\x8mSNd!ewAB-lM.ՆaM9XVUջ Zk%-U[c$USyZD臀]>qnx]vaħAP~}neIyk`G>>U췌uCA8[B* bN낦7j:~?2#kJITJ@oPRĪEDU*U-G%ZZk]JZe.:bTATd-;!a_GAܚ17t0fd NJm,u>`s~wu@OR&p;[3` h\n4{aW̍t/o۫f5!v+r?sѹ5Z}bVyրu"u٩1ۦ.К(yCN<*sm`=W:4}8%)0viYq,nmc_>˓^N_?j-e jF* rs>e(֜jM=U6$l\Pt_p@=mz)ܮ7ƪ*pi} I[;Hc "ʿ]$9˲!xj"=.ڵӃҴbLy6~1#MZiGǾo4g# W;js @GT |Zz~PHhU{Kh w?T ֪徼ܒavcɿҲe S:Zfø/K*ĂXTɸ[NDzл ZkmDj`YWQbyI o>H+L&Kh{C1_1t _ׯ˯q>]# T?N[|S_.9߮jѿ{УH.}Ӊi׃yIۭ'OUiHJSz7hڼsxYW&Zo9Θn.ִQ@%C{Kj JoKeוcMMy4 dqԚ7?hd˾[ñvYvA]/u-#^ϗm z1F~]_82C#BkDSj ǙyGYfQyשSΙ5XkU5A"< Pri*^|<״H- Coik0o9wmY. vBUݶnhg4Vimyk}xT*ARsr;{oARwCT61,t/kH%SUY.a}zY3b8vrm95٪%$eR˶Ơ#Ҷ[@o8r4%;Qߑmc0qKkX vD"76riL-1 j]AC!N4By6Vv}m[k)Gk)tyݤV7`*l?9n<5|f\yAsb5_pJjk~nvu9!~ȭ2i޿Qyڱyy^-0mA48# jl@q<Ъj@OUXBacq/}ݿ~})-DZc;{îRr8 sn欉B,`\txZMv3m$weJ3dN|=_K]Tգ=OiWA!@E5\JOsD92 /p^u ֏4?}giDGlJ,z~_ner}6įlU.پ?XVdPo`>}Oן>8CFQ+u{rY/]7L Cz_;x2?~u?|[`"ܷ/ۃO%X6H6f|}>GOџҾuI$w]! Wn=Cl ;M+-okeGDBt”F#ެX벯?o׶Ax7?|+#$I~Vn[R.rmwYӾք\oD0Ss„,KYv4={0*( C-MGzM M޻6"TfF6(6D$BUs 범ROuo).[ZVڕE$睍-yu6ֵI70v.a_3>No֧eU:e#,c?}7G~T-&zIOǧ6)Kxủ85On~|F}AeTjgnK!eZMv--wa>xyW A eck+ߖrħ)`iyY,]mshh|^%|O(B]Rwj|PH J1. -WIj6[2Ck%gW\|DȱQ$q( IGW4뺀vO;IR%;06T7!2DDZS1?ñCy"Hp䋵Z[iyAB k le $u].FVy}28|ey];d+y-{?aY^LJ>m)nBçX}.?E9v(ϙr$C*3_LOtmm!Qc5r==?|K{(NSS^.ǓyI[9?0s#ihk{׼{3˗O-tzkq ʲHMԜ`wKz޼'e8 *ZSĮkɵZ6Һz몴akMyIE5Z\&1Xxpadg:vdItK֠`˷֭KłߊPsSKIk`:,LWj:XߖY"4>Қ xO[) 9ax˺]w_p IDAT4c(aăw.C kNǸض^lC6O[rж A?2n9°5ݒMw$Ox 58iЍiÇ܍vHԢS.P֤cB4` x玱NyK@cx C5u奘"cPG0;-Hb e+tʩ"$ǵ Ve3Yk;TI 11X~ä[ςZ+/OTUs!Kuk "R*]`" *~w&0y 2)ն}Aq s<$|0 ecDK:Rږy-龷\_Km#\ػ'ژ$럎&uY: m-/_xv@yhQ{%Vk=_ 8uӷ1Dol5pp,r̀bjaZ  NbJGzOއΖaT9&l0 j;? z.]1mݫt5ts]jf2R*..%WzYW-#[kv&l}.cTRj/N6\k+28 ;\rBc[05Lf~e$vޘl7%{1S"IM5fe=rK{ID)xrn뾯 b!o *N㔸emYWڗᑌ)iK-$o.nw7<@HsZ&@&" bP8qAE1ƾCo0Ov ַO__]h8?>>_J{p:[Jji,6}YJ-}I,2ٓt0TXqCTA^ @hP;KޑjZ{]Meٻ=1؉/UTa[ڗj$/)7GeQ覡5#c޷/K=MnהR[J붋4.G YZȘ 5eKI_L{[o[y}S|K"ٌUå4$OyM{^iܓhё ,ou߷|އZ_<=*]ey>< cU1I1W&.><<[!aFNm[ki]ûGi>ڹTԈH7z獋DJU\徥}<>y8 wuΗ\٘*MTiC{Ny:s ˖. B-u_(r+ c0K}fR8.CpS!D>Y/=okX2Xk&BxUnT@co1gc8Č(88qܧ=k.l1SN{21l՞eFsixOO.B>γ+}O8s}_R]t¡1Yk;ҍut2eߥBR-2(~IOn\좋#c)-!nZKe oG  UJ^UP¿t0 8G UJ] ;SrjU58gakRV<==Zrnpnr&H*فq֖Z8d5`j.6XDySjs`pԲ Tc J*ч.T;j bۖl x򇁽r1"B5fosj8E`d%RwSoȩkfO׺T7"p:a&#&k+9U絤L"X1k56f"tH˚b߻ּwUm[yղĀakjn jkOHQŢ}8qr[AoaBo-Z!H8e{39q E/^}jۻTJ[6L4t^c<!,R`;|%m 4 ,hAD vd}-7G(z-˲ j+"$"ъ<.A Qp`#R&׻5D1Гjе ]Fz/[f½c`Y;FXo}?oK],Խ` C1쩀 *uoOfC{] 9cm_MoMC`d ț1 ׾ !4}kpx:y_^׊n%_{3HJUjPZScx|y}\ui٨@Vh& C@ke ɚam) Ae_9zjeO@/_Jn}=3xoQlRݗz(ݧi:9]T Pjg dc!SG#\k]Z{j$1s0y}e/ 0/!F NR9[ҚWgU{ɹV6\^o8[v֎Tm-K]_h= Ce)yjmZTCJGv_wŻ}ɻ{<8g)[uHowA8WR`!/_y͌FeՖsVD2,o?y"%r*uPUG 'o^ζcRdǖ ̈RI @jftM뵥^4˥ cGtۺU4477w/V_6mӢa^[K. l4YࣇN]: 9'ƈD;=)e(v?D[K~iPbjg[.PZPo&&'ܲR#֚1> ~m_=*0b OCU ;Z IYq % ekrϷ4`Qm] yzZ4HuEѴo:h}7j-Zgi;v a f.oФzng(Rk%]j'wŞnݺl뺫jMyӶ<}<%!]g$VQ!i3"JP[(} hEC͒"X:DQP:B^4s. q(HD5#8˶8ҞhV/) zт*h;!<@![)څpܶrZmʝr!@7eT40TcQۚ5*,)q\˝8U{tB)E)4)=i7̷eɨvyrYrDM|35 D.Ԁ'g@mLG6=Zm./YQy{ﭷ=oؐ1d^^W,܋0T|tֺ7|;7.8D"fgq.,4 bں(esqͧ#a%n{sbpA3 ] XwZ+;L JReCJ (3 Uџq0jn޲^je=@V*UR$V<4. @ٓQ&QBfnҀUm߰:P)wbߕtYR1Dd1`FymYEʖ6N Tjm9Ty 2MRMnhqfo[Om=8N󩶺W;0E\kQAN3 &_!m\z z0qWBCDTr{'DvjloNH0=w~nxgt@[j"mK }xd9B)8;<2FƱ*hτ@sc[3PUmWjvRDFwV2J!Tb'iaxV' 3mSggU~O6iz߻ X{ڛw8ϵԡfͻ{3Gil,}u-Q(a-&NRo}7L$0s09$@DڴrUkZHnŔ3;A!vN k'8U,%Jc@Tm#֥=Ra8 ϗVS^;L?I;[l?ܪa"&ZwCv]U)228󖖼T̀E[MhC.DjzCf4uF:O]u~kIYET> D^{Xw*yi^Nj؍>8c/cmSֺ}f1-q:{|7"D`LKiuyUjԐc"rjD\QzM ɜodJlaw@ HR4f,KT1J_6V~o:0AK_2(H ք` 9ow]()'kG%Ty`1m)0rv-5 L=J;8ߍb_{ߒcUz*1}[΂Tu:ZV!r:9K4,v;1O߮_όɟNnOPZs2Çnhf0a4b$g~y';:0hu`Oe얌MTIwvlep^K&ə{/RUU\ra#4i =3-JdVfS#xЍ5[.v0fs8!gN_sj!E\y/& 4'"Aog{dJTB)!:0NDov(Fˮ:YJ!g]\yPv+:t1>F#mH0yڶMK0t6rMɟsW]m-jYY V/UU w{ǣ5v}3tK"M KNY oۦLh&Hv|&3q*8}7̇Gq1Bir RA)@@ RҺjw QA.BH)A 0.@;c \ z= e,quy b橔8\\KIE?߾k|7?6YZB >㘟_'QqbBUwzMJ Wpiڴ\$·k  xYђc(/o%4l$=Oথ$GRpXa]| @Jg|a6dmՑu>-/G/k㣉 1U6@wcb+%&OH$t/-/: ud u'|:_0wCs"vh+uWc܍J޷UR-4+8OCH \:}uoO%YRJo!8M1ZkSc?bPon.n$L+77RyJ?O!`r7d?i  fMe%vye)c?-eU8ͦb]{VI82qO$lZbPڔdU:1쩼tmz*1XH1@C ^56ٿLuM"O|׬x:8NjnV;|:j+UD.y5e~:7(G;نySqۡ(J\R2iFXSJw~]\ @)$=,Tbʔ K SRH@9Cɀrz=.fDt=+%)BRDN4 \W;۵%z4WR>ɺey]m򊵌1ak9D+E-Psi֚38ỏ/w7?ٙu^_+>}C!Q:K4aɋ_^|dvb?cZ{*%pXtZkeU}~^61C^ ӄS~zMS^O̗㯾 F83yna gL ;N!L>bNwxAbXIވze\rpeY~}a,OR.vМr/<`d2R.DuYSBy2)aV,P7K$0}ʄ2(}]_Ep0bR-UT )eYRDv'&5ӴuZ0kMYx&!j^_|] uӯc\ׅJ[;:xúuM.`!fq~c~Vhk0u>CƸJɺوnFJ4n& Igh^IM VH~zݦ4߿s(#r8jO꽋zv~B&f1_,aDR:݌f\\<3Mt!\4L洛6iukF}_|:NKmX4Qyw^FJa?_G/h7_ ;u>ϣwf5??!SJcJ>})"\B:u]5KPXJF3-2傅΃ж}d]{YurV&KS]U%3͂3DR(RFOjy޹Laq]M BuOS&mv IDAT 񩮪&DQh< YkcqaNh$S.tZBIMdO瘎Ut>אP":C RLhj BS*+n+Wo f[źn=jf.R \UL\67}*6τBôq/J.ERFr^x?|,ԬJ꺽lv_fkMXav}2eowy6޿%@JUN3ՑB. R1D) g%2KRJ{!bV4MIJxϛau=LU2L\m沸vD6 >OȘFoOƜ=PWja p3# U0kbV[IXS8UCox)<)\D4Ja1DŽ$S$6'R'N(\AoM)Iૺɴ!E\4CJ삥Q-/ .-lXIA:LizV'.Q:H8vTnN a3t\'ur#dnCqaѲ2tT$H \JFk@rz4s ޝWs21^VH&@ ?× +);<ϋP$/>oG[#T#R(y7/Χq JVc! H1boq()&Z})2MFkҚs0XF$(ĴB~*BS|?g`9!W(u}$tOQ0ъXr4*.n#g}(g⮯,OB :+DgZba°xI0 nE' e+'Vs}pBO$yH0@c>`&7))`{la@}bVFUy-13/+u,T5@KˡP2/6$@,ͮ#)dd1.K(PRrRzE_qM +QQEBLjt~ZJlxniRxάsɺ@$'M~Gd{Kmu."\܈HsZfE 9f&:Լk3wJ Сֵ#),,+"9Ӵ6sỳ@p2-eRP^GhRq e6wUTX^9mucJ m8oyl9.dkW.{\RzYt8iMG)+n8%Y%cNKU]Uz3-1)ݯ{+T*W"\+~j^GoHl8-4ZofF I u)(6]?rinvkŞ,{ h%, %L*&+&Z JcT~mݘoү@~k势L`Xs2`MM +-hUkW#벾fPe/Zˎ('Uݡ.Lrda5T C˧vh!(0@_]DA)@HjG RAkKxX_~i&]z j|ct݋tO/o~w KY/Hl]tHQPB[^)¦ƭV Hq-Mm_7xܫPtG.hDiKӳww;J=4vݎJs,e<}sǏo]I-<<4Mi+|̩'Nfr:}xPyJobq\BVӚ e|2ˊ#T \pI)ϻ'aZJrvн>}%9/[+U\ 4|bv\5pS^qA))A9{k\|>jgs.2M@?F>8ivRNJ%>sh75TIk}J%0yp$LJoLαЌE KImșpF)EB #  `ɐ"$#PQXj7zZU":3E?hQBpφ&Z3t  (x d&K5ZRL y/WrO%uNLXp&,8fZ a:T4Ffgj^#`V+6X_2T;Si%?ar \u5R7(jV}9,V6/yƧИˉn dFV[S!VН[&1?(_M잻XDp>%֤@5YT?mzؾ]xAb%/3Q&ɮ< By9z.);޼bCn[!*<>>xrN"Pxr JL)˥h-ZpNH)_di`J|DYkʪajBYp39CR\jOka D 9\7_/4uss q 9̟?__~>i<)cP߱fͶD)EAFr=|>rG}ǟU6s~xfD;e1NtH|Dխ??x).Jcu凧ݽ?=5*~O.>W^G$ώZbE Ǔpٚ瓭j:h<~xȐXDO˒4LxKgζZ/ߏm]R5%SIW;4͛JA'߿8b|N7/k{j]yr|wO?yPNΔ-- .bOgnt✧1&55V BjN9xLp5wi O鳼̝ b%#tWWK%<e5JoeMPzϤDVxu؀gEjtΚu΅JH1q>clr/Gm9s)y1΅"(Ʈ"ؤÞqhi auP] ? f3fFVtߝ'SbfJ՚義1᯿៏Is}e+4j{J *ȳ}fr<ywƞQ% -/q^U~WcS9Y@}?^ʹ|{߭ q`$y4S pDxfZ/$$:cB 9%H# Ɵ?:^sJ_cH'>l>*ɭG)`WFXS4V~NQW=ǏYe}B& K`B놐ۼp\T#OsAIN#~ gRnWؔxxZa͘aW*a7 :[TۜT, u;=>õǗ(M8}3CULH+Ґ|녞.p e?XPHQd:H˻2MzG-~#EW2ǝ[=NүaV+hTˆjqV]nJZVb^q69͹] P:zWEH;`@+s. V\Ҭ9|$l@rǔVC̗ML ވdB\/ö)U-a3JimI9L.T@~|4Y]=Ӳ9k.uua4ۛ@hxF[Yt=$Aܶ&&gCy" )@^]1aZ}7 R O/W}4MѸiY8Y!jkхȕ P~@/SF]fRLh`?:RX)3H xL/N6Xk)Rb_oa٪\Z|'%__ l|JJ S*c{E901v⯿ՌQXMMdVTW^p$ 8gZԴ0UUx/7=֭oj"ʢk2K%.Y;4)$ UΉ"uB6€V2g"H-7mUgݾ^aʛhfM!lKT 0K3b RUBj" goT$0$B[k2!+u̴8kW3H^1c !SRMK% \fd1r^:3b.L9x69^sA@~ (8_±"@\81V֒.(XY"lUD~#m%}VL`1qK15J0Ry&JJQ!-B/݆R7&Z_'f`%4Lѝ)٠Eij8&u p #I.۰k_ u#+Fkt4r&J|,ϩ 0eI# QzMi*1(0yB80'd F0m*m)( !9dL{$WRwM+ |PJ! JIwNfQ` (EwY量_{KQæ;1h) m`%֊W_[N yzNxnM'6k7q-^nI%r%E[-$ܓpMLQyJxOMp.miـqd]`19=_'Œ.k:%s\!'Y1:gP7&DrAR!ZrH-uDJ.>C Ml=e@ϝg# R ;&|nu 6SBS΄3A[r̋IBVBG6wd-Gmq miW.Iаz*HBɛN =F/)jo6hRyw2}7'x;n:XZj%&s9)ǘl8s%z k1Y%!,8GFvKxG%`M\޾_4e})j/?і9Q _ƣlT!C&B}7XF/;8_W:yb|8crA΂\sU*U8E["(s,$Z2rO #adyS-"7v@8 ŕRa +k!8@K))~5|;&:-OX:itLuiUx^4 ܵdI양N5KZU]tNsc U9BH-z8f>0.{fsΌ+ ,%+!ucv%m٣'# m'Vbތ/eKϮ<иmWC2+ʅH+ڕ'e(${ײ34]ƬR[M%gZk1|bK 9iz)D! Y'D0"i Bʄfc><. i? _~IlKJ C6nkBA,VxqYtGkniNRy4 #U.)$z*At ^$L IDAT7lc$Wt9Fݰ&HOJU/>\BsqRRZb9!4n뙇ҕX ΡFCN_.u9)0c"@fg(4ض%}U0v[5>u؋+͋>Y |)/cuZW^y!^azU_u*z9/)OY䜢w2;8"ooog`!xdb{CU^QtbY/16LX [.ho{d?X U.,>9.%E'kZr :5zc!Jӫ]^e)4 =]i^V9;t0EdRޕRҴ?M!斄`J`R~qPN1KYuM=4FƪuY1Ň4_tcöYtWwuv }n'"nIV}W3a ε㸖0Ȧ%BWTy=cNQ.$^"3ySBW%XA %e)1x);~^h `Rv1z")3cw0&dbh]}ԯRuwI8o}m.*0f4?fKۏoӗ7OzpZqHN2dsu|8!:U[7,[֬5\0_PLr8O\ߥ䷛Pƺo}UZitq~v2C{-.87|jy*|?٢eY?-Oφ=.E]<7;Mìc[ `rxw)]g .w|q3:]:Wwy t:*68K: 1Ϸ.Yf0`v7׏ x`i SYn@kN2ts[bWZ>NV$TUi !i9g0H;bj+`A C7g?J3G%{8 Mz4XEX2T 4s\w'ǷjτV-{go!NFsYW޺ڇ-Edo~*;nbW.KRDt%/p&/=y)n;|u6b@y iU>OS y3loا ͆^$Յ@JSYUvU uBh@T">w\M8;$AmK! ϳiS"K}SDΑ;o nex<5ѹ5t ,M̈ZQe^fT33G0A]QDT=(OrsFT缁{zoMTKUMdi -tɼV,D+*f;V @45v;fmK 1֥ݷ񘧩\>4ZN]CR[(7SS]6a杆e gTj+x1ܩMU!4eS9G爠Y2#p71RIp;fhd>lieZ{woŝ(CŃGNKwvKɥUufCnjiq!ThFR؛kpe! 0/y?t rn[\C~3BJ!SPw Z:w1qJ2bd睡hmVC"ZM~}?32.Ɣ D=7%Bbp)Vplְ ju&T:w).]^^^FSv JiR=x" 5̳Zn\N6b%Wj*j^rʹ "+%/\kD:fBTET #&R✇S9s\KF2C3mNr--׳͈ =YߥO_@fv@/>ηUF&=z2K]1sSk Uuff^FZkIrXԴT1 $G$cN1㸌5mJk-ZA^rJε ˒SQMH9fVk%w,K.{?3&)S*L_rЏ &Zi(GB20fWnDjy294e !GAD^ m"yCfTUEtĪέWdL9Llf>1eBk/R7t]/!]:91;K->%nskG2e8P Am>/D |o2H!"`ͭf4UUDVI\ىHyҚ9bn-TEՊ˲0xly2.+kSB>/ YDZkkEk)ᤦ̬❫ a:o緿OJ^b} A@~굯|yo~f%tp jCjιj{_JYg]QU#bD\"RVo+jk pDBI!"13;f1Ndh\JYq7Qªn *R"t.͠|+"jҼsLkusƞC3v]"bkH QJSR "8}ߔRknm/Q+.N'"Z5hya<@J|i<(. I@i.hXg?|=LӇcOO~D1Y@cdςW/сATI1q) "r_UTB[9f~TDTkslfLb fbBpއVz^eq\fJLfD:=2B̀X+92j7U(.TLZ1Z NMaCx]]߱ct,uYGRZ~` πD>b^{16f.'$U"WK1ZٻUjk"!h~9s Cˈ=/?}k?;wȺgdv$m}s_w;u?v[xxq;lsaAՙX{]X5 !YV#BYTy)b)e٬nʊ%@Fb*0ju# ~]lDgԂKK̨}8Spe29 UQ%@~!@Db ZVͽU뻎)缺kuf{JQѪ֟1Z[k+"iZ]e 1fgulS1u] Zk!ŔTNVHSO.xJEvö_?~B$F(}N><]`p>Op]q(d.C5i-1#!;"dcR_U_WZ#cETȱ2ʎEsjSDf޹e'G]xd<"VY\ j{^x;ŽWR3<&Ctl*!xeJLM$$"5%]k-lTj y !#;sCShر9`̳s~+%)(sa"91 1@oVbMªGԵ8ǍCcö΋p>!,xu'۹ͣϽ/o/ tXfϷ[m2_tllK?>"erAyk:r<PUxS5 km2gL^IU ȱd;B{'SɒaL8(vӴ\+rw{'\_pu fӌ|vZ,cWj02CS$$J\sV͚ʳ܊ ;%CFS Z!98vEL޹. ҬSR[t^ƘsvZ!]1>>!"N'")N)tqw ǼMKIҋWk|X2TEpb n CHq8w.w2Ӟ=!>du9.b՚M*8"FGЙt<9"St p8pRK154@#yёc.V,Hp8A&݀n;shVN8,Z~۝扐vik̮ꐬ6nxWկW>W#D0PD@T3%h{zO'E'/|us墿{!zhBmmuG.Hx9= vpg$M8#..*itn9y\Klȭ[ax}dw»o?9>98t/yYXuw ^NWO ?~V4?|7{㟿ͷT,X6C/ ÈjͶKݲsf? [v!0SĬLt"Te/|bRpB!xOWR~ͥyf z/KʃO?!_I_|qu sإZz%V*&NL,ٻO.0./cԘ">kr!ƀ0pX{l;@ wMt[˲0Z8&?s %/*mwH&ؤ30)@6q`DNə` 1e6Yϗ1C]l9H~e1jẐ u]RhuU3pm`mǧb[i˸<}|;O{5Mfr:u)9?2lVJmvWWR'>)xBKfd]R qJ!4)ZbsO46gC;_'$oo{aL}9K YJL5WC]LIJT$ƈ!Rf38֝xvDJXD|Hsp!ĮDt)9UԒ@ZuB11(ZB LdwM  1gW,?+󵚱qH+~X?g 1Tlm\ I<-c-3lfКl609| ZK]J)029B@"ʹ!sdbjd;Ufd&e"CBS#F&$Bh,ƌT8G&2=D$D@Mo11AD@Ռl=5ׁVggFU@}U/?_&u3B[2@"0P5fg%o׹5gd p@ㅟ {4f,^Vl L" =S Dh?L Z[k>= ޾/(Ln`gA[߆BfuSe{Om ,̞;3Zz& |^Æk+V`J~{5XEk?+#Zreo~GYR<۳ے5! 2|x>=i EWyݺ`@kIDAT~C?Pc35f?a]tC{Ǘ}ttGV GvmU<~??>y?ߩƏ.I5hȷL}@>?Qe/8~(?β<}fg2ii| c|>?GᓆIENDB`camping-2.3/extras/images/uniform.png000066400000000000000000001205051427044526700177520ustar00rootroot00000000000000PNG  IHDRgAMA7tEXtSoftwareAdobe ImageReadyqe<PLTE{eyat[öŹҕm밣…xZ̯'֣pٹʱltG󩛽嵵 v]y픋p{khz}f4Ǽԧi}zbّAߢv`Ѣ-瞏xb{f+bGw_{hww`ड़hQmSǿŚ]{dbJrYĺצaʾhfNi{czc:$u~wcxꤡswKIDATx읍[V3!&1 ) WZ$GTg]:wӯ׭an߽ / өw^T@^'s=| x4?'O x4?~O!z'oh⃸, 844O g^$ˡuYYYH}>t{^}٬z .,BYdwՃ Xm鰧TdCU "-I YU F1HkmȊI˺!)H1jA 2j0U&$ME|m3T7Q Y!6  !-%$]LKoG^5i[0 Dr/2\M+A_ ֳE>QT ZWyz,Ǖލhd7AR];4P,_ިBP2zF]Bݹ=^ތד00^d1)ۻw#v(-e;z>d! uCQn"'n*ME֒+[@Eܫ-s &oEDb s$PIФYӱ_3bPME'_9BpWnPO߃VYJ$)p&qC ?ERcL',#+Zq&y=^3vc櫓jW i(yeT-"ωj>=^{>zMb7+$CxAiWUlm]?^v$1^CN{97#qqVP.0{z+}W } Y`۶- 2whyڟe!I[ xRW(ޔu8L}f/\2J6TSr.`fd C$Fܖ: d&jZ9S\7URrC>K{ YGCv~v-v/&*v `jy{r}eY\y"C"<=T++%6@tt˥n뉚 ղ?+Ȟ7;D-aYOz'ݤIYSTÝCXEdnpL dYಫbDSAɽp5=\r 8 ;.7k`M n' . "h^I~'H_Kqx0]}dO,0 m&ە`Uw9=*0@*LT TBLZZ,ܪŇGΟ JL(PV$Nc Wp89; nV`\r)W{dU @Elz*- ;Q77[?];L9a 6\IlGE"IX&RMv#-O1~pi94 ߨWpΕBg=qSc$ê9f;-"SK'vpD҂?)zqGIRi $'zn4,in$ظwfFr E w`1N*S7Z,e6EΡkD@/ʀh-M{m 뺱G_oNTѱ| Ln$j1EByC歮OϺERoOsE $٤'zT>ͯ[Fq|Lo-Tt+)kpH["`m͍0e9.S{ZP{k #JbKJ<]zDridΙeF*Iڨ~X*Ʉ'D;B,Mcn".X+Ժ*IMOܞ {ʮȧ"&!cj|4_[NZj(Bn8l"T=wwwd$g! )6XN d$ J_F z.CM6`egJ V>&T,G8|`xF"pnd@꒒J|Db(Lȴ׭yA`agJBHUY24G>RWﮇ_b>&v`@,=-⚯WUDNeaII L y.BSs**! {SX*Rf*Y#IeHz7xE͢ n>!Ne7mBM < H\zVJlth롩J򴄯 #_˺RWJ-A@*CeRD|1nYEfGL߯RufWi,.\C\#и ބ1*45jR8ִ}z92ZiRVI(2"Df?-pw,f!u,BNoM%IQ7:*aΪeDSrfl+^@VQNh?%ZTBH.u`u-)*Y˴g",p1-]8*Z޲:!WAfXSՇjڑ8UjCGZR|5C*~h2hd_ҠΥ^6Y%ʐ@%\'tjǹٜ4Z**GGh\E]MJ nTC.kxP($.bD:濸A[A ^qU#]XE+mWIܵ t`K.obWC+"Q+"ƞ-E^! hEkY*Q _P|Ls!c3^UExW+T>f k/ #gLM4`(YSDR6$IBEՠ{AYI>7@%tNfEPY+5t*2'#b kBX"&\| b"\^<44|Pz_TKhgEopZ"cL,:.soY GMnBrh{; {XuhzS{OL.^5Օx$,SԱNcYR['6dЯB9ø?;:Y-GG5x |P&9n/ 0^gfU.wZ鬓=,4=׃ #{'%W)k|>BwPt䢔M&xi U|3$U 40[v_M*nh )iC5~| A1{a](ƒ{(ZlXECjjUCiNMۯy?tRwPגTOks{T0ZZD M9Nl2v `#5?)ͳW^}q!﹢xIG29P%- c.ed'9BNc[2 ogN#W++Ϟ=[y7.S=맡E4{i"4]['n08__NT`MPbkH,ҔJ ,~J&qGHJ7`.+-$|A$ި6<%GwhqZƃMH`7qz:md_cOEx͟1`'Y>D])\ŅJl) ߈ظ&uL?mߣ{F(l6*i^#xOSJSuHDojV͓8\"._߽'- '2'@)W:'>Ku^'kh|Nж,g> ۓsƠQYB8 ٣#.؏Oo^ܤ| 2џorZ Ty >C h3{ЪC xh'JұRj`޽6)EI$T58f}IRVZ0qnVt(9Sb}>gR[_YWЍ0]vk3wg@G.wSYܷCUUIUPT2)6;/!͜^B:H4}[WIRոM2}&N݈m %CqArwDԀW R/0-6Rf DSs(_侇vI[.;jɾzY7澑NN-zhyR1G3o ^~T}tf<8 -iOZOKPi$. Q{ -*h\Ǎ!7o54 ,Rws]i5ũ+\/Aٮ@lǬx`ht]֠aJ`G@X3bTidLp ůh嵓r _>@Ket!V(4$U%J0͎V74E.n`ko0WB~"f*~]FF'o_j@նȸePEV,NS7j{.gah*2h,~o| {m=ƂuȢg #Fv8S=8# pY,܏ŧ;<x>7*cEI)6V])%@}IBECnR4<N:RFԾ`yؑO!z, x#,u|n_~o~g߿yϞ^ҏ+ H8b!vBqIC|H:? HXtxl!n}nlkAs`J-s/`6V^s?oZLq; sԧ- ȧ;yղ6tXqfM?ul8|I\G5v+XS// ի <^~wtՏxmT&VFT !SLڟ(Be kZ=LBBVq,PYntRvއڵj@V[tO njZuʜ/f[2,%OV+e]֍.i@C ~Մny,ė{i錙vZC}iH1W>V>ŧ:s]Taɿ|KbrLQvir*rJJA4Ht=u n[\쵣e.`*ΐJ&p۽A {^!gy:%#jkyCF vg.^Npg]{(n4>rΊ[WHe'?e_grXrʂaL|!o;#NfJ$8+i߁@t0htlgaXrj?3]?JlFGFIf0|IC&Fl#\GN9bǀ@hC&!AkI  ? 3}N`@e]{I<ÍFf6ER Kw >=&|Wˢv"7НM/`JWsrIC&F:-U'~5Jil4i<0"5kF;' f6ih:tifU|ꕕվ7Z_ &7[ Ux4qH>@hvcS]CeрYem+z$ݷ٠]FXuĐǘ?3z6go6<_?L3?QC}xR;O~hH9Az`W&pVȿmHT$YÖIŠ}|5g LQs[Z|,+d.ShѲ2]¹v=*~~A϶g< AkVY$wɧI~xрG״kF[0U^o7z󇷟SfW]Gx挗O{.Agqf bNH":rdp;AwJ_k%_Pė/޼~ehVUU.#Nӵڇc⒫6Qע!=eu=W+?zO9Ϟ}w5ǖHNOtc#}GU{l#fsog_ <~Qޡ?wRdb3>"dÀڹh իfcCْJuܝqCKJiZ?j z-(!Q8͕T(VWQg T%ԮM{7QM~ܙhXUhv~eQxԞ}#xJw;t;gte():cait 8AO1|o[vkoG섺_RIzuT{"vܻ7,!ď,! DŽِ搾%:[ `.wb#+F,z?o_wx7[$a}RtRT|gNͥ\?UWH::[rIw鼵[FP:,0w«.pNc;F]qB~&rJz8K[NWW>q:=tYNg}ӑW_|GCyEJE%f{L,_`kEB,Z*VUeN|3mt_<{}\͌hWnjXuNg+IW?^C4LfڏX÷/43Ǽ 覼e5-4} wUe]+eBL2 ?T;ħFD䑹)br0MRI)^}K-OeoݻM*w(9- HQ],p^J1'FM۹0(VgqXS^ś/~~|;?dBP8X9"9f5m\l~r>:˦_{ CYOW?zg5|bi h<avsgN! SRcTVJ=,p@L :+t7 \2Hk7HjSĴArYe"Dȫ$YP߭tBRK\Ijjv4%eץdZ'ء!|-ph@cvNE[C:-~(`OKCc7=XAn_]=/B*Z_9>6, 1-#]kml."XqʝT/VW_}gҋ^ l?{5Ӑƒh l/+6.f?"+kcybgE[/]RY%ihhRܿ\o&qhfMRvH\$.݅qLFWW8"Az7MwA_/>{K6$/:$mA|"Ha)􎓒΄]VEfɭ0eV8ܡ?-I~M 9IJ2,ɍAY$ KyO=w/u&t2Bn#q\޾bq0 Υ$)Jyn`. J$"F pjis3?7.ab6<ґC;4~\eLjnYsciϼ=wzq.|'J hT4,И, ^V:@WkzC=iK9#H;$7s,XfI X&Cv;<0 ИҡB2( jYcE]E]T*AhGȵd^EX8#A>M2;>(n[=)y2k knob~wHVޫq90r͵Y Ǐ̲.^`%W|fݪVx0zyГX 8 x _ :oRKfH:WF?lt?web5nρVu!.J^9d};x<0HCf2 "YVKY瀆*۟JʍVIq&GX6˽]^>ixs4!(]01knO06XpcՍSI pƐ~DB;G8~)KI 䑀l֑'[`M\&OBp\:Lǯ;ԏ\OG^ZHwXx `e&vhRëa##㪀uHjr޷kQrA6ǡ\X U] o4Uu2&4"Kjow88yk]:cM.LV4Z>`CY^x5K- W'֐䙚u. Ű\v5OR! bREʺ=, \B ;[S< J0,6+IVQ,и܌]*B2dYt/ z'` ~?R]cN825'b'@1U`+z4IhC?fU!uv6(fXQTngA__lzfw=)&J"$\0v+eI:Wao?tԛ8y( Jgpdzz4x$.7  &S;|,܉\k.nTn2=ml{\.Iu\f{9t71*Pug[K~h:9^jh !̙^#f5|w3 m@;$eIFQaMji(gV}:6"v(A)((u%5pox5C螸i/ Gpi H֜I"f Q_?Kt?)atn;ez<;n K+ǻP'Wn%3]}ڝ (Mar$^ΌYiYdҁ7'5`Kz9śIo4`ww99nSA[9"0AU4&7Fbr $E3aXJ /$fUthz+2k], ޱ'NOiK ^C7[~ȜQ@w5q0U`$es_6|\B~Ҙckw8EbޫIywGI20馻]aaG8~/?ݔ5 ]0asܺӜJ"U4K`FA:r-F%YCFVF]!Ze:VgQ;AY'+>a1+KK@XRIX%G-f>m2n%X9;:|:pݛgS琿œ/ 0 5R\N}f*r Yq 7;01 v;MUBf)n l0d_gns~?'#JD!I/ae^Fr==qK! $E{糦mM@ .Lx2w6G!3kb%8WIէTɞ#|_SX]kshr7rhRv)w![ 88<6٠i[}FdY7``yNаw]p[$ȶ?hj"tvm4NTаζ_3Г63dqm^E#hs|FL JoL=! (M, 7?U[@'*η(!-Xj5})uC>F*i*Zq8< Knϳ!YK ɀsv&JhAr|999sRgN|O?S#s+>{L$N@ҙz=;45x7E܊ēnhHaHh"FI3sRxsPCV>0pS蓷 N[[|$,et&J7/T2_\fv+ׯᣝF GiY@20s6v .!B[} il2lI tgpiR]8R!N|>_9FBUN@JPߦK%6|$뜽1h[=؊h ^\׬GDBz' |7\FnҜK +>#`jtta͆vRCл{ZMuژpI˖xh9Vk1C]gU"/ ɀW~l.n Ej\jRV"DE:zzoHC]&ӥ˓`薚8@02wciVz :葹6Y88$ݒH~>᎝~ 7Cঊp7Pu0tݪG=Y1HwvS^K k+X{.Ἣ D=vwـU!C|}ŅIj/ ]]GriO(+k[*d4- pH.vmt:I'ր' N =,ք(z E(CH0\ /Cysq|;`E*.w-^_Zc 7 # wlNZ iսӽSTzcH|pA|-p}Y5%El/plX.rN1&eԸ0Ok/! ;cc4;9]0[]&ܠUbi"&Z՞t:!sA-bh(l@hl6|,p*BxeKpc(~ToLez8I0r#hhY0` QuVr%kT,ZӱMpT2:TFN6NPOw{̸kmgj!PZ8UIHʮ}sG-p^ԥ,7.# {pg.S2.r4jP)Y*:^S9Wlݵ]bH闔X!4v>?׿^nRL[fﷄQ]ޚÁq;u:gH?^_ ߂ ^<[I#{p7]qYCYPb1Z#C ᅵBvIp4wL(U+޿n0nStzo e*@<7鋩ӗ2/$ghF2$J_?~owG}o\Z6`<(9@$kp1 zj3N,0n8{e'D-fzƺh8QOn$\f8'HD׼n>15A.Fn#GWJSt,3Hq7N߽Sߜ;\%ekV0|A~p*LjVFhyF9Y9^+/;C f#RLZXߋo AӃY~{:`88ES{(`^o#Q#HkWAfcM'e9pͽ[X1 {SNE|K  vrm ^yaX&ҤK\V!NJbC- K? gqe\k0`:2{AG2CyѹFgzJp M[D im˱̰+l:LWllxl`\ dE\Z]D~Ď]7K5wH.$s!P<#GMnxC ` }\Ay؏&M;Ͼ5g_ yW9T2rАNXK~:A!`}g_=\0g0`aflmx;8|6`mNgqcZ:`IjL_2P;P8Eڸ~]GF 6eq`x fA 6'"P}::l^VIةёnUZLw=d:}W*Te`uSHlm:c+\UI%i9 AjS0(s5ΚJdn "lj€u #5Jdg麠e>#A^ҥ' 6Kn|xIMgZ*? 06ݶ9= w2gdILLny@zUnqs|"ȡ'*pOa8C, p5`|#\~^MW[ ~L` de[u 2D6˳CSx@A{h5]*%TGNS!|p1CV57JHnF{~4`ܡ6[l [ǐ H·g4}u|_+a`a;bAKCP-ҍSrjx̍:}=NYGj/iHԍH"i& ]gQ-m5aT ֦^Opϓqh4JU0(~7Y$8@ ~}󪲃f34ehﷲ2 )~ƿ D%8;p)LϠ^3utO {d`ql pacգNMutCZ' c-y '  _JdY =# EqaYǏ Isx Qp!Lo 1LX.g;Ώ9tR@Vn 9G#;mzN{JZލw7MX a Kr#@굡gt;!Z]߸K?&E*9^H3׏`B8$AonyU?7ڻ[V{K]߯38qހs0`8 Y޾Yw+^E(~3Q|x$`/XVnψvjʪo5p px%TH%#4i8HbΡxv)&s%d֝<ދsiW/M.'N‡|H beYB~y>gho ebَ\tj.;њ<}*`TE{cYEx`\| +oGwBz=H/:2Gt x=?em7m2O1:<J3Zww"ԏv7Yѐ_/\yiex^Fx@e#;z7u3I|~3Np-9*uIR(>s  +^ppJ=_v(˄[˒rץκͮsc^.9~WEx */IrWͦ\v*g-CʉUO 򱱓׵t$etq\Az&ADOkDҺʤ*>B'C ibVO Em0\X\'W v3Z˩ͱ o<yJǞ\M^]ndC*^ =E3W.(uN %mt?ص֒$EiF%bob׀Kf<7SH^ՊPӤ'd)Ma1 #-*~-i.+WUi!Ύ7OWtjn&.>P<{@Q.ڪG4}߇ D3_!( u}zyFBU6Tn 8HF)VlTvnʳTr M1|~/4eŊ=Ok֪), T%5Ոtlf*n`ׁ R室WK -`E|ZáNSr%k;&[d 8^,V; KW+,׮)>4;X(:R g5}})):[GA1`f&;rFƽv/w ҋ\Cjl-"[Ai)RV_j*DNtȺm p֊;U@ǯN yZ^f$*!;.h.;@)DfUge[%(UZ'yXD@LrԳS z9"ϵ&*qyV)e5tMul܋N̶c@1m12SthC~8SnҘܗsE.!|F^LzG tX+Ɠ.qV8$t]|ohS+,*tZS~hEХCd 7/[H0+X`v& JA!єGO(Z@Ȝՠz^e|5x @ZUo5d0ؕG+2TkCmAtĞǸI%( O+\ک@Gn;nZff緁&\I\hcҩTQβ}t:AX/ֻᰞ=:֋{@T鉴ӭ㶥IL,vGg@O* 3\'WSѸ 9ósD6ٚa̞Vn7 "̬$ "d%o;4S]_v GHJHAՖ TeS5G$@ N98B1 X[%cYOj?SzXW7' }ɠ=7^\_ܸt׌d٪$Wp 8>OJ]5ٵ{"9S3oG7 \Wl BfH~FDžiUɨ|]مb$J.*2/9Rb _Vc'&_= >"il| "m-Nr^}~4Ctks& D 0˺R<0>O|X5̧4`K\,Gͦ0|-=r s `=r ž (| TO2R}Gt( 0'ǟ]\|4WiD8mr\T&1V ƚ9U DWN5NH)o" ?׾ǀM%XQ-f<*~t N,8Jw CݡVJ&M~݋p@=c<; -Y~R!^8ꛦj.oXn'U9=&KhTV.F3կ҃OTZ*lpl$H!UF6`O)0&':CIkzFl[iUod8RM'K%|yVJrdx,aAg:ˠrL'hT4JՅOɪ{N`dmws@bj,EGM;l"x88a(gWgBwa!'G+&h 3H%d;qյAila++A Ea۱!II*P_h-tZӔqa]Ld#ỎYwVkGxF;|)DdDW/)Qt9dJ`-HDvڅ xN tuv0-Jiΐ%*`&ʣ@{Q !<Ó;ONEYŌf \'6ꠍGV;2c=S%܂@y{JSO*>߳^SWU+Ex@)uJ'\V$S52:㽎TO§4v 5Vi &P5;PoR`EAkBv,jvRŎYM__̢@\DX/8=2I6puckDo̔GS)X+v:x\CkwT!m' jk%(R٫c #%b-s1QVA!N;#v`]6ـsI` ;BV5'Ms"\qPpr Kƶ8D d/\4$5 &'L{15Q9(3R-FS"r=Ķ&`R(OiӋNlS|8TXN.K/||7kcE{0`#Sj&Erb0Q){8uXw~ml=T|`ce خfG)/`^)9$<_b=y'SL: T8eSI0*ڪfG:::&^4D3*/ ĘnaŅܮ$}#fxnLi^F/\H8;N;P:v+{vEWerkdEAhP` tW`Ysۮt4D8< 0&+ ]t)t_[.prFt_qsB/jϻܻQ)dF8܄f/XY p(Yq(N[9k0N: $D }Nq\%㑶xwW(MǪx\t[Z+5r>eC7 qjRo'bB0\,r0VL[8ħЛj rH0UI) $l8d}; NYG0Gvb-X*b墣җE_Z\MSZuF7 +p:aFrvQƀ=+1:;,kNˢP]!,}<)&8lKppoer 6"ik&G'l4I x]n$ͮ q ݹ\qxֵl0o`Mlpq7Eb 6bi$]9"2T%+32{/*3ډ{Sz1j{ %,{^[4X}C\ty^*Oꈤ0X?moj]&nv@\`w7ULl<+bpP9CP:VGeQ~:9dPGWڹ87CSdkØs?rO3Pȟ+2S׫)`][ #VB9dvߓĽ5EQ~׺:` ^oa{1j0i 0>|$Ӄݒ`nR젼m"{c`9ܩrfFҤ., ^d5z削NV 7^.e6 Mqҗ;.4`7Ui[VF)`pC*Bs𥷥|Y m|<yd(s$q݁$PfLd(N`QGN|e7 RN>)>ws^Mq qRݠꙸnHL_mwL9 0%4_7sm:EZ ;nppPo x88?sOzHΓ5|:rNpPb: >*0,lV͔=Yl66^j{'+=^hVpp\TE||!&tn{/͉eNPdG."\Ipvr{]fR+0nΜl@ɋahc\KRnJB{ol8'3u x:_gV U vpN7OtGюt}iY۸x|©=5je7&&JZen3S )$ AÍq{u6,n{fUpQUklO\r!JZ /.*dc˦&H9)jPp*cK0nc6%2rF7MZƠ͇6%OG0^j=0Dĥ,Vs. =Q]:r~8&ܣm?qCqko*YAR({m7粏X%X;s0 ǿb /Z-He` ljVBX8iB8JsـoB" {c~p !M}nl Pb:VmDmO)ភ|qFn 7͗(ƧCpJ.1߉ h|{b7}tlXsޟw +0f—nbS|'7xw6}tI!сۀ{Bm%-|Ïo$ӟ{繃tdcJJU;:QWE^o L'S?͙JT Ǔx3Y9 9h9e%ql7(qp/zcL}ψ/Gr UAcCjꦿs~A`Gp5|$E]d <~m oZx46ܽB6ڗ\) 8X)҂K_t abߏ"o>|+St)yw✕ ԌoҒt3m*$~F:Y < 0?uK3[-fG5v+0[AL`i4l019w] )q2}g6wb|) _1;EUc_'lԏq򷻡2gz^١df▀C  _="w2DŽ޾$LtE{Ki JAw*.0*d>[e?+&s ҨU?afҝܓL9 3S]Ox߾_wER6mx4tDxͬ%YßmLЌ0f)%t0UtOKOߜ]MK>N pkj&c-+Y=ױ);SjхNiQS_Fxijc?#i"d:k6_$y3Bnf=5]++/{*7_ #d8^Y(il&Mo5ݟq17{9~5KY-#Uk+/vh 1[=[pNj^P ܚ6a7n&o%7ח_[E7kLӉ=(]R/.Ày !rW `jꙟ6ߟ~˄0OBd3lNSCxH}:dW77[Z|L$xc(JbhPcig aaS~|\27ސpSfC{ҺƬPͷK<#1紨vBJOi, 7u6d{KH?ۚ} 7L0 O?L/{ n p Ba6:~[~ߒã@+; Wz=Kp|6Naq(A&"s~6ŗ |-—N; gfJ˓cM )u*ždo;&v \i 7 #u7*^+ѵ2o&"lR1~{}uo5Yŝvq%(p# 6JgRUNtMWU~?;9. Ú@&󆥴a\ q> 4w 4؍\O&K0]v$(?||,K O>>v7KhG( .6ɋ_W*7Jw ѫ@ȍK)uQ4?Di)nZn׸5BN't&;qX4;}xg̪]KpP+nZdL= MV{9~T i諚N ?(/Ly,۩-J}gS-B_&K40SoƗ>20Ct Gg+&\`|fݕ~)G0_3 Jof.c^B696Mf]t\9$PՖ@2q[c\d od?'iQ%f,J!i[ tb_0nXx3ZзpznA!Xh`z?_tC$8=ݔ W2|7PKK0޿/kO$feaʊ>& J:&G/SY#<6n>u$\m/ܛkNn|:wi|νߤE-{{EClvxoIox45i5i?vU.JtLe<!%rNVȉ"vpUC7&UcϾ)K3B}Lp!`[?7%:ͰquM}X,Agm0c,[y|ɵ4f*>|KO|٢*?'O='7=6nm s]]\E6 5?dCAkd>.R0X7+v碤ds@} >P<;^Q>__lc&wX 2RȉmM5Bݛ9q+EGn"ԖVvOV8.2c5=yl#խ@Xʲ<`Q]r~T? l0@2U!n8Bi_WUT]{yxXz!8ݱȹPZ%3~Q}X02d ܜDR})Y\\ ƭ^@1 <@G<^;FyƵ bYS!<[+S2s?>]ៅ&a ̅x ރ#,cnrU_$~WoXOGIݍ*ʱH0* J|1՘!ȅ}dVJ)|hncȌ\}4n#=k("fd;yDeA# r"뵴ŋ?5X+S'Uw'I[oa3@e:N)e|cBz^ &.V;S!{:ҵP '}c`oʵ(MI'\9 ]vɊ rݲwN+]V; t"n5)O.B,g]x|戡^8Ko ;>!EIJ9p'$P-[:`+*X웞J?/ûn& Xt K& 7馚+M P/uUs9Yϑ8.#eRk_)Lᘑ>eoŷ?SyIxUĔ)FGGF@ҘU[Dj %3-/e`ߗC q6r|5 &CwzX,6d=0Ѫ>p\1FTEIRXy8sk E-?5ve,}ǥ p^DLl7]e$vqibƝr{윬9>o-ǝQ0x󝝟zT=>8>8dJ #I(U$ ]%dϱXp:TQbDBQ3J$9/XJ#s ^Wh!'@i ܖ-?i[`w"ads9s&& $e[R J,rTEqҐ"!*kx_t蔐qx#%=/>;j •gFy(ׅ{de('N?[ɮB6:t }a4!8 tuׁ|hdY,W `( !zC04ba6 DZ1PCC(ϗ'`x?4ʞqZPU I (3{ T_]tS<l$RqXHXsG_N#W@_u#9sr3 k()Hd5LC&@5u^//fsC0ҩC'u (&%FwR7T(妳9`d x1"M F(:$4.JR5]ՑI*2_Z(\ vʀ= 9E#J %QLeFxe,'>osN^]w=d 5N5uI"Pҙb[k5oxQ[$h@ƛ3iX3~E552ގ%@I eEnM`cjuIBNdP`L7+C0h$X\{<^2ֲB[ yUkK#!+uhJLzѵZS+֠DIёjAVy?'?>MC>w ȱHmv!/!nmKרS0%ѕǻiUJZD&=YFPQT^S)ю[3,c骪酜2ݴFEd7rFGC#N(RN͈E>lNH|W5k(pH20#5++w K6_ }1@3(7["&IXO?N(7p"j(K(XBRۻa4|nPK LLoǫ75kWk ØZ@1UERM>K PjG~ր;}ƝuOV7KcpQE.,'UuPQl `0qNnr|=@-,W{=@IC•6@*| }VLJ{/ 0o8kScuU \Agw(ڳrR*j/eC&Zz:\;S5!/zKO x2\ e͟DkUXz]Afs' pvxX9)q"^wElp`d44@\676z/H(h. AB$]UdW_H:RH#afz*rimdjR Xu>E+DT_LzaXXҒXV TPLUD@&#TwO֧?Ӹ o5:!2ըi1^)HݰC"kz8[9KAM>N.ηax\J˿`P꜒ %z||,Okԕ(&rO?^GWx]fX"[O5<3Ir^/owB}HDڛi BpY>(` 6BzyWƾU6pKU G(^)O/kN@*Al뷳|J75c6m/1oOwkh pg(6D1pH9t*Ba*Ip4gN ƀ,o]Z'sUyw՞ XI%f KAa{JV:~ufo,}0 p+aakMSػ쁿.|:Pi/PrEغDG{:T: ۸>bQ'e|_|{acȭگ>f?z6`Zj eK,y$D? W8AiDPu`^%Bo[Oٽ`EV/tm{~ǏYxq h$ZкZ+˷Ǯ/Z_bnwl}Y+@l 0{(ļҰcC)$>ap Jk\7e[C=J:vÕvsv">=Y!#K$>o#.ۮ@iYEp}i"tEŏb[ kƯq(y=+kvIDuT(ȐQ2jVNMUgв]$Ǖ㐮~3!C`Rt^J$PKXgy@3)A3clSE=ԺKܑHl6&rW@ag3`)GIRY)}ak7j{v/dzKqMko,<| (sdj 쨾 BIfCJ9^F2G`%rmB㇃WgS: _n/JÚA w_NrPlf>rsF{h / qSjXbo{+K) Q JR vUQ !gwOo7i2Vwz'lBP"CYkBbSgPWrtmC/ U" _Z8X,vI^zBs2 %|ˉl%7{F@T6`t(qಞPJA=g}(9w^g\[S>)>ָ^+Qo^Iq9/MhrVB@;o~Ҕ^U¥_dJpv˵ZM_UU$UU[-`M" /];qe ps(4osۏ|> ǀ?N:&{NB^?Pz*IP D(LHcy$eea]fs@ⷷxEKd=xu㛖ލ [QGѬ %jp 0׶Ό.LZ'ZfIt5x$ -%? Ƞt:W=O? ŬUDI-Hpg2m )@ŏT|jԛ/ß }T!"XG,իTA#7:ҘoUlF]ZV0RkqS|@c] aDGg$I0$s*-wB(آE:~rσ[|GmBY- -:@*΀ [%S-U(ϣw74?߳z);QP)ކw\}( hB>?:zXJukeQQ?sӗ2#0 <8ZqSK)^{xMǛI/0`J{;!=Yczr3{c_q͋i鄳FsMw21=<ӵPJi䮚UV Sgo_^Gzupy=aEZ$JR({ڇA+[]'l2+95lFCP6:{ֺ#ddLB{y%u0/ al9 ?hSR {DCp!0ښe*^[ F M %$`6 3XO޵OzLz2dn8~K(d{(;mw,;Vve)k9YX{pdC*g'9N |@)F/"\S;G`\m^2"茓)= vJLvICbRz{DWQzռh怟q R]FK0> T0~=I=o8 RAP>vpصSuR6]xz٨$,XKe\IQ4tr(y>=F^)eogrLr: scr@}M*5qenBsٯ0nL)5m8U`#{\Ռx;>Zq'㐁**'R`Z0lh':n'Fo@K'%[!oyPz8t0`;5& BWT4^Aۭ{JS;K]pj>H:AZ.) ^*b_̢2^0c3|i) ,1޾ dq%_`:LIgWI%M{ ?P =F_.u=`  1 7p?{t"ֽt*Sm.):: %MV Vǒp%~2LgҒBA$i\lOgk3g˫' Y>#8Řp5c/0FCiDr=U-l*nZ`KxJƇb{NK۬j* ?`&x`I` )ppwk9'rO*|6dME"x:`M-E2BoH*hrG‰;\_ǀWIp)]<e* @Sloҿ*J(-EL9ʜcx╰sf]_+FQJ~c{68=X/8A$P!}06FJf6jdf:è@ՅZ~|V{42iK/Dp1RejHlE%R$J|ܧڸu٭do=ATLGYx&Aalf{n9~(Rq,9F6{kD JPlv 5F9;2{Ko;vX:' }wQKeVhEJF׷"9Nzxz.jCRwN2]kca>X! ?['iYT )MRq<ƒ(/s]Qj|<Ы'J^ﴬt[ÌgKDEwg&<886Җ[A f|c(~FY.UV5J7GGD#7 0kZK3m* _~YɪKϦAS!g 4'Ñ4N,dZ@P܌ w'yxw\W+n^utb`;E@o+!mln1J 6zPF@ĹI2ug?8 H}mˢIU?M/|aV%8g1IKE~p!C(lō($ERޣxo86k.eĘ l4gKKccb!~KY[1nq Ip0~=E{럺~Od-UatA'F8hgH,2%Ӌ>#.tM_ C!1o>c4%'mpIԚ;S}FyܺnWɹS e\qNzFc?ʴ+2~ΫI?x :r+Wu ծ_GN\fOIENDB`camping-2.3/extras/images/whale-bounce.png000066400000000000000000001075221427044526700206500ustar00rootroot00000000000000PNG  IHDRjgAMA7tEXtSoftwareAdobe ImageReadyqe<PLTE[fSvjzЌdմŘtHWeo_i9EL0wKPPrrٍwڥ[htz̥//y&P\c3:Bjtffh##'3484swyga6۵DEH9HS[TUYl`mu*jC%*2DڡALTP.sxḸ[q{s%w$x_"TP{*4=V_j#+8yJHdFX rȂq=enmm殢c[P&" mLB5Vuqp 1QDvhgZ@;0` j52.kFdQPS$LKP boou h9d..7kko eLS<>C [ \~~+*)󫫭 ~Ypssu:+02:Z]_cLPQclf)sIDATx[H\/Z Xr b+h]eyXBYK(AĜ IrBêA?&ް|hJ\1$0Tg^F"Ɋ'59ckͺIgֲ.;M"XwV? zUmK'ML$d7iߏ,NU儌r.B|Cr;2ݕ |B)bk(a$k0o@H2ߖgRߟH$_B^VB0iD)dbTđsƐ$ѿ"I qW+(dbe$.$io6^B@Jh%іTS' $a¢(KS*l_W9nز?CX |D/]"QuB:؜ u1vqgy} ᯞH9]7jEZ)_DXg'.LɻROY?%2$y"]?#3Y8?0E5~~r56A=$DJ8qOW[3 F~1)a,#t 2Dw -ÊFȹtwf;†HlRW|qGPm&erw T_8򭻬C.qOe +Îj٨XqG$!$ CSJbZ|1*#EY jkhr84fw|X1\(#K9C;-EΦQGVʊȟβʚf\j"mQ dM!lchMsH!=OsuV)uIB#)9"Sf}W]VmltGVB_u_uY470Q=PQV]=1#do̱QocYJUh-zYh"-rؘB-P()J0M}&M"r}M]ƁaÎQgDçO}@/&YH \߂`VRDy0dq(2足(Z!BS`EyPeBH+yTX_gNY, .~^$QxE8N"ئ*O,rURU \I$aeab*H"K% Y$_GbDD'ab=bZ aa"ZF;Nv97g9brz i."|EظɎik<|ӪPWpu)6V>(*BWee1Vf#23u6EW*&f<ɤweCA OAB\PCqfMEޔ"Ȕ+ " ɞm`~ Ddɷޗ1ׂ]_"qkL' ȳ$(AV,ģHƙCwTÎL^JH?S؊1x!$s%za" 4\%5]aSȬMءDO`d|1$VY粦>nf@|."flY(Ӑ겍+[s*3I3 pMئX]򥂱HddZ&\ՓQXwkǣ^򺽾:a8H",DcԼ2&,AhI]QU$&{Xf4N0+f*dj$v?t"1i[Bbu0>#'+e[YDBʝ)e֝ޑX7enK޺XDEץ䘼_sPTY"ȷ%xԹ4"Q p.n205IZϤ-[dDZ^˜_ t? Y wT`X 2^8; Cէ\<cAN@dqj( <'&r`H`:+14O E9e<5H-81%mZ YzdV`]2٤Q!Qh"@ AۺI>rxNS_]]MNm#>'1ingb 4rcD=2UE!$R(?& !#!w BlqK3w4yxSw!O0vŷ|mmm"Epc~~fx1r*KM QE45`֧/&E7$a49–߿2F$ၧ']ʿ(˄F[^?O`Wl}\,9Ѻag|;BB3"=o]w+QXP~jtUX7$=4\z42S Q܁"o꒧5G{!vYyoܻrE'~e/8_]+&'9FN77O7ol47yEV[9?fQL(%&lh# e 8s9❫bb` F(87T9?0>1;8/kpwE݁/n[vm;H[[$F{Sm- t$PeH^eC__6=" \fcDY{X0@#b2z(enoh(*G #♍!keoW Ë@m~[w>ܘXiA%dX\J]sΏ J7#(o'w>{㡰Ww/7;ÊP`n0Ϝ*/Ul퍜ƴEDJ2\'H[vF݈D2ؤ `{Ç?.Z{29m ܬo1 x++En,ybjww}t1 WFWQָh%g~pmeĂ }ft=e} (H'F%Uֱ&O]7***nT{+lr"o=Yh{lqyitU溯:${0@$uECGYaէ#F9QZJIQZvFO8\Z,s?;}@H{h9as g&A yͭCoRwDzChM1.DhtBr>tDR\>@ܽq<;n8*OQoBW7Zt.8Y\`-0x[-ƋaLc([GW"9Hz(H2qN"T!ݽ>6V4S1?MWR]I~?.䈧[ ׾M] lmmm[c(f./xdqwDb=kI$L9cxP[XV1Gyh(<˛a*bssa7p!; ?cfSff<Ѫ̵BbDW'T_2NM,*m .go@9m`> ^֝zZ@'Y%R{F8Cdf7#q{"m+8ov𕥩s/X@>{g$$ w;MEKƕ WFjCwheؘFxjQ}$UD";E6_[r}$0pZ%NHEYѥ $D&P0@ sWϭ7LgCCmghnk[`x0*}H[ۢtno;V_`7d#no<_^ [}+}= -XhV )">BhJ[]#(eg 2Omn}e1̌c~u#wGCs[A;#"6OQoͅ!M]0V#N2Cc2Қ4,366rUIZZLR*rX2|lwM錬@` u͛q-OE>ZWN9oWrsc”z8' _0x;mMS+<$,qF`eg=B(>u0/Uk-DVKx0-he駟\<:W? _%`BzJĴ<0800mir9ޭx`ldln 6ZjB6H JY`|k*ٚDR˪j:-T1L (b H*(o)w(GMɟi6Z#ErΣʉƕsYix}h469CƾKMV˵Nu C/CM Ym ŨR Y4q-]gN"ɬD@ fT$L xj!퐌LƆ|>_LOF斍ͥ E{ȿ5߷?]v9 s`z<+>v1'cPf,tp^M0S361ؒA: +r"}p`M3q" HPϽǃא7۽l(ZO4±z䌶7L9+]Ѩ+r&{Ub]` Fd-k#ss'%Nˋi;JgБYHT! WPgS3Tr098Q <"xxӁxl`||`|`j*dA֨("@ޤ),o-M+*L"P^e IHyaJҜpK&F}#HSr鮬E\ڋ-go  ΰ~/$Q }~㖙/Z%үiRr8w~I&ݢ霄m)<u:\vhqqU@+:0*ЮbAR jER ̆e|3!|f$aOEew;`hB3_sa$^P~vG6RSުS&dD}LkK+'eADcqnBsU=`{Ffhx}EC;\?X6`-is+dM2GZ*%A ͚,!%HrHR*~u #kO%o#ZR E.>oWY]Xp [xi BLH{E!%,}UDʖBi}RMJuD~ͷlN\>HVB'O|brQ9Q0,CY)1è5^`~#g!R17ɢȷ;͛1B,55~FT 7`6hnjs43>תxXd~D+qL2ԻܽTI6v,&DN`=bJGjFPzS)rRy"՚JsNjN::*\G6 <]Ku#lv?Gw~O+a*"=_@^}P99z0@?;>JTI[;{in  .8Pl4W:tNb|uQT'ňN W ;E;9dAku3Zu޽{r5jj ;?HȨ涒1jѤC%IzMbuf RZc*'!a֒Xj̺kUnR_']U)9DbkԺJ%<@TYwg:K?|!ҌCM͙[/tzfk wI-BgCz`*Ϋhq`  \sEY(]nX?=`65 nCM-x XfA0myH[ZO\SJ "!ԓjjoWbtG!:ykJ6njU-6Q`72xj$_ŋ"ҙONP5s"dq߻ /p*~=Wwv97${~=uL͇.xPP=\`υt{3f5^tHg3EPH}!RXM1UZ5IU?3 ImqX x  FsIwS?.v &1=!zja#X3b&`Ib0TlڳFL_Fg3^lR0ȧ<yAhYěn9)(^ 䝼0Ի';u˫ӞEX.`$`Zٌ}_۽7U6Ϳ"+kqgN8Sz0:'d-' Dt؃6B#M~&w0 !31#:|Y-79zy`I륵\(jS> -%de}k:5#i$vha6,VY!` F$XE?rȵN-C==y\z8WD}#"T@,ftAVeH$xg[Oc|\Q牔;{mAfpB7201GP$yˢיX/½{0=xygy[ zWeW"3#pV1$AK} [z35k4:4Jfn!܈s 10 :Z-uQ~v,|M@/)CP2hQv]`!%P>'|`4m9|Z|@*X^&v:5QTUqɎ\D: Y'?8dZMY=TphoccOJcn+wWVےHK!"w{{"QF&?m&;6EVta!L=,n:"Z$ ?bgɼ2D&n%`f 2Qc=|Mdn0Iڿ5XYtafĺf,?V9ă+=N䳬.;WTO 7OZ B톱w}#ΗWd+!X9LjJr*}incem4"Ք>!.X.`y6%D#Q<6y8eRw%RE"R.֜<4RGA¦GoEq^rzT#lLnj8 #>$3&جY2Ҷ e(-( ŢdEp@v-3ɔsP.KDҁ'f 2 x*V\!YJޓ5/ڈ P&7mz"?`D:Ək%B΃/3to7EX 0Da4}-vj H&jrV:IH 8kޢBl BH5ī?|s,m1m9kmyЙѼI΃D5Ք~55GCd#U1,Y&x&j'54ᬘh!k_*X/,ޟ)=\hx4N(GRU5 AeUޱ.D):7'2I@3¬IԵi^g- C\U|~l2A4_(wNxWZ`Fye{a'D rxù Ԕfhk`0k;`k3~ 5n2G#2 'aLh[6Dy+",H("I# '3hV5Wm,@Y".\rL$ OjPdbF*RE`XNy*oP)Ђ\30Ӊ''6e^_;Qő4CN1 @  ehk"kE ȎHkCLaK31b\AVU TjBq:6Ԁ:G+aoGR"\TYgut XUjڐ* @͐0G#3ơz`"  d7a 3qG8KUYի @[7!5 [[IW0  z kd+ڀƑuj觍}^PN¬*K ɨ<qDջy0i{0XVLV[DЃl֒V2,+6@vS$;9Z lB:U'.~ֺpiV"2BoIsf˻WulHu5JxT6˕3R"P3/Nj2CWemEv#yLO>(= l9!GvH(iA5U=އ 0V;{6nx̊x kas8v;ŚoaaaǞC󔈵r!`9\7ej'X!t`[s3@tdBMd-]+L K֏Lاn0'/?Sh(*b˃{8 b,cTMx͹_Ey&§o&"yG(j&ߊr(3m e.YZy==bOSnkYV^?Y>؎ ȔPQo*!RQ ('T<Go==.c{PF͔d3dM摝S(wu k9G('욗̱k@W"1гIςۓ`eƌ{zjtC쯻RBRs;!DOyhG}"kڳk"i"YC.Q(AA ZD$uPw/_!gûhMN :'WwE$=ޡ_p{ľS/lD[RX(':5vDF;+Dg"|7ju{(:4!(X"/\> * T,'e)_sFrk'QI˚`8S+ 6X }~IDki %KYqϜ$r.dbsDuVj˵!ƑRU 5*Iv9xN0"&Įu ;gL$V؄-XQeYA> K 5l!\{ DfPχĭ.Hs;>c+u`*'sݘFA.eMM tg[mwD=ggSuckw=1/ы5؉5y[<ƙ࢏ްaI|>k,.JP,?vC$Z΄T(؜xD4ԮPClB#~: a!/lXZM3sMOՊzIF$l2YHO^YNF*/Y` 5Hm3k9-Y uX<찞FX f#FLZUH,oZ's/d_zaV@Dy"$xe~V-FqZisHFQtq6\9iY2 R}(yZ0+@dT Pn:xƼʂ!lJ0D&ojo܄MWEDmj CL|O'Obm{@NZn#^_-j  _{TM~=^1e-9ɞHLU"+x["e2-yS*^b; $R6>*2H@-$XUuq7=k iIih+ uIr4ߝ/t'6!L[аb@A{@T{zUX8)/J#t]'^ة`J'Rhb]oZC9k{?SzaZ(sƠz2V!TUs/9B(.DJMmղHW93!ў@s؜h *K:siqo:ܙ 3K-$'i/$AG0lgHMJ!4]AX(.6OTgI5,i)޴B]ջF~¶|__3s31SQ#QA1dg2!)NW:R"%NK3E_؛ug=uGJ5We(»hlkw&-8_H\t;2(kV ZUo?a!!l`u$( 4_'&?+cw Cv1 ~19,ҏX:Fb$z-[-N$DHѲ l/گa I9|8l V[["Btq'YdLzŻ @1tO#ZGqsefHq Q$)'l9!Wd&Ofi@fkHwCݬt Rg Kpn"+'A$Ȋ$QK]Hh_Hq%6HR9&YiZ \ZiCQ(b9^uLlSQ,8Qp"uO; TTf"@Pf$9fŻٜ: g2TG 6Nnͧ݅L[>sRT2 ˅0)htF@(-GCMZ.<bovfZc6*:OTfCHxpBzm&KWR*F#\6*7;f);ܨyX/yK. nuR-yMn$ZvYΠ^N_bM_De{"q 3)~2 1,HV k x,5FawpUzq!7n,kw7,/\A7GNf[6OtT`{ SswB:hݤl^n!# Y\qRYT;IM?0#LˌV3V_9BDsr7JJB|s7ߔu?}֕ڒH9YR{ԃrP:.iM.~Y-BIQ z$gӉQdecPڟd Rc}…P7-Bc'+yb̓<' ` lqvHa\ @4&.37Qmm7A3`.8ub1mBֈ%e4tp҅N<&dIQJ$P 0ɵUU#a8'}r ##5ıy4gQur [(*6HPáwF$1*WΞ|MIP$\{ᛒI0~fD"Tr@%?V>hl[uk)aH);cCRYh \]HkI-5(}ˁޙhT#Gj}JmTr#EN1(j@$5,9#ZX,PfK~FڅvȭJm 4K[i?lvN"=yX.6v-' `sW5fO;PA$ uƏ bgVK }PYE~Q'tIv t$\$C4d3‡5yJ,ka &sZc e⺻C1&T#.զ H+MEVNZKrH0H(Wl;ͅS@?bn pYf(9.~rSt. ,gk=Md yx8ϖ-9 %%Nծ::sA}ZFssΰ;**zc~HM9!jZH3$bR,IEv6og WiGv2 9m@fjfaXLL\Cl0׹I."q0DTwݔ: (+zzknݻ_/o=n>.w LK?8_ .!=hqjx:S]IJEFhSjyA+4J>e='}&=K`?\aBl"y莢<=ZR%5ȳ8yRP^.=E!\aM Ń(sedb=H:!𧻓.{6CuuQ"r"s":2}4LЭǎ݃P!plcGOX֋r(}K^ɱ w1~ȱY`yww ecQf SBC04LvN , 6]i\J؜i0|W-!pZHQGg*&Euk&s9.B]kdD1oZD+0J $tZ8E<#4$r,~vǎ>BmUʃT D}j YIJ&Nplo3R緔qËGYI] 0q/'` 2!m‰QuY $$&+LrȱWEZ{SmfxJooD0rM*>Vtq7 zQ_AT -(@dyɞZ:yFЯg55Q?OB8g0z9Xu9I}T"$%2L=?ʁ°LoȾ܂2VvɹIj뺢$eqOV ̋Z_W#_1+%%GkONExzDu1y  22;lHf(ʊ$CK"eB 5L+ %=P(U%=lA/Vu 91(/`_5 i@TrXmr_Dp3bW6b>ݛ&a`JeEEG,V;uz{d/T ιY%ieI@ :Hc#[e!'LΊ`J$+rI@t( Ϙj8X=ڻ d9,YpVe*OeWj3ef-bCu kh^Lޟ_pmt8 gj\4{+~4tݩF Ʈ|J @i Y;gN EWFwv~\&G|,,Gax!;cl\JSsySkusYUUF$,"`{:yrL65o쌵9İ^] UXYPy|{G" @Idb};EʮbᾤzBv5g+v gY@Z--IR~p _* 5&j _HY@}ѯ^yz-Хi)CXŜ<9 w=>vMy1?(Dˤkj7SȁTj;K1/-XNSxH$g--pRcic oޕDRgh&a@vH\AXYLs LIGZfVlȧh;9'>~'kOt|BKM>okhA>xG^2XQ&È kP T#TR.S5CQFEG`;5 v|e2 zXW/I͆rdȾ4J9+ISr kйX։$ӌ]xd 4L+W~9sD4rUO5:NB#[buBmAXy Y lt9sFVQ+}w2_)Eiz>ozKśK6.Wx( w`ðhy4Vri(IaRZpb݉6rf!O.;YfMؿdttj8X8Xq:'VB#;޹M:2wNƃ`*T)U4ǰ(3\kߖW^gpS"NM*Je Rb14 WHؘځr`(PA/CKfXxzf(|S>2W: M/Cen3ei.ulfV|*}eC'f~1 U/WKV5$Q+H܏@lLf>ҚIIDUMAn@35v]zobs6n7m_xDێHP;hpq5̷nu/ tO *ӺD(ܕJ$*ڥD-E5=+*3': ss1싥C.rK+.TL- {JiŽHZ`iSY üpB-P"oy0 WrZe}@ O @'9)EHGR1_s4ݕNbb^!ӨwZQRqdžʛ*#Ec=BkǶFH5`-v ӿR Z2mn# ]ndX7"7GV̼"~~4H . m> Z:^{v'H`do r]mXYY!icöɳx,kkX gso4X g(1Z1#s.$ 9h Mr1QLu+oW%ڒ=P!H4@{.Lq7AL.(ći4K zW?^\}񂂆:zu``j`M`,ҶH6&A3#ss"ȫ>?Q\帹ȼAF=HTos%A,2TӰ<ڦC4S=]W M'm"۰ ޙCj|m;D)%/\Vܺ|T^{`ԔoB[/ F# me_ejƍ w9:Z1sՏQ! dd:CxDI *B/>v,cQ)MVB Lb#Sz(qO]A@%Njf$ B-nxe+O(ݘq(J3}u *nAEh\ʕ\z\hswj;}Eo_0%@j`H+z;I$h9\ hSwvx/D4Ӛjj9EI> *Ju0!B" ESiXۘ7J6Pm1! Nw^RK!\유9 fJnԷܝe4ud2YoCo}+>xb/9ci@M5wqb;TTbks[upWDҧ/'+/ 뉔M[!OrȈҁ0Z$/8(; L="q;pć$WR7ч#,(璄YJlݙbpbZ6-ج}*yCyi%X7Hy1y}H&CSׯ'dil_mo_xu R,ɡʛHDgK9H/u68  Mp]K:jY;uSO wʜM*^\-|mˋbEbb3 {xcc>w@訳rr%;8O~z:0TFF= $,؆V/ww; @|cN*12f$1A3.*X`vio][T>td;R|"-Cϳߒu2F0HND`FI3ZsIuT]TWByp^_=k|nwhi~>Jm W weLExu4+ T mJCnm1( (0lD+W?x٩?b3 ADb!R%u}abX x"iPƬ'b]Ų%6UV塉hRxO&3Ukcg_`G">/+&>ds Iԣ;tL5q|`|k30X%0 :#ki<؍P, W-(j5&xaVG= ɾǷb؜0T^XXXu6vzhGw<3V,𱻍|4zS~I#f10&UN+_U7F;BHlXǁ.|>EG,K 0 +Bes;e O|\rne@sw # :CKNݺwW&>9?OĽVfJ/0 LC* /36]Lr~AC*򻴻cҳ4*rv ͖ -,hDJ&.c0@ zOKBG*Q{!lK$)*zm8HD1orR*Νޅ9VpO~F뎦S@ֽo_X=rR9x҃U]jMWˆ$'dL4ObWQ1Z-c[%D pG|qӞHf89̑RBa&`Ef $ڡ(S]zw ܆FZ`<DsT&Vv7_9ExrDQAH* j^Sͪt䆚W])~Z'jbK&q^MR{T t6R mV=:nz@NJ:<ÒHWR51b,=V:T/wH`I(Q_$2Ծ;{ߞ?^B_|@ cN28PRipjJ04(!R/T8{r[J8=KVNمٳYB(ݞh{8ۛ^i8qˏ_oߟ[{1554v۶#Z[PFoWMpGU1PymCW7XfDPߕP.}W-buϼE_|n^~gDxYFJLJGS)}ֳC=y6}+:y>Iɓ>|p|9Y#CCi9;x cU '>|ҳUBsimk~>[_WgF_d ?~d7j6 6ϸ: vLNnT*S/~mڌldG'x/ vߝ_X^,R?%s{9E@vo~`sr`܀ܷ3Y:Q^ 7^nռҭJκ*?e/n}!2ixTT-cLA]Ul!YZ_Q'tZYzHtE ɐzPoQ]jñfIy %= %YZ$D,C*AjW/=2ԤئU&xy˺Ծn|m99_JMRG|ئ8%S*&xƥLhnԞM_IUP|Ϳ7z[&S.MbMS#R#Kk4 WޥٔCX{tK͎pҤylvUƾ2r@U)~y_£Yxw4 uJ֥ŇԺO^z+v`FkDӃd8ʩ+_ ]z! IIkiuSWFC De>Ȑz[,x9i$uIsOiԙ,U*$&qzpG!z}Zko3p:^3[=:EB S >J@4`ٙ:"aIQ?< ~+_M,D޼]0d!X ~`(Ґ3Y/_L6ᘆtBV}S$Gߞy!|EV$薩q~]qA $'Z0OfH"  7i$xlM2C=:h̠&%-T^J 4A+'uo 8yz>tK|`&A2C 0N#hyV#qaVQ5_8;{w=}Iot>8 $5K$ls$`tV } F0 A}RV$Ǩ?8id恬LSf1g5 4#7{Sѝ>< Fw>E&s̀.~F5GW!R~TdBłryzi26kcuK'5Z<␗RnSwsIVep:t[TJHImX{qq[3ϵE@budH% W }2kssrgG,~u[TR{xq64EAJ^t"BJx@zkzGGG${wʐJ64wyfT/~yيjMG~[T:sacQ-v1NjZǗa %UCpunܸNzeOT2]jnO?Z@UgwU2-&,u ޹NR4IaԜǦl(OuːJ乿Y;{O68ߥIbDJB4WW'CL$W7rܒi"lxe;<*1 GáH^#CZPnKu}PFHv2: HNm8ؼj[[ɚ4c{7\lhٛ<:!-ar&cg#P8l2oUzEY %=}ىl9[?b7r Fsdy ⅌&6?&b 7sR2ur<^ bWkrorbluө3ɪK0 UDVHf̕21sdHEys- cZ1g)/:xǺz^ $y%8;j9I;xH;v,:'!+%B,]x3Ne[c ;š6XUiB!u6;Q!`LVgn偱ݮm4t䡱Ҿ *ۍ&]BJi02hGGuk.Պ$W-d2LWU]Ŧ'LDSӝQͦv[tǺ5Jeuh&@PDd2EkaS⬦y}+m@RY{.\jd ^Xw<;^RnnDb1JXSd&KKIIC;WSMǏ 0HC]7ӯNJ±s#8W^㿙ܗ`"X4:ӤT62 k?y}Ep]2Hq懟dƇ[?iJĢ7n4&d pKlWjz6<ܴKFQ?G?y;eH,ĿxS׮ML Mml~=RDuܳX:H@[eW?o||u_~~ eڏM%4U7ީ?)CKsDG]WǀY;:kdHnp7mu+x>SI_jGvwː#ѡ,r<:Co R!ױ!Z..;6Ay~^EJ(ԯa>ͳ2$iOul,{=QratlbĨixd )_٪K(M`.Y,#Md5[Hvːr|[ʜ|r*C}|lUf+=}HOyhJaΚ"цwvjZ5)C!神PGuBۧyϰd:h}×eIXVYq>L&:i'uOQ7x\r %eH$`tsh>YuZ`Ѷ]wOGӃAZ4hhv7}rdzr<>.hN}==S7Z=q28։hku(/DS¬*W0NykQ]%R*2QZzZ:sOHhHtₖ&gDVO}oo'sWdrE{WM$EdHf{z ɔv|Z:*CZ6+`s9/YnҎJ~h9B͞(2 I I®J"Vɐ-  F&RxtOiAPy1ɐ H%*P{8߳io$C* 隟C3lڔ%C*k~zKʫK29!͕= @IL]S!- м: ;f(Om id] ̫0bANITj'bTwz waNITH-Aqnw BIGn̕2+!`tRU8A^7"! OU +@P 8kDr\M QBZ 4Jrv pJv"x^;ʑHy )ׄD x#ܣq^0%, pvy@ rN 'D pC.Pr@p01NK K92%҂jeYn`0ŋ I, RN0;HC bmNIewVQr eHPzi:A^<"('qXbcj5ftgLI4 A@\Dy qZr@nͻ.CV/ŅYpQ@,Ub8GRm @ ni띜Soqxe)U8œA`(%w1cI3Ba(NX,T2"t1! PA'fO9jPdA0r#wdip9YC R!Z@ͱʩqY  =qB1a$%ÂrA% `h()U"Kad ֶi;Hz W63>Dx՜ҟCXJTX9@(S,ur$kOq@r^9+`A`bq#3HɔT)u:aYIќZM ަPu39⢨鸐T;:-(0#+t i&!meae Tt6j,@85+V'ܴo E"/DY()U$IN(u7phXC^tq;HA$PX8=NVGS=cnK(|UIR@/3ʢ4^h02^7p q2sV.RmႴ7lAaIAĹ]XՐ$G p$Z8)`򃩁3R:,zpNM9 WAj}K(@²%^CŋpJt&?;DX!m$, pBr{N ;G(@\IirA}hΒo\eAJyY u@ ~8m#:r)= 8%XaVS\m7|:! rmrȤ/B$A(` o mT)ԫY Q%!A_9XT7,Iv$NҲy"U 2ʭB8l !i(`.M4ՎJ-e-nXCpڐ+8:Ϣw\ЉW"D]v 8&R\ \xvVdg9(!Fݓ#^ #]k))SA. :@ ,a$r@^j`  9'a;< dDS3`،:(j[?d<:+4{)$@ 3\VSJ)ZbT Bn4ꊻ(#H\> 3.pz3?#r".,N\i D;4 XP0א4< p*Kc f "'T!ۖVYNIz"*RFVWJRD&j)?Uy 5*K*R"K U ݣBʄZVV?NI&qUujH0/\U]r'T1;(aS-ꅔ{wEE;2qByO o!h-`2ԠJTi[KeH _ rHiyH2EZt:! Af W!pN8tɧd[$Q2)) I*%C- i>Lˆf* RjHL$CE$CE$ I$ I IRB d."e^IENDB`camping-2.3/extras/rdoc/000077500000000000000000000000001427044526700152445ustar00rootroot00000000000000camping-2.3/extras/rdoc/generator/000077500000000000000000000000001427044526700172325ustar00rootroot00000000000000camping-2.3/extras/rdoc/generator/singledarkfish.rb000066400000000000000000000135431427044526700225620ustar00rootroot00000000000000#!ruby # vim: noet ts=2 sts=8 sw=2 require 'rubygems' gem 'rdoc', '>= 2.4' require 'pp' require 'pathname' require 'fileutils' require 'erb' require 'yaml' require 'rdoc/rdoc' require 'rdoc/generator' require 'rdoc/generator/markup' require 'uri' # # Darkfish RDoc HTML Generator # # $Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $ # # == Author/s # * Michael Granger (ged@FaerieMUD.org) # # == Contributors # * Mahlon E. Smith (mahlon@martini.nu) # * Eric Hodel (drbrain@segment7.net) # # == License # # Copyright (c) 2007, 2008, Michael Granger. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the author/s, nor the names of the project's # contributors may be used to endorse or promote products derived from this # software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # class RDoc::Generator::SingleDarkfish < RDoc::Generator::Darkfish RDoc::RDoc.add_generator( self ) RDoc::Context.instance_eval do org = instance_method(:http_url) define_method(:http_url) do |prefix| if RDoc::Generator::SingleDarkfish.current? prefix + full_name.gsub("::", '-') else org.bind(self).call(prefix) end end end RDoc::AnyMethod.instance_eval do org = instance_method(:path) define_method(:path) do if RDoc::Generator::SingleDarkfish.current? "/api.html##{@aref}" else org.bind(self).call end end end def self.current? RDoc::RDoc.current.generator.class.ancestors.include?(self) end ################################################################# ### I N S T A N C E M E T H O D S ################################################################# ### Initialize a few instance variables before we start def initialize( options ) @options = options @options.diagram = false template = @options.template || 'darkfish' template_dir = $LOAD_PATH.map do |path| File.join path, GENERATOR_DIR, 'template', template end.find do |dir| File.directory? dir end raise RDoc::Error, "could not find template #{template.inspect}" unless template_dir @template_dir = Pathname.new File.expand_path(template_dir) @basedir = Pathname.pwd.expand_path end ###### public ###### def class_dir '/api.html#class-' end def template(name) "#{name}.rhtml" end ### Build the initial indices and output objects ### based on an array of TopLevel objects containing ### the extracted information. def generate( top_levels ) @outputdir = Pathname.new( @options.op_dir ).expand_path( @basedir ) @files = top_levels.sort @classes = RDoc::TopLevel.all_classes_and_modules.sort @methods = @classes.map { |m| m.method_list }.flatten.sort @modsort = get_sorted_module_list( @classes ) # Now actually write the output write_style_sheet generate_thing(:readme, 'index.html') generate_thing(:reference, 'api.html') generate_thing(:toc, 'book.html') generate_book rescue StandardError => err debug_msg "%s: %s\n %s" % [ err.class.name, err.message, err.backtrace.join("\n ") ] raise end def generate_book chapters.each do |file| templatefile = @template_dir + template(:page) outfile = @basedir + @options.op_dir + file.path rel_prefix = @outputdir.relative_path_from(outfile.dirname) render_template(templatefile, binding, outfile) end end def generate_thing(name, to) debug_msg "Rendering #{name}..." templatefile = @template_dir + template(name) outfile = @basedir + @options.op_dir + to rel_prefix = @outputdir.relative_path_from(outfile.dirname) FileUtils.mkdir_p(File.dirname(outfile)) render_template(templatefile, binding, outfile) end def methods_for(klass) klass.methods_by_type.each do |type, visibilities| next if visibilities.empty? visibilities.each do |visibility, methods| next if methods.empty? methods.each do |method| yield method, type, visibility end end end end ## For book.rhtml def chapters @chapters ||= @files.select do |file| next unless file.full_name =~ /^book\// (class << file; self; end).class_eval { attr_accessor :title, :content, :toc, :id } file.toc = [] file.content = file.description file.title = file.content[%r{

(.*?)

}, 1] file.content.gsub!(%r{

(.*?)

}) do |match| arr = [make_id($1), $1] file.toc << arr '

%s

' % arr end true end end def make_id(title) title.downcase.gsub(/\s/, '-').gsub(/[^\w-]+/, '') end end # Roc::Generator::SingleDarkfish # :stopdoc:camping-2.3/extras/rdoc/generator/template/000077500000000000000000000000001427044526700210455ustar00rootroot00000000000000camping-2.3/extras/rdoc/generator/template/flipbook/000077500000000000000000000000001427044526700226525ustar00rootroot00000000000000camping-2.3/extras/rdoc/generator/template/flipbook/.document000066400000000000000000000000001427044526700244570ustar00rootroot00000000000000camping-2.3/extras/rdoc/generator/template/flipbook/images/000077500000000000000000000000001427044526700241175ustar00rootroot00000000000000camping-2.3/extras/rdoc/generator/template/flipbook/images/Camping.gif000066400000000000000000000247411427044526700261740ustar00rootroot00000000000000GIF89a3f3333f333ff3fffff3f3f̙3f3333f3333333333f3333333f3f33ff3f3f3f3333f3333333f3̙333333f333ff3ffffff3f33f3ff3f3f3ffff3fffffffffff3fffffff3fff̙ffff3fffff3f̙3333f33̙3ff3ffff̙f3f̙3f̙̙3f̙3f3333f333ff3fffff̙̙3̙f̙̙̙3f̙3f3f3333f333ff3fffff3f3f̙3fM!, H*\ȰÇ#JHŋ3jȱǏ? TG$L@\2:{`~g+5|BIM, H = d ўR 4ui =4yB2L3ZaʊCΚ Ī"lʓO)RLMbJV R 1TM*K{3?eRn-hN m[hp@Y-&\4ĩ"=Zkd%fpQ^ر%{7VȝG[57y·֭t]g=U!Wy֜'x%('\pvU^T_,&X{x\3`7`%X"ȕZbjDvVy65,8)W\rX%qФVuML |\) +g\Msz]eZF,b^j`p2:5W d M [[{<Te_iy'䊄h̀Ҝ يuWUkY9"˽9%4Zq=byN:nRɊJحRYa]ZԲG|Ι颜!VLzSIxXضT+b+&ն)cECUr( dYY{@wp{ rma RWx |tqX<k Mʘ/iLR=zwc^l+2A]-0C3u 4D1,Wl=),_ \)}ԹGmrl{E3 R4М}^\1tWHxX}dsJxyy[y .M 4 ^,93D92'2Mr0ⴰwUOHka^Qx1]_9 dK9 8ʆ?ͬfWV\P1ݮTEYL 2MZp_Pb#w6,4laZ\9ax`=4=R)(X! 9|ADSu@,S[v<غVr_$ 1<-Ӹ3(TUCE1Myz. 30 ix?%kC~^[d5agH3GIx/b\J2f'<p Ke$׶2RE" :q6t*AP'1aA\<~kVo:Otu/"\_m IAIK:q20l*Y({" kyTekd̨m.s)/%E Fh%*JT~gY/|Dmebqn 1UIu Y*[1CΠ6|7+8@ݰ/lj:xs5C5bg4SS-Dw_KIXSD{4NٙZfx7?9ռԌP͙ zh!Mp3E$pPNM+xIӞ. qی8E~ӤiydB 'k e uy=BWW8gh꾉O Kw 2PS 1}b/"u:Z v S _mf6D11"P'k@ 3 d&5׃ȲQ%>1q;E" Pe~U'=ߢ06@]S-<1*w;%R +qS!b>tc&#"{XDoh}Q}+/!,Wp XW%Ȳ~!r lStrMN!?EGP1"kq xw! 7,pNӐ Ry7 1UyqȦ/"B [.I:+%}"i!W C)'#q+P+MvVUBʐw)lsVӎB&PQJɱt!W!7H A$k}[Y ҃zɓ!G)D j ywǕjl`+1, ;=ezUWY9!FqֱW@VcDOyA K8<ɆB9} E2'XO &DͶriU!ҕjz4=hR),]\]]`1Y}UrQaw9}_w;5bvQd1%w=hEtnT-vh7xq^wLwyC2\0٢+O`[#S " >T/rVڄpCJQv~8hWRфh!yaé/1i21/| vM1($΂ʡ0B. d oF  "q;@!eaUИp\90\ I-!,P`P0TA' gau%)"J P!gTJiD$,y!%b61ࢋR!+P5%2{UyELאؘV,4AWQxNd5|]VOeda N$UǮ\m<܍ܩpI˴Zfm+,X$ЩVӨ RynTrc*WhT*]\  I R<,Kř,ɍy<";qv p+2bY}ʝ:lí3ZV!\ǩk0j[l7XK1];|1DI!"ݫR Jtu- q}Y1UNP1<X|$2ɟ]l=lmq5C #cUV JՃ,cڏ[-Kzgz=ɱ ĻΏYL]ØÂx)\+eP1)`]{,@L՜>dmNVinZZMnPP:>ɝ>ɾ>M~ͬu+U".QT=,ɻ^+$pI&=,+@vZ=Φlp4;0m+Z,O Q! QJ^)<Ėlӎ;eDm>ʟE( H߿߭LY2ݔ-+P$PIn q!r!'G 5O@o+T S~/1CoT{Y<%n5 a,!"pm+RwiM %|N_hM/ ckJl^5 nN;{1OP$O1. } )`2PØ~ANU'/+N bJA+2p  A++ Xd0FD+XX +(qࡂ81"'1|Rb*K,kXat^|إ v G+,d!PrYU^e.e<&jY (|C-`Mv;MӢ&Lۉ?T('=@(zD9b" (V,%2 ;p),4V:(%) ChZ7Vj" :zС$K=ZJDQJӯ"V@YM.`.0RaHУ=#'*z(kR*($vB" 2q& (22!SNQ=dH@T/Qղh*i'+B*PEVRSM{&O {H .b,`9 TFز4h1l=*z)1T+V)Nk7L0hRkJK+X9v-$hAl* bZ /sIH9:¾hp'= 8dͪ@&PD*hfA@VLXLY֎\Į@ܒ,FGl k""SE!B 4i pbU@*%K15۬{:H$Wg 8i3Pb4o 踘x*֣@c(wdžokBd(h"Vb(FkA^C4=M+2I{&(%$! ÚApXaENB:"͂Pp֖!ѰBnR(y觕V+~UA"3M;j!ka$F0 Q#†ltT*K% O#^c7<_lcC\!h8vY`P"P M;jb:eS3a8≌X!c{C)&z@,1(E 2J#, dZujEE2Ce/yip2c\36ǂVr$Qr*V|OI QL D4䰀WO,X eA<d"p1NJDYA9ل $#hP: j$B&~*A"sHldW5bVXFD ˏ4X%@䲊* ˾hd$j:R){PV̢*}M30cvI&k*(<$aˋh Mh]iV$>&&RPj2Ft8WQqh8S.H rtLȝ,2bg($9*V(@t" a 9\GV RRQ~WlAfE͊VBOjAL¼(!y\(#ǫV qD[U!d@50"gM0d6:r 5/T2ѠgxRxN Q[)yҢjT61X8Gn:O|W? Ԟ^h_<,$-7J#1j7+!…'+deRC_qg 'jkega-Y*IZ3 * E0 )qrB؂ Ѐ 4D)N:Nh@#ǂFX Ys`aXt 2B;|I\CV( eLߧ9yE\'D=3JѐFo~JZa )NV./ ސ&$*Jºc)H=LE'vTPz*Jq)tnؚ:MdN֭.O A?"U\d)..-3&wpbwU[vK zݩkU5!RgE {]VA {/IGX'^"]m>7QN ua b`~mIQ%(`Mɓqp>Ai[жxBV06DNjE(/ZΊP,,ejdz1Ƚe^ ~TPs~pC-+4cW{>lck3m>896p.IyX x+⃂< hSC֨@l! 0 (˾! ¸+?q (@49`/:pMb<A)كQj =Ïh8ؗ!@5e5$$:#@ 8*9=@2۔Z<1U5,2tY< \ȉwZ!鉠=2 E##y|A8zQRH;Y4cDȄ Ӽ,يCAF\'Pq) PE^ ۉ#2(L "PA؍1:@dA*;љP U,+>4YL9((G U܏B>qpDEZ39_dܙ+>%ix 8V $ DE'xX#+ bMA O9QI+@2F+=.P2I+z;SPS"xA=Њ4>ͻUBJ =@G -UF|?H!1,JbbM6kݝO>۸<$X12Ph; \EX+VxSM :V׀<) SX ۛ?#@'֡K 2R⓶М*5R YPMxA=Lx'Vİ̡\K#! Ra )\[A3<LU0&j4 ya͐aD+`:TkHpqCQ ndP_AO ƿILt |V`=xъk܍żx@婇~<&H8O 6Gm0 S8SӐՊ( [ٸ@՟U X|[y VHXU Q MҒW,) =@ůLH$鳍ZsƑ4S;+ħE$yNI5[x]-sSg-`ҌÝ#zOL\SA_y[M)f!@Uf:}AUU[߫URA܅QŒX#tVL糘+ɒZC ߠjcm:_T~/p.+Rɡ (jj>::ʫЋnk`LLȄkNl^lnlR;camping-2.3/extras/rdoc/generator/template/flipbook/images/loadingAnimation.gif000066400000000000000000000133761427044526700300750ustar00rootroot00000000000000GIF89a ! NETSCAPE2.0! ,  @Rihlp,tm#6N+rrD4h@FCjz]Lj]﹬R3-H$wPy |KI\K Q\]PI ~$ ~ J %:`@ XP@AO 2|(D**HE}lF=$Ƌ@\9L J()? ``C=4RO@>}5TDU^=UQH~Xe֫:|AoG4Q]V;ޙ  jUzҧ]$jQM ʆķ7721@ܒDH;܋%ЅqۛXMgNάgMWz!`dѥ]uja}a ݸ6;5լDzTh,5~z~@y[:3M7gG]q=@c5 4u흃5__9_AMtB\K ⵘ}" @lDiQu+baq&{#'LHjz"$& $B! ,  EI@*PR*;ò[Ll7DҎ%GI0)mIԲ`I}>ă}-RwFu}y|O(p*"rt{RA>( ^( "4 333..Чɞ v.*o 3u zz( D.z Ha рP*6=%8A%ϤH]JQ&2Lz녆Aɔ>50j( ]T.s *I@m:nTx^7}v(Hy!8/ 4^ֹGi`zA"?l8]'STҧX*3@XMS<@蜮-./Ay5|Uv۬ϰVr4<A:\4bmdvi{˿ӛW'7naq{"8V_u_^2_[koTpbp :M skx_"Vؓ! ,  %R4 yԬԢ\K٩l'#QZ0ՠVTyïe]U4f!Pc0zkW+_ ^ў`v"Ŋa@x:@O,ȬtK5zsRtH5y3ʥ>yz=3[/Z|&x @7nGdn B^|{ZCN3Z&$p{f;^C}Co %! ,  %$5,%:k.]#> B"ZRp]2Shk9E-e<ͩkGj6-twsz|mOQtix41 ) U"  BQ B)# B+u  )ێ #" pg߿=QǍPa Q*֑Xb<88l$Q,? y2=0յT#rΗ5 9`jLmgTJ2MWy6jukիKy0iP0JCg&(`oq]혗']u}C*  ^\sxaGtKpeÈa@jvlgVDz$ͣQ>mb16}4 @l`ѪzvsGt 2y֐/V+SNJ=^"+Y  09ڛ>0}Lj`abqO '8^Xd|5 [ahWY7֑W6!i!qHX&(@3&B! ,  Q$@RS" k; S]3X'+ζS tUik `m0c]wldt$v}k~zq,lryux?)B ; 00;+ ­G 0-g+ .z%$z +l(jx9hNt<; 8O7yxH#|bH$ѼLipȅѐ 2"-A(P$tj@{s(W$( =!u5(jmn wmĸoA@w/d1+:'l1f#|,z20Hg@1}թٯ[3S+|7Uǃ.{ P卵0ʶc=+PL03:~p'v{On<~)@uP6 k (àK'vK V`I9(ZLG!! ,  PeRN*',Ԣv*o<bKs M3k>oR sAE1fm):kvvM{wl rtqPm|uF~?W a3& A66 - 3- .,6o 3/h-o 'dzz&(CGo߉Oa9ќD= kOOEuj(ǑLʏTIR&K.H= ttI£B0E:qjԪPhY‰ ʾBFdÎT0@ܝK!Aݽ_aw9hP(!Ѣ,fʓmZ,rƝ[|.1鑣wZh%.:^mRi綽7V޿}c\u;o>oüu+],Ebd]/\.*h3;߯~ |HoXg6] !! ,  E9$#%5(,Ԓr(i0.݊`lXtD%ydJ6H^.pl[k=.˿(>Sx~gypv}lNtjx{e> ( ~~ -- ,-(Ĺ ' e -/p(u '" u GN šL1:I<9uG '9cɕ-Q$L'lD7UTTׄ tѦBU@%JJŪ)W?LSY bZvmމБ]xF0I{VXg|-BfE#|.@4OP4u8S g ֥_n;Ө՞(բQ)wǮ<.(ʳ2?eG|LՅ"x/-/y+,p=E1 dQٷ~2Kꔛ$r `m*YsC zFn ak}nnI#Q! ,  %R4Ԝ"CA,(~ں8׼دDA֐2hs݌+p]ˤObKw8^>uz`H~htkv"xm|1Z, -#51"1''[#, ,}, ' p#h1 w a"w #p"b1V-akcB)P@;]XqČ#N-A"߲SyrL4eMʚph\@eq/o D2(𯘳q s:gu@Fu+QAUӯZ#xbR4vG1 Mɻ;8FP`B۩3he11O~ ˟>z(C3A=\Z-ڇAa-qiSRYmG(1;Ž=dtqB[! ,  %dihlp,40T:Ԕn琘9␄\< P)JZ+^f(-N Ǥ4ant#o~$z|u#Svx@"'% #m# S%  $%§ #ְͽ$& A"%  $#H݃'/9BH EdOC)Lq#Fo gBbDIʤC'CRg̙G 4^M=F\dd3 X堙Kl٣̊@@T?}IVUT+ol|gr TeluӭޒvG]l<ݑsg_D )4EQ_i)Pi#!h Da %.@qMOQ `pnyHWC"$s* ]hp$V 8 $(the section + the method body) function m(name) { var base = $(name); return base.parent().add(base.next()); } // #class-something -> $(the section below) function s(name) { return $(name).next(); } $('.source-link a').click(function() { var link = $(this); var code = link.parent().next(); if (link.text() == 'show source') { code.show(); link.text('hide source'); } else { code.hide(); link.text('show source'); } return false; }); if ($('.ref')[0]) { $('.mod').hide(); $('.method').hide(); var hash = window.location.hash.replace(/--$/, ''); if (hash.substring(0, 2) == "#M") { // Show the method and the section m(hash).show(); } else if (hash.substring(0, 7) == "#class-") { // Show the section. s(hash).show(); } else { // Show the first section. s("h2:first").show(); } // We need to scroll! if (hash != window.location.hash) { window.location.hash = hash; } $('a[href*="#class-"]').click(function() { var link = this.href; var id = link.substring(link.indexOf("#")); if ($(this).parent().is("h2")) { // We're in a headline s(id).toggle(); window.location.hash = id + "--"; return false; } else { // A normal link s(id).show(); } }); $('a[href*="#M"]').click(function() { var link = this.href; var id = link.substring(link.indexOf("#")); if ($(this).parent().is("h4")) { // We're in a headline window.location.hash = id + "--"; s(id).toggle(); return false; } else { // Normal link m(id).show(); } }); } });camping-2.3/extras/rdoc/generator/template/flipbook/js/jquery.js000066400000000000000000001547341427044526700251610ustar00rootroot00000000000000/* * jQuery 1.2.6 - New Wave Javascript * * Copyright (c) 2008 John Resig (jquery.com) * Dual licensed under the MIT (MIT-LICENSE.txt) * and GPL (GPL-LICENSE.txt) licenses. * * $Date: 2008-09-25 09:50:52 -0700 (Thu, 25 Sep 2008) $ * $Rev: 38 $ */ (function(){var _jQuery=window.jQuery,_$=window.$;var jQuery=window.jQuery=window.$=function(selector,context){return new jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*$/,undefined;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){if(match[1])selector=jQuery.clean([match[1]],context);else{var elem=document.getElementById(match[3]);if(elem){if(elem.id!=match[3])return jQuery().find(selector);return jQuery(elem);}selector=[];}}else return jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(jQuery.makeArray(selector));},jquery:"1.2.6",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);return this;},each:function(callback,args){return jQuery.each(this,callback,args);},index:function(elem){var ret=-1;return jQuery.inArray(elem&&elem.jquery?elem[0]:elem,this);},attr:function(name,value,type){var options=name;if(name.constructor==String)if(value===undefined)return this[0]&&jQuery[type||"attr"](this[0],name);else{options={};options[name]=value;}return this.each(function(i){for(name in options)jQuery.attr(type?this.style:this,name,jQuery.prop(this,options[name],type,i,name));});},css:function(key,value){if((key=='width'||key=='height')&&parseFloat(value)<0)value=undefined;return this.attr(key,value,"curCSS");},text:function(text){if(typeof text!="object"&&text!=null)return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(text));var ret="";jQuery.each(text||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return ret;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,false,function(elem){if(this.nodeType==1)this.appendChild(elem);});},prepend:function(){return this.domManip(arguments,true,true,function(elem){if(this.nodeType==1)this.insertBefore(elem,this.firstChild);});},before:function(){return this.domManip(arguments,false,false,function(elem){this.parentNode.insertBefore(elem,this);});},after:function(){return this.domManip(arguments,false,true,function(elem){this.parentNode.insertBefore(elem,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(selector){var elems=jQuery.map(this,function(elem){return jQuery.find(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function(events){var ret=this.map(function(){if(jQuery.browser.msie&&!jQuery.isXMLDoc(this)){var clone=this.cloneNode(true),container=document.createElement("div");container.appendChild(clone);return jQuery.clean([container.innerHTML])[0];}else return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.grep(this,function(elem,i){return selector.call(elem,i);})||jQuery.multiFilter(selector,this));},not:function(selector){if(selector.constructor==String)if(isSimple.test(selector))return this.pushStack(jQuery.multiFilter(selector,this,true));else selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get(),typeof selector=='string'?jQuery(selector):jQuery.makeArray(selector))));},is:function(selector){return!!selector&&jQuery.multiFilter(selector,this).length>0;},hasClass:function(selector){return this.is("."+selector);},val:function(value){if(value==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,values=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=jQuery.makeArray(value);jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)>=0);});if(!values.length)this.selectedIndex=-1;}else this.value=value;});},html:function(value){return value==undefined?(this[0]?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},data:function(key,value){var parts=key.split(".");parts[1]=parts[1]?"."+parts[1]:"";if(value===undefined){var data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if(data===undefined&&this.length)data=jQuery.data(this[0],key);return data===undefined&&parts[1]?this.data(parts[0]):data;}else return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script"))scripts=scripts.add(elem);else{if(elem.nodeType==1)scripts=scripts.add(jQuery("script",elem).remove());callback.call(obj,elem);}});scripts.each(evalScript);});}};jQuery.fn.init.prototype=jQuery.fn;function evalScript(i,elem){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}function now(){return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==i){target=this;--i;}for(;i-1;}},swap:function(elem,options,callback){var old={};for(var name in options){old[name]=elem.style[name];elem.style[name]=options[name];}callback.call(elem);for(var name in options)elem.style[name]=old[name];},css:function(elem,name,force){if(name=="width"||name=="height"){var val,props={position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each(which,function(){padding+=parseFloat(jQuery.curCSS(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding+border);}if(jQuery(elem).is(":visible"))getWH();else jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)return false;var ret=defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=style.outline;style.outline="0 solid black";style.outline=save;}if(name.match(/float/i))name=styleFloat;if(!force&&style&&style[name])ret=style[name];else if(defaultView.getComputedStyle){if(name.match(/float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase();var computedStyle=defaultView.getComputedStyle(elem,null);if(computedStyle&&!color(elem))ret=computedStyle.getPropertyValue(name);else{var swap=[],stack=[],a=elem,i=0;for(;a&&color(a);a=a.parentNode)stack.unshift(a);for(;i]*?)\/>/g,function(all,front,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?all:front+">";});var tags=jQuery.trim(elem).toLowerCase(),div=context.createElement("div");var wrap=!tags.indexOf("",""]||!tags.indexOf("",""]||tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!tags.indexOf("",""]||(!tags.indexOf("",""]||!tags.indexOf("",""]||jQuery.browser.msie&&[1,"div
","
"]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var tbody=!tags.indexOf(""&&tags.indexOf("=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName(elem,"form")||elem.options)ret.push(elem);else ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||name;if(elem.tagName){var special=/href|src|style/.test(name);if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(name in elem&¬xml&&!special){if(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem[name]=value;}if(jQuery.nodeName(elem,"form")&&elem.getAttributeNode(name))return elem.getAttributeNode(name).nodeValue;return elem[name];}if(msie&¬xml&&name=="style")return jQuery.attr(elem.style,"cssText",value);if(set)elem.setAttribute(name,""+value);var attr=msie&¬xml&&special?elem.getAttribute(name,2):elem.getAttribute(name);return attr===null?undefined:attr;}if(msie&&name=="opacity"){if(set){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(value)+''=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'':"";}name=name.replace(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if(set)elem[name]=value;return elem[name];},trim:function(text){return(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var ret=[];if(array!=null){var i=array.length;if(i==null||array.split||array.setInterval||array.call)ret[0]=array;else while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array){for(var i=0,length=array.length;i*",this).remove();while(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn){jQuery.fn[name]=function(){return this.each(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name){var type=name.toLowerCase();jQuery.fn[type]=function(size){return this[0]==window?jQuery.browser.opera&&document.body["client"+name]||jQuery.browser.safari&&window["inner"+name]||document.compatMode=="CSS1Compat"&&document.documentElement["client"+name]||document.body["client"+name]:this[0]==document?Math.max(Math.max(document.body["scroll"+name],document.documentElement["scroll"+name]),Math.max(document.body["offset"+name],document.documentElement["offset"+name])):size==undefined?(this.length?jQuery.css(this[0],type):null):this.css(type,size.constructor==String?size:size+"px");};});function num(elem,prop){return elem[0]&&parseInt(jQuery.curCSS(elem[0],prop,true),10)||0;}var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute("id")==m[2];},":":{lt:function(a,i,m){return im[3]-0;},nth:function(a,i,m){return m[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i){return i==0;},last:function(a,i,m,r){return i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i){return i%2;},"first-child":function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},"only-child":function(a){return!jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},parent:function(a){return a.firstChild;},empty:function(a){return!a.firstChild;},contains:function(a,i,m){return(a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},hidden:function(a){return"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},enabled:function(a){return!a.disabled;},disabled:function(a){return a.disabled;},checked:function(a){return a.checked;},selected:function(a){return a.selected||jQuery.attr(a,"selected");},text:function(a){return"text"==a.type;},radio:function(a){return"radio"==a.type;},checkbox:function(a){return"checkbox"==a.type;},file:function(a){return"file"==a.type;},password:function(a){return"password"==a.type;},submit:function(a){return"submit"==a.type;},image:function(a){return"image"==a.type;},reset:function(a){return"reset"==a.type;},button:function(a){return"button"==a.type||jQuery.nodeName(a,"button");},input:function(a){return/input|select|textarea|button/i.test(a.nodeName);},has:function(a,i,m){return jQuery.find(m[3],a).length;},header:function(a){return/h\d/i.test(a.nodeName);},animated:function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}}},parse:[/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while(expr&&expr!=old){old=expr;var f=jQuery.filter(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context){if(typeof t!="string")return[t];if(context&&context.nodeType!=1&&context.nodeType!=9)return[];context=context||document;var ret=[context],done=[],last,nodeName;while(t&&last!=t){var r=[];last=t;t=jQuery.trim(t);var foundToken=false,re=quickChild,m=re.exec(t);if(m){nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&(nodeName=="*"||c.nodeName.toUpperCase()==nodeName))r.push(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec(t))!=null){r=[];var merge={};nodeName=m[2].toUpperCase();m=m[1];for(var j=0,rl=ret.length;j=0;if(!not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")r=isSimple.test(m[3])?jQuery.filter(m[3],r,true).r:jQuery(r).not(m[3]);else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i=0)^not)tmp.push(a);}r=tmp;}else if(m[1]==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m[3])&&"0n+"+m[3]||m[3]),first=(test[1]+(test[2]||1))-0,last=test[3]-0;for(var i=0,rl=r.length;i=0)add=true;if(add^not)tmp.push(node);}r=tmp;}else{var fn=jQuery.expr[m[1]];if(typeof fn=="object")fn=fn[m[2]];if(typeof fn=="string")fn=eval("false||function(a,i){return "+fn+";}");r=jQuery.grep(r,function(elem,i){return fn(elem,i,m,r);},not);}}return{r:r,t:t};},dir:function(elem,dir){var matched=[],cur=elem[dir];while(cur&&cur!=document){if(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return matched;},nth:function(cur,result,dir,elem){result=result||1;var num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)break;return cur;},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType==1&&n!=elem)r.push(n);}return r;}});jQuery.event={add:function(elem,types,handler,data){if(elem.nodeType==3||elem.nodeType==8)return;if(jQuery.browser.msie&&elem.setInterval)elem=window;if(!handler.guid)handler.guid=this.guid++;if(data!=undefined){var fn=handler;handler=this.proxy(fn,function(){return fn.apply(this,arguments);});handler.data=data;}var events=jQuery.data(elem,"events")||jQuery.data(elem,"events",{}),handle=jQuery.data(elem,"handle")||jQuery.data(elem,"handle",function(){if(typeof jQuery!="undefined"&&!jQuery.event.triggered)return jQuery.event.handle.apply(arguments.callee.elem,arguments);});handle.elem=elem;jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];handler.type=parts[1];var handlers=events[type];if(!handlers){handlers=events[type]={};if(!jQuery.event.special[type]||jQuery.event.special[type].setup.call(elem)===false){if(elem.addEventListener)elem.addEventListener(type,handle,false);else if(elem.attachEvent)elem.attachEvent("on"+type,handle);}}handlers[handler.guid]=handler;jQuery.event.global[type]=true;});elem=null;},guid:1,global:{},remove:function(elem,types,handler){if(elem.nodeType==3||elem.nodeType==8)return;var events=jQuery.data(elem,"events"),ret,index;if(events){if(types==undefined||(typeof types=="string"&&types.charAt(0)=="."))for(var type in events)this.remove(elem,type+(types||""));else{if(types.type){handler=types.handler;types=types.type;}jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];if(events[type]){if(handler)delete events[type][handler.guid];else for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(!jQuery.event.special[type]||jQuery.event.special[type].teardown.call(elem)===false){if(elem.removeEventListener)elem.removeEventListener(type,jQuery.data(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events[type];}}});}for(ret in events)break;if(!ret){var handle=jQuery.data(elem,"handle");if(handle)handle.elem=null;jQuery.removeData(elem,"events");jQuery.removeData(elem,"handle");}}},trigger:function(type,data,elem,donative,extra){data=jQuery.makeArray(data);if(type.indexOf("!")>=0){type=type.slice(0,-1);var exclusive=true;}if(!elem){if(this.global[type])jQuery("*").add([window,document]).trigger(type,data);}else{if(elem.nodeType==3||elem.nodeType==8)return undefined;var val,ret,fn=jQuery.isFunction(elem[type]||null),event=!data[0]||!data[0].preventDefault;if(event){data.unshift({type:type,target:elem,preventDefault:function(){},stopPropagation:function(){},timeStamp:now()});data[0][expando]=true;}data[0].type=type;if(exclusive)data[0].exclusive=true;var handle=jQuery.data(elem,"handle");if(handle)val=handle.apply(elem,data);if((!fn||(jQuery.nodeName(elem,'a')&&type=="click"))&&elem["on"+type]&&elem["on"+type].apply(elem,data)===false)val=false;if(event)data.shift();if(extra&&jQuery.isFunction(extra)){ret=extra.apply(elem,val==null?data:data.concat(val));if(ret!==undefined)val=ret;}if(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(elem,'a')&&type=="click")){this.triggered=true;try{elem[type]();}catch(e){}}this.triggered=false;}return val;},handle:function(event){var val,ret,namespace,all,handlers;event=arguments[0]=jQuery.event.fix(event||window.event);namespace=event.type.split(".");event.type=namespace[0];namespace=namespace[1];all=!namespace&&!event.exclusive;handlers=(jQuery.data(this,"events")||{})[event.type];for(var j in handlers){var handler=handlers[j];if(all||handler.type==namespace){event.handler=handler;event.data=handler.data;ret=handler.apply(this,arguments);if(val!==false)val=ret;if(ret===false){event.preventDefault();event.stopPropagation();}}}return val;},fix:function(event){if(event[expando]==true)return event;var originalEvent=event;event={originalEvent:originalEvent};var props="altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");for(var i=props.length;i;i--)event[props[i]]=originalEvent[props[i]];event[expando]=true;event.preventDefault=function(){if(originalEvent.preventDefault)originalEvent.preventDefault();originalEvent.returnValue=false;};event.stopPropagation=function(){if(originalEvent.stopPropagation)originalEvent.stopPropagation();originalEvent.cancelBubble=true;};event.timeStamp=event.timeStamp||now();if(!event.target)event.target=event.srcElement||document;if(event.target.nodeType==3)event.target=event.target.parentNode;if(!event.relatedTarget&&event.fromElement)event.relatedTarget=event.fromElement==event.target?event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!=null){var doc=document.documentElement,body=document.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc.clientTop||0);}if(!event.which&&((event.charCode||event.charCode===0)?event.charCode:event.keyCode))event.which=event.charCode||event.keyCode;if(!event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!event.which&&event.button)event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)));return event;},proxy:function(fn,proxy){proxy.guid=fn.guid=fn.guid||proxy.guid||this.guid++;return proxy;},special:{ready:{setup:function(){bindReady();return;},teardown:function(){return;}},mouseenter:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseover",jQuery.event.special.mouseenter.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseover",jQuery.event.special.mouseenter.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseenter";return jQuery.event.handle.apply(this,arguments);}},mouseleave:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseout",jQuery.event.special.mouseleave.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseout",jQuery.event.special.mouseleave.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseleave";return jQuery.event.handle.apply(this,arguments);}}}};jQuery.fn.extend({bind:function(type,data,fn){return type=="unload"?this.one(type,data,fn):this.each(function(){jQuery.event.add(this,type,fn||data,fn&&data);});},one:function(type,data,fn){var one=jQuery.event.proxy(fn||data,function(event){jQuery(this).unbind(event,one);return(fn||data).apply(this,arguments);});return this.each(function(){jQuery.event.add(this,type,one,fn&&data);});},unbind:function(type,fn){return this.each(function(){jQuery.event.remove(this,type,fn);});},trigger:function(type,data,fn){return this.each(function(){jQuery.event.trigger(type,data,this,true,fn);});},triggerHandler:function(type,data,fn){return this[0]&&jQuery.event.trigger(type,data,this[0],false,fn);},toggle:function(fn){var args=arguments,i=1;while(i=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}callback=callback||function(){};var type="GET";if(params)if(jQuery.isFunction(params)){callback=params;params=null;}else{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(res,status){if(status=="success"||status=="notmodified")self.html(selector?jQuery("
").append(res.responseText.replace(//g,"")).find(selector):res.responseText);self.each(callback,[res.responseText,status,res]);}});return this;},serialize:function(){return jQuery.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return jQuery.nodeName(this,"form")?jQuery.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type));}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:val.constructor==Array?jQuery.map(val,function(val,i){return{name:elem.name,value:val};}):{name:elem.name,value:val};}).get();}});jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(i,o){jQuery.fn[o]=function(f){return this.bind(o,f);};});var jsc=now();jQuery.extend({get:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data=null;}return jQuery.ajax({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function(url,callback){return jQuery.get(url,null,callback,"script");},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json");},post:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data={};}return jQuery.ajax({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:{url:location.href,global:true,type:"GET",timeout:0,contentType:"application/x-www-form-urlencoded",processData:true,async:true,data:null,username:null,password:null,accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(s){s=jQuery.extend(true,s,jQuery.extend(true,{},jQuery.ajaxSettings,s));var jsonp,jsre=/=\?(&|$)/g,status,data,type=s.type.toUpperCase();if(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param(s.data);if(s.dataType=="jsonp"){if(type=="GET"){if(!s.url.match(jsre))s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if(s.data)s.data=(s.data+"").replace(jsre,"="+jsonp+"$1");s.url=s.url.replace(jsre,"="+jsonp+"$1");s.dataType="script";window[jsonp]=function(tmp){data=tmp;success();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch(e){}if(head)head.removeChild(script);};}if(s.dataType=="script"&&s.cache==null)s.cache=false;if(s.cache===false&&type=="GET"){var ts=now();var ret=s.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+ts+"$2");s.url=ret+((ret==s.url)?(s.url.match(/\?/)?"&":"?")+"_="+ts:"");}if(s.data&&type=="GET"){s.url+=(s.url.match(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)jQuery.event.trigger("ajaxStart");var remote=/^(?:\w+:)?\/\/([^\/?#]+)/;if(s.dataType=="script"&&type=="GET"&&remote.test(s.url)&&remote.exec(s.url)[1]!=location.host){var head=document.getElementsByTagName("head")[0];var script=document.createElement("script");script.src=s.url;if(s.scriptCharset)script.charset=s.scriptCharset;if(!jsonp){var done=false;script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;success();complete();head.removeChild(script);}};}head.appendChild(script);return undefined;}var requestDone=false;var xhr=window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();if(s.username)xhr.open(type,s.url,s.async,s.username,s.password);else xhr.open(type,s.url,s.async);try{if(s.data)xhr.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xhr.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */*":s.accepts._default);}catch(e){}if(s.beforeSend&&s.beforeSend(xhr,s)===false){s.global&&jQuery.active--;xhr.abort();return false;}if(s.global)jQuery.event.trigger("ajaxSend",[xhr,s]);var onreadystatechange=function(isTimeout){if(!requestDone&&xhr&&(xhr.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival){clearInterval(ival);ival=null;}status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xhr)&&"error"||s.ifModified&&jQuery.httpNotModified(xhr,s.url)&&"notmodified"||"success";if(status=="success"){try{data=jQuery.httpData(xhr,s.dataType,s.dataFilter);}catch(e){status="parsererror";}}if(status=="success"){var modRes;try{modRes=xhr.getResponseHeader("Last-Modified");}catch(e){}if(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)success();}else jQuery.handleError(s,xhr,status);complete();if(s.async)xhr=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xhr){xhr.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xhr.send(s.data);}catch(e){jQuery.handleError(s,xhr,null,e);}if(!s.async)onreadystatechange();function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xhr,s]);}function complete(){if(s.complete)s.complete(xhr,status);if(s.global)jQuery.event.trigger("ajaxComplete",[xhr,s]);if(s.global&&!--jQuery.active)jQuery.event.trigger("ajaxStop");}return xhr;},handleError:function(s,xhr,status,e){if(s.error)s.error(xhr,status,e);if(s.global)jQuery.event.trigger("ajaxError",[xhr,s,e]);},active:0,httpSuccess:function(xhr){try{return!xhr.status&&location.protocol=="file:"||(xhr.status>=200&&xhr.status<300)||xhr.status==304||xhr.status==1223||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpNotModified:function(xhr,url){try{var xhrRes=xhr.getResponseHeader("Last-Modified");return xhr.status==304||xhrRes==jQuery.lastModified[url]||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpData:function(xhr,type,filter){var ct=xhr.getResponseHeader("content-type"),xml=type=="xml"||!type&&ct&&ct.indexOf("xml")>=0,data=xml?xhr.responseXML:xhr.responseText;if(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if(filter)data=filter(data,type);if(type=="script")jQuery.globalEval(data);if(type=="json")data=eval("("+data+")");return data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)jQuery.each(a,function(){s.push(encodeURIComponent(this.name)+"="+encodeURIComponent(this.value));});else for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else s.push(encodeURIComponent(j)+"="+encodeURIComponent(jQuery.isFunction(a[j])?a[j]():a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock||"";if(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName+" />").appendTo("body");this.style.display=elem.css("display");if(this.style.display=="none")this.style.display="block";elem.remove();}}).end();},hide:function(speed,callback){return speed?this.animate({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css(this,"display");this.style.display="none";}).end();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle.apply(this,arguments):fn?this.animate({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();});},slideDown:function(speed,callback){return this.animate({height:"show"},speed,callback);},slideUp:function(speed,callback){return this.animate({height:"hide"},speed,callback);},slideToggle:function(speed,callback){return this.animate({height:"toggle"},speed,callback);},fadeIn:function(speed,callback){return this.animate({opacity:"show"},speed,callback);},fadeOut:function(speed,callback){return this.animate({opacity:"hide"},speed,callback);},fadeTo:function(speed,to,callback){return this.animate({opacity:to},speed,callback);},animate:function(prop,speed,easing,callback){var optall=jQuery.speed(speed,easing,callback);return this[optall.queue===false?"each":"queue"](function(){if(this.nodeType!=1)return false;var opt=jQuery.extend({},optall),p,hidden=jQuery(this).is(":hidden"),self=this;for(p in prop){if(prop[p]=="hide"&&hidden||prop[p]=="show"&&!hidden)return opt.complete.call(this);if(p=="height"||p=="width"){opt.display=jQuery.css(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)+start;e.custom(start,end,unit);}else e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)||(type&&type.constructor==Array)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.call(this);}});},stop:function(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue([]);this.each(function(){for(var i=timers.length-1;i>=0;i--)if(timers[i].elem==this){if(gotoEnd)timers[i](true);timers.splice(i,1);}});if(!gotoEnd)this.dequeue();return this;}});var queue=function(elem,type,array){if(elem){type=type||"fx";var q=jQuery.data(elem,type+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",jQuery.makeArray(array));}return q;};jQuery.fn.dequeue=function(type){type=type||"fx";return this.each(function(){var q=queue(this,type);q.shift();if(q.length)q[0].call(this);});};jQuery.extend({speed:function(speed,easing,fn){var opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&easing.constructor!=Function&&easing};opt.duration=(opt.duration&&opt.duration.constructor==Number?opt.duration:jQuery.fx.speeds[opt.duration])||jQuery.fx.speeds.def;opt.old=opt.complete;opt.complete=function(){if(opt.queue!==false)jQuery(this).dequeue();if(jQuery.isFunction(opt.old))opt.old.call(this);};return opt;},easing:{linear:function(p,n,firstNum,diff){return firstNum+diff*p;},swing:function(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff+firstNum;}},timers:[],timerId:null,fx:function(elem,options,prop){this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)options.orig={};}});jQuery.fx.prototype={update:function(){if(this.options.step)this.options.step.call(this.elem,this.now,this);(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if(this.prop=="height"||this.prop=="width")this.elem.style.display="block";},cur:function(force){if(this.elem[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem[this.prop];var r=parseFloat(jQuery.css(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat(jQuery.curCSS(this.elem,this.prop))||0;},custom:function(from,to,unit){this.startTime=now();this.start=from;this.end=to;this.unit=unit||this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update();var self=this;function t(gotoEnd){return self.step(gotoEnd);}t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timerId==null){jQuery.timerId=setInterval(function(){var timers=jQuery.timers;for(var i=0;ithis.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var done=true;for(var i in this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if(done){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(jQuery.css(this.elem,"display")=="none")this.elem.style.display="block";}if(this.options.hide)this.elem.style.display="none";if(this.options.hide||this.options.show)for(var p in this.options.curAnim)jQuery.attr(this.elem.style,p,this.options.orig[p]);}if(done)this.options.complete.call(this.elem);return false;}else{var n=t-this.startTime;this.state=n/this.options.duration;this.pos=jQuery.easing[this.options.easing||(jQuery.easing.swing?"swing":"linear")](this.state,n,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update();}return true;}};jQuery.extend(jQuery.fx,{speeds:{slow:600,fast:200,def:400},step:{scrollLeft:function(fx){fx.elem.scrollLeft=fx.now;},scrollTop:function(fx){fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style[fx.prop]=fx.now+fx.unit;}}});jQuery.fn.offset=function(){var left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var parent=elem.parentNode,offsetChild=elem,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt(version)<522&&!/adobeair/i.test(userAgent),css=jQuery.curCSS,fixed=css(elem,"position")=="fixed";if(elem.getBoundingClientRect){var box=elem.getBoundingClientRect();add(box.left+Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));add(-doc.documentElement.clientLeft,-doc.documentElement.clientTop);}else{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&!/^t(able|d|h)$/i.test(offsetParent.tagName)||safari&&!safari2)border(offsetParent);if(!fixed&&css(offsetParent,"position")=="fixed")fixed=true;offsetChild=/^body$/i.test(offsetParent.tagName)?offsetChild:offsetParent;offsetParent=offsetParent.offsetParent;}while(parent&&parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/^inline|table.*$/i.test(css(parent,"display")))add(-parent.scrollLeft,-parent.scrollTop);if(mozilla&&css(parent,"overflow")!="visible")border(parent);parent=parent.parentNode;}if((safari2&&(fixed||css(offsetChild,"position")=="absolute"))||(mozilla&&css(offsetChild,"position")!="absolute"))add(-doc.body.offsetLeft,-doc.body.offsetTop);if(fixed)add(Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));}results={top:top,left:left};}function border(elem){add(jQuery.curCSS(elem,"borderLeftWidth",true),jQuery.curCSS(elem,"borderTopWidth",true));}function add(l,t){left+=parseInt(l,10)||0;top+=parseInt(t,10)||0;}return results;};jQuery.fn.extend({position:function(){var left=0,top=0,results;if(this[0]){var offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=/^body|html$/i.test(offsetParent[0].tagName)?{top:0,left:0}:offsetParent.offset();offset.top-=num(this,'marginTop');offset.left-=num(this,'marginLeft');parentOffset.top+=num(offsetParent,'borderTopWidth');parentOffset.left+=num(offsetParent,'borderLeftWidth');results={top:offset.top-parentOffset.top,left:offset.left-parentOffset.left};}return results;},offsetParent:function(){var offsetParent=this[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test(offsetParent.tagName)&&jQuery.css(offsetParent,'position')=='static'))offsetParent=offsetParent.offsetParent;return jQuery(offsetParent);}});jQuery.each(['Left','Top'],function(i,name){var method='scroll'+name;jQuery.fn[method]=function(val){if(!this[0])return;return val!=undefined?this.each(function(){this==window||this==document?window.scrollTo(!i?val:jQuery(window).scrollLeft(),i?val:jQuery(window).scrollTop()):this[method]=val;}):this[0]==window||this[0]==document?self[i?'pageYOffset':'pageXOffset']||jQuery.boxModel&&document.documentElement[method]||document.body[method]:this[0][method];};});jQuery.each(["Height","Width"],function(i,name){var tl=i?"Left":"Top",br=i?"Right":"Bottom";jQuery.fn["inner"+name]=function(){return this[name.toLowerCase()]()+num(this,"padding"+tl)+num(this,"padding"+br);};jQuery.fn["outer"+name]=function(margin){return this["inner"+name]()+num(this,"border"+tl+"Width")+num(this,"border"+br+"Width")+(margin?num(this,"margin"+tl)+num(this,"margin"+br):0);};});})();camping-2.3/extras/rdoc/generator/template/flipbook/page.rhtml000066400000000000000000000017301427044526700246370ustar00rootroot00000000000000 <%= h @options.title %>

<%= Time.now %>

<%= file.content %>
camping-2.3/extras/rdoc/generator/template/flipbook/rdoc.css000066400000000000000000000050111427044526700243100ustar00rootroot00000000000000 body { font: normal 14px verdana,arial,'Bitstream Vera Sans',helvetica,sans-serif; line-height: 160%; padding: 0; margin: 0; margin-bottom: 30px; background-color: #694; } p#version, h1, h2, h3, h4 { font-family: Utopia, Georgia, serif; font-weight: bold; letter-spacing: -0.018em; } #logo { width: 150px; margin: 2em auto; display: block; } h1 { font-size: 36px; margin: 1em; } h2 { font-size: 24px } h3, p#version { font-size: 19px } h4 { font-size: 17px; font-weight: normal; } h2.ruled { padding-top: 35px; border-top: solid 1px #AA5; } /* Link styles */ :link, :visited { color: #00b; } :link:hover, :visited:hover { background-color: #eee; color: #B22; } /* menu */ #menu { background-color: #dfa; padding: 4px 12px; margin: 0; } #menu #version { padding: 0; margin: 0; } #menu #links { float: right; padding:0; margin: 0;} #menu #links ul { display: inline; } #menu #links li { display: inline; } #fullpage { width: 720px; margin: 3em auto; } .page_shade, .page { clear: both; padding: 0px 5px 5px 0px; background-color: #fcfcf9; border: solid 1px #983; } .page { margin-left: -5px; margin-top: -5px; padding: 20px 35px; } .page .header { float: right; color: #777; font-size: 10px; } .page h1, .page h2, .page h3 { clear: both; text-align: center; } .ref h2 a { background-color: #eee; color: black; display: block; text-align: left; text-decoration: none; padding: 0.5em 1em; } .ref h2 a:hover { text-decoration: underline; } .ref h4 a { border-bottom: solid 1px #CC9; color: #000; display: block; text-decoration: none; } .ref h4 a:hover { border-color: #694; } .ref .mod { padding: 0 2em; } pre { font-weight: bold; color: #730; } tt { color: #703; font-size: 12pt; } pre.sourcecode { color: #DDDDDD; background-color: #775915; padding: 4px 8px; margin: 0; display: none; font-size: 8pt; } .source-link { text-align: right; font-size: 8pt; } .ruby-comment { color: green; font-style: italic } .ruby-constant { color: #CCDDFF; font-weight: bold; } .ruby-identifier { color: #CCCCCC; } .ruby-ivar { color: #BBCCFF; } .ruby-keyword { color: #EEEEFF; font-weight: bold } .ruby-node { color: #FFFFFF; } .ruby-operator { color: #CCCCCC; } .ruby-regexp { color: #DDFFDD; } .ruby-value { color: #FFAAAA; font-style: italic } .kw { color: #DDDDFF; font-weight: bold } .cmt { color: #CCFFCC; font-style: italic } .str { color: #EECCCC; font-style: italic } .re { color: #EECCCC; }camping-2.3/extras/rdoc/generator/template/flipbook/readme.rhtml000066400000000000000000000021301427044526700251530ustar00rootroot00000000000000 <%= h @options.title %>

<%= Time.now %>

<%= h @options.title %>

<%= @files.find { |f| f.full_name == @options.main_page }.description.sub(%r{^\s*}i, '') %>
camping-2.3/extras/rdoc/generator/template/flipbook/reference.rhtml000066400000000000000000000043651427044526700256700ustar00rootroot00000000000000 Camping, the Reference

<%= Time.now %>

Camping, the Reference

<% @modsort.each_with_index do |klass, index| %>

<%= klass.type.capitalize %> <%= klass.full_name %> <% if klass.type == 'class' %> < <% unless String === klass.superclass %> <%= klass.superclass.full_name %> <% else %> <%= klass.superclass %> <% end %> <% end %>

<%= klass.description.gsub(/<(\/?)h(\d)>/) do # replace

with

and so. "<#{$1}h#{$2.to_i + 1}>" end %>

Methods

<% methods_for(klass) do |method, type, visibility| %>

<%= visibility.to_s.capitalize %> <%= type.capitalize %> method: <%= method.pretty_name %><%= method.params %>

<%= method.description %>
<%= method.markup_code %>
<% end %>
<% end %>
camping-2.3/extras/rdoc/generator/template/flipbook/toc.rhtml000066400000000000000000000024571427044526700245170ustar00rootroot00000000000000 <%= @options.title %>

<%= Time.now %>

Camping, the Book

    <% chapters.each do |chapter| %>
  1. <%= chapter.title %> <% unless chapter.toc.empty? %>
      <% chapter.toc.each do |id, title| %>
    • <%= title %>
    • <% end %>
    <% end %>
  2. <% end %>
camping-2.3/lib/000077500000000000000000000000001427044526700135555ustar00rootroot00000000000000camping-2.3/lib/camping-unabridged.rb000066400000000000000000000653631427044526700176370ustar00rootroot00000000000000# == About camping.rb # # Camping comes with two versions of its source code. The code contained in # lib/camping.rb is compressed, stripped of whitespace, using compact algorithms # to keep it tight. The unspoken rule is that camping.rb should be flowed with # no more than 80 characters per line and must not exceed four kilobytes. # # On the other hand, lib/camping-unabridged.rb contains the same code, laid out # nicely with piles of documentation everywhere. This documentation is entirely # generated from lib/camping-unabridged.rb using RDoc and our "flipbook" template # found in the extras directory of any camping distribution. require "uri" require "rack" $LOADED_FEATURES << "camping.rb" E ||= "Content-Type" Z ||= "text/html" class Object #:nodoc: def meta_def(m,&b) #:nodoc: (class<Camping.goes :Nuts # copies the Camping module into Nuts. This means that you should never use # any of these methods/classes on the Camping module, but rather on your own # app. Here's a short explanation on how Camping is organized: # # * Camping::Controllers is where your controllers live. # * Camping::Models is where your models live. # * Camping::Views is where your views live. # * Camping::Base is a module which is included in all your controllers. # * Camping::Helpers is a module with useful helpers, both for the controllers # and the views. You should fill this up with your own helpers. # # Camping also ships with: # # * Camping::Session adds states to your app. # * Camping::Server starts up your app in development. # * Camping::Reloader automatically reloads your apps when a file has changed. # # More importantly, Camping also installs The Camping Server, # please see Camping::Server. module Camping C = self S = IO.read(__FILE__) rescue nil P = "

Cam\ping Problem!

%s

" U = Rack::Utils O = { url_prefix: "" } # Our Hash of Options Apps = [] # Our array of Apps SK = :camping #Key for r.session G = [] # Our array of Gear # An object-like Hash. # All Camping query string and cookie variables are loaded as this. # # To access the query string, for instance, use the @input variable. # # module Blog::Controllers # class Index < R '/' # def get # if (page = @input.page.to_i) > 0 # page -= 1 # end # @posts = Post.all, :offset => page * 20, :limit => 20 # render :index # end # end # end # # In the above example if you visit /?page=2, you'll get the second # page of twenty posts. You can also use @input['page'] to get the # value for the page query variable. class H < Hash # Gets or sets keys in the hash. # # @cookies.my_favorite = :macadamian # @cookies.my_favorite # => :macadamian # def method_missing(m,*a) m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super end undef id, type if ?? == 63 end class Cookies < H attr_accessor :_p # # Cookies that are set at this response def _n; @n ||= {} end alias _s []= def set(k, v, o = {}) _s(j=k.to_s, v) _n[j] = {:value => v, :path => _p}.update(o) end def []=(k, v) set k, v, v.is_a?(Hash) ? v : {} end end # Helpers contains methods available in your controllers and views. You may # add methods of your own to this module, including many helper methods from # Rails. This is analogous to Rails' ApplicationHelper module. # # == Using ActionPack Helpers # # If you'd like to include helpers from Rails' modules, you'll need to look # up the helper module in the Rails documentation at http://api.rubyonrails.org/. # # For example, if you look up the ActionView::Helpers::FormTagHelper # class, you'll find that it's loaded from the action_view/helpers/form_tag_helper.rb # file. You'll need to have the ActionPack gem installed for this to work. # # A helper often depends on other helpers, so you would have to look up # the dependencies too. FormTagHelper for instance required the # content_tag provided by TagHelper. # # require 'action_view/helpers/form_tag_helper' # # module Nuts::Helpers # include ActionView::Helpers::TagHelper # include ActionView::Helpers::FormTagHelper # end # # == Return a response immediately # If you need to return a response inside a helper, you can use throw :halt. # # module Nuts::Helpers # def requires_login! # unless @state.user_id # redirect Login # throw :halt # end # end # end # # module Nuts::Controllers # class Admin # def get # requires_login! # "Never gets here unless you're logged in" # end # end # end module Helpers # From inside your controllers and views, you will often need to figure out # the route used to get to a certain controller +c+. Pass the controller # class and any arguments into the R method, a string containing the route # will be returned to you. # # Assuming you have a specific route in an edit controller: # # class Edit < R '/edit/(\d+)' # # A specific route to the Edit controller can be built with: # # R(Edit, 1) # # Which outputs: /edit/1. # # If a controller has many routes, the route will be selected if it is the # first in the routing list to have the right number of arguments. # # == Using R in the View # # Keep in mind that this route doesn't include the root path. You will # need to use / (the slash method above) in your controllers. # Or, go ahead and use the Helpers#URL method to build a complete URL for # a route. # # However, in your views, the :href, :src and :action attributes # automatically pass through the slash method, so you are encouraged to # use R or URL in your views. # # module Nuts::Views # def menu # div.menu! do # a 'Home', :href => URL() # a 'Profile', :href => "/profile" # a 'Logout', :href => R(Logout) # a 'Google', :href => 'http://google.com' # end # end # end # # Let's say the above example takes place inside an application mounted at # http://localhost:3301/frodo and that a controller named # Logout is assigned to route /logout. # The HTML will come out as: # # # def R(c,*g) p,h=/\(.+?\)/,g.grep(Hash) g-=h raise "bad route" if !u = c.urls.find{|x| break x if x.scan(p).size == g.size && /^#{x}\/?$/ =~ (x=g.inject(x){|x,a| x.sub p,U.escape((a.to_param rescue a))}.gsub(/\\(.)/){$1}) } h.any?? u+"?"+U.build_query(h[0]) : u end # Simply builds a complete path from a path +p+ within the app. If your # application is mounted at /blog: # # self / "/view/1" #=> "/blog/view/1" # self / "styles.css" #=> "styles.css" # self / R(Edit, 1) #=> "/blog/edit/1" # def /(p); p[0] == ?/ ? @root + p : p end #/ # Builds a URL route to a controller or a path, returning a URI object. # This way you'll get the hostname and the port number, a complete URL. # # You can use this to grab URLs for controllers using the R-style syntax. # So, if your application is mounted at http://test.ing/blog/ # and you have a View controller which routes as R '/view/(\d+)': # # URL(View, @post.id) #=> # # # Or you can use the direct path: # # self.URL #=> # # self.URL + "view/12" #=> # # URL("/view/12") #=> # # # It's okay to pass URL strings through this method as well: # # URL("http://google.com") #=> # # # Any string which doesn't begin with a slash will pass through # unscathed. def URL c='/',*a c = R(c, *a) if c.respond_to? :urls c = self/c c = @request.url[/.{8,}?(?=\/|$)/]+c if c[0]==?/ #/ URI(c) end end # Camping::Base is built into each controller by way of the generic routing # class Camping::R. In some ways, this class is trying to do too much, but # it saves code for all the glue to stay in one place. Forgivable, # considering that it's only really a handful of methods and accessors. # # Everything in this module is accessible inside your controllers. module Base attr_accessor :env, :request, :root, :input, :cookies, :state, :status, :headers, :body T = {} L = :layout # Finds a template, returning either: # # false # => Could not find template # true # => Found template in Views # instance of Tilt # => Found template in a file def lookup(n) T.fetch(n.to_sym) do |k| t = Views.method_defined?(k) || (t = O[:_t].keys.grep(/^#{n}\./)[0]and Template[t].new{O[:_t][t]}) || (f = Dir[[O[:views] || "views", "#{n}.*"]*'/'][0]) && Template.new(f, O[f[/\.(\w+)$/, 1].to_sym] || {}) O[:dynamic_templates] ? t : T[k] = t end end # Display a view, calling it by its method name +v+. If a layout # method is found in Camping::Views, it will be used to wrap the HTML. # # module Nuts::Controllers # class Show # def get # @posts = Post.find :all # render :index # end # end # end # def render(v, *a, &b) if t = lookup(v) # Has this controller rendered before? r = @_r # Set @_r to truthy value @_r = (o = Hash === a[-1] ? a.pop : {}) s = (t == true) ? mab { send(v, *a, &b) } : t.render(self, o[:locals] || {}, &b) s = render(L, o.merge(L => false)) { s } if o[L] or o[L].nil? && lookup(L) && !r && v.to_s[0] != ?_ s else raise "no template: #{v}" end end # You can directly return HTML from your controller for quick debugging # by calling this method and passing some Markaby to it. # # module Nuts::Controllers # class Info # def get; mab{ code @headers.inspect } end # end # end # # You can also pass true to use the :layout HTML wrapping method def mab(&b) extend Mab mab(&b) end # A quick means of setting this controller's status, body and headers # based on a Rack response: # # r(302, 'Location' => self / "/view/12", '') # r(*another_app.call(@env)) # # You can also switch the body and the header if you want: # # r(404, "Could not find page") # # See also: #r404, #r500 and #r501 def r(s, b, h = {}) b, h = h, b if Hash === b @status = s @headers.merge!(h) @body = b end # Formulate a redirect response: a 302 status with Location header # and a blank body. Uses Helpers#URL to build the location from a # controller route or path. # # So, given a root of http://localhost:3301/articles: # # redirect "view/12" # redirects to "//localhost:3301/articles/view/12" # redirect View, 12 # redirects to "//localhost:3301/articles/view/12" # # NOTE: This method doesn't magically exit your methods and redirect. # You'll need to return redirect(...) if this isn't the last statement # in your code, or throw :halt if it's in a helper. # # See: Controllers def redirect(*a) r(302,'','Location'=>URL(*a).to_s) end # Called when a controller was not found. You can override this if you # want to customize the error page: # # module Nuts # def r404(path) # @path = path # render :not_found # end # end def r404(p) P % "#{p} not found" end # Called when an exception is raised. However, if there is a parse error # in Camping or in your application's source code, it will not be caught. # # +k+ is the controller class, +m+ is the request method (GET, POST, etc.) # and +e+ is the Exception which can be mined for useful info. # # By default this simply re-raises the error so a Rack middleware can # handle it, but you are free to override it here: # # module Nuts # def r500(klass, method, exception) # send_email_alert(klass, method, exception) # render :server_error # end # end def r500(k,m,e) raise e end # Called if an undefined method is called on a controller, along with the # request method +m+ (GET, POST, etc.) def r501(m) P % "#{m.upcase} not implemented" end # Serves the string +c+ with the MIME type of the filename +p+. # Defaults to text/html. def serve(p, c) t = Rack::Mime.mime_type(p[/\..*$/], Z) and @headers[E] = t c end # Turn a controller into a Rack response. This is designed to be used to # pipe controllers into the r method. A great way to forward your # requests! # # class Read < '/(\d+)' # def get(id) # Post.find(id) # rescue # r *Blog.get(:NotFound, @headers.REQUEST_URI) # end # end def to_a @env['rack.session'][SK] = Hash[@state] r = Rack::Response.new(@body, @status, @headers) @cookies._n.each do |k, v| r.set_cookie(k, v) end r.to_a end def initialize(env, m) #:nodoc: r = @request = Rack::Request.new(@env = env) @root, @input, @cookies, @state, @headers, @status, @method = r.script_name.sub(/\/$/,''), n(r.params), Cookies[r.cookies], H[r.session[SK]||{}], {E=>Z}, m =~ /r(\d+)/ ? $1.to_i : 200, m @cookies._p = self/"/" end def n(h) # :nodoc: if Hash === h h.inject(H[]) do |m, (k, v)| m[k] = n(v) m end else h end end # All requests pass through this method before going to the controller. # Some magic in Camping can be performed by overriding this method. def service(*a) r = catch(:halt){send(@method, *a)} @body ||= r self end end # Controllers receive the requests and send a response back to the client. # A controller is simply a class which must implement the HTTP methods it # wants to accept: # # module Nuts::Controllers # class Index # def get # "Hello World" # end # end # # class Posts # def post # Post.create(@input) # redirect Index # end # end # end # # == Defining a controller # # There are two ways to define controllers: # # 1. Define a class and let Camping figure out the route. # 2. Add the route explicitly using R. # # If you don't use R, Camping will first split the controller name up by # words (HelloWorld => Hello and World). # # After that, it will do the following: # # * Replace Index with / # * Replace X with ([^/]+) # * Replace N with (\\\d+) # * Turn everything else into lowercase # * Join the words with slashes # #-- # NB! N will actually be replaced with (\d+), but it needs to be escaped # here in order to work correctly with RDoc. #++ # # Here are a few examples: # # Index # => / # PostN # => /post/(\d+) # PageX # => /page/([^/]+) # Pages # => /pages # # == The request # # The following variables aid in describing request: # # * @env contains the environment as defined in http://rack.rubyforge.org/doc/SPEC.html # * @request is Rack::Request.new(@env) # * @root is the path where the app is mounted # * @cookies is a hash with the cookies sent by the client # * @state is a hash with the sessions (see Camping::Session) # * @method is the HTTP method in lowercase # # == The response # # You can change these variables to your needs: # # * @status is the HTTP status (defaults to 200) # * @headers is a hash with the headers # * @body is the body (a string or something which responds to #each) # * Any changes in @cookies and @state will also be sent to the client # # If you haven't set @body, it will use the return value of the method: # # module Nuts::Controllers # class Index # def get # "This is the body" # end # end # # class Posts # def get # @body = "Hello World!" # "This is ignored" # end # end # end module Controllers @r = [] class << self # Add routes to a controller class by piling them into the R method. # # The route is a regexp which will match the request path. Anything # enclosed in parenthesis will be sent to the method as arguments. # # module Camping::Controllers # class Edit < R '/edit/(\d+)', '/new' # def get(id) # if id # edit # else # new # end # end # end # end def R *u r=@r Class.new { meta_def(:urls){u} meta_def(:inherited){|x|r< (u) { return u unless u.respond_to? :last u << "/" unless u.last == "/" u } # Avoid, a lambda to avoid internal controller route A = -> (c, u, p) { u.prepend("/"+p) unless c.to_s == "I" } end N = H.new { |_,x| x.downcase }.merge! "N" => '(\d+)', "X" => '([^/]+)', "Index" => '' # The route maker, called by Camping internally. # # Still, it's worth know what this method does. Since Ruby doesn't keep # track of class creation order, we're keeping an internal list of the # controllers which inherit from R(). This method goes through and adds # all the remaining routes to the beginning of the list and ensures all # the controllers have the right mixins. # # Anyway, if you are calling the URI dispatcher from outside of a # Camping server, you'll definitely need to call this to set things up. # Don't call it too early though - any controllers added after this # method was called won't work properly. def M(pr) def M(pr) #:nodoc: end constants.map { |c| k = const_get(c) k.send :include,C,X,Base,Helpers,Models @r=[k]+@r if @r-[k]==@r k.meta_def(:urls){[ F::T.(F::A.(k, "#{c.to_s.scan(/.[^A-Z]*/).map(&N.method(:[]))*'/'}", pr)) ]}if !k.respond_to?:urls } end end # Internal controller with no route. Used to show internal messages. I = R() end X = Controllers class << self # Helper method for getting routes from the controllers. # helps Camping::Server map routes to multiple apps. # Usage: # # Nuts.routes # Camping.routes # Nuts.routes # def routes X.M O[:url_prefix] (Apps.map(&:routes)< e).to_a end # The Camping scriptable dispatcher. Any unhandled method call to the app # module will be sent to a controller class, specified as an argument. # # Blog.get(:Index) # #=> # # # The controller object contains all the @cookies, @body, @headers, etc. # formulated by the response. # # You can also feed environment variables and query variables as a hash, # the final argument. # # Blog.post(:Login, :input => {'username' => 'admin', 'password' => 'camping'}) # #=> # # # Blog.get(:Info, :env => {'HTTP_HOST' => 'wagon'}) # #=> #'wagon'} ...> # def method_missing(m, c, *a) X.M O[:url_prefix] h = Hash === a[-1] ? a.pop : {} e = H[Rack::MockRequest.env_for('',h.delete(:env)||{})] k = X.const_get(c).new(e,m.to_s) h.each { |i, v| k.send("#{i}=", v) } k.service(*a) end # Injects a middleware: # # module Blog # use Rack::MethodOverride # use Rack::Session::Memcache, :key => "session" # end def use(*a, &b) m = a.shift.new(method(:call), *a, &b) meta_def(:call) { |e| m.call(e) } end # Add gear to your app: # # module Blog # pack Camping::Gear::CSRF # end # # This feature is an experiment. Inspired by the way that Cuba allows # plugins. # # Why have plugins in the first place if we can just include and extend our # modules and classes directly? To perform setup actions. # # Sometimes you might have ClassMethods that you want to modify camping with, # This gives us a way to do that. # # Optionally a plugin may have a setup method and a ClassMethods module: # # module MyGear # def self.setup(s) # # Perform setup actions # end # module ClassMethods # # Define Class Methods here # end # end # def pack(g) G << g include g extend g::ClassMethods if defined?(g::ClassMethods) g.setup(self) if g.respond_to?(:setup) end # Helper method to list gear def gear G end # A hash where you can set different settings. def options O end # Shortcut for setting options: # # module Blog # set :secret, "Hello!" # end def set(k, v) O[k] = v end # When you are running multiple applications, you may want to create # independent modules for each Camping application. Camping::goes # defines a toplevel constant with the whole MVC rack inside: # # require 'camping' # Camping.goes :Nuts # # module Nuts::Controllers; ... end # module Nuts::Models; ... end # module Nuts::Views; ... end # # Additionally, you can pass a Binding as the second parameter, # which enables you to create a Camping-based application within # another module. # # Here's an example of namespacing your web interface and # code for a worker process together: # # module YourApplication # Camping.goes :Web, binding() # module Web # ... # end # module Worker # ... # end # end # # All the applications will be available in Camping::Apps. def goes(m, g=TOPLEVEL_BINDING) Apps << a = eval(S.gsub(/Camping/,m.to_s), g) caller[0]=~/:/ IO.read(a.set:__FILE__,$`)=~/^__END__/ && (b=$'.split(/^@@\s*(.+?)\s*\r?\n/m)).shift rescue nil a.set :_t,H[*b||[]] end end # Views is an empty module for storing methods which create HTML. The HTML # is described using the Markaby language. # # == Defining and calling templates # # Templates are simply Ruby methods with Markaby inside: # # module Blog::Views # def index # p "Welcome to my blog" # end # # def show # h1 @post.title # self << @post.content # end # end # # In your controllers you just call render :template_name which will # invoke the template. The views and controllers will share instance # variables (as you can see above). # # == Using the layout method # # If your Views module has a layout method defined, it will be # called with a block which will insert content from your view: # # module Blog::Views # def layout # html do # head { title "My Blog "} # body { self << yield } # end # end # end module Views; include X, Helpers end # Models is an empty Ruby module for housing model classes derived # from ActiveRecord::Base. As a shortcut, you may derive from Base # which is an alias for ActiveRecord::Base. # # module Camping::Models # class Post < Base; belongs_to :user end # class User < Base; has_many :posts end # end # # == Where Models are Used # # Models are used in your controller classes. However, if your model class # name conflicts with a controller class name, you will need to refer to it # using the Models module. # # module Camping::Controllers # class Post < R '/post/(\d+)' # def get(post_id) # @post = Models::Post.find post_id # render :index # end # end # end # # Models cannot be referred from Views at this time. module Models autoload :Base,'camping/ar' Helpers.send(:include, X, self) end autoload :Mab, 'camping/mab' autoload :Template, 'camping/template' C end camping-2.3/lib/camping.rb000066400000000000000000000105651427044526700155270ustar00rootroot00000000000000require"uri";require"rack";E||="Content-Type";Z||="text/html" class Object;def meta_def m,&b;(class<v,:path=>_p}.update o;end;def []=(k,v)set(k,v,v.is_a?(Hash)?v:{})end end;module Helpers;def R c,*g;p,h= /\(.+?\)/,g.grep(Hash);g-=h;raise"bad route"if !u=c.urls.find{|x|break x if x.scan(p).size==g.size&&/^#{x}\/?$/=~(x=g.inject(x){|x,a|x.sub p,U.escape((a. to_param rescue a))}.gsub(/\\(.)/){$1})};h.any?? u+"?"+U.build_query(h[0]):u end;def / p;p[0]==?/?@root+p :p end;def URL c='/',*a;c=R(c,*a) if c.respond_to?( :urls);c=self/c;c=@request.url[/.{8,}?(?=\/|$)/]+c if c[0]==?/;URI c end end module Base;attr_accessor:env,:request,:root,:input,:cookies,:state,:status, :headers,:body;T={};L=:layout;def lookup n;T.fetch(n.to_sym){|k|t=Views. method_defined?(k)||(t=O[:_t].keys.grep(/^#{n}\./)[0]and Template[t].new{ O[:_t][t]})||(f=Dir[[O[:views]||"views","#{n}.*"]*'/'][0])&&Template. new(f,O[f[/\.(\w+)$/,1].to_sym]||{});O[:dynamic_templates]?t: T[k]=t} end def render v,*a,&b;if t=lookup(v);r=@_r;@_r=o=Hash===a[-1]?a.pop: {};s=(t==true)?mab{ send v,*a,&b}: t.render(self,o[:locals]||{},&b);s=render(L,o.merge(L=>false)){s }if o[L]or o[L].nil?&&lookup(L)&&!r&&v.to_s[0]!=?_;s;else;raise"no template: #{v}" end;end;def mab &b;extend(Mab);mab(&b) end;def r s,b,h={};b,h= h,b if Hash===b;@status=s;@headers.merge!(h);@body=b end;def redirect *a;r 302, '','Location'=>URL(*a).to_s end;def r404 p;P%"#{p} not found"end;def r500 k,m,e raise e end;def r501 m;P%"#{m.upcase} not implemented"end;def serve(p,c) (t=Rack::Mime.mime_type p[/\..*$/],Z)&&@headers[E]=t;c;end;def to_a;@env[ 'rack.session'][SK]=Hash[@state];r=Rack::Response.new(@body,@status,@headers) @cookies._n.each{|k,v|r.set_cookie k,v};r.to_a end;def initialize env,m r=@request=Rack:: Request.new(@env=env);@root,@input,@cookies,@state,@headers, @status,@method=r.script_name.sub(/\/$/,''),n(r.params),Cookies[r.cookies], H[r.session[SK]||{}],{E=>Z},m=~/r(\d+)/?$1.to_i: 200,m;@cookies._p=self/"/" end def n h;Hash===h ?h.inject(H[]){|m,(k,v)|m[k]= n(v);m}: h end;def service *a;r=catch(:halt){send(@method,*a)};@body||=r;self end end;module Controllers;@r=[];class<(u){return u unless u.respond_to? :last;u << "/" unless u.last == "/";u}; A= ->(c,u,p){u.prepend("/"+p) unless c.to_s == "I"}end;N=H.new{|_,x|x.downcase}. merge!("N"=>'(\d+)',"X"=>'([^/]+)',"Index"=>'');def M(pr);def M(pr);end;constants. map{|c|k=const_get(c);k.send:include,C,X,Base,Helpers,Models @r=[k]+@r if @r-[k]==@r;k.meta_def(:urls){[F::T.(F::A.(k,"#{c.to_s.scan(/.[^A-Z]*/) .map(&N.method(:[]))*'/'}",pr))]}if !k.respond_to?:urls}end end;I=R()end;X= Controllers;class<e).to_a end def method_missing m,c,*a;X.M O[:url_prefix];h=Hash===a[-1]?a.pop: {};e=H[Rack::MockRequest. env_for('',h.delete(:env)||{})];k=X.const_get(c).new(e,m.to_s);h.each{|i,v|k. send"#{i}=",v};k.service(*a) end;def use*a,&b;m=a.shift.new(method(:call),*a,&b) meta_def(:call){|e|m.call(e)}end;def pack g;G< e raise MissingLibrary, "ActiveRecord could not be loaded (is it installed?): #{e.message}" end $AR_EXTRAS = %{ Base = ActiveRecord::Base unless const_defined? :Base class ActiveRecordCloser def initialize(app) @app = app end def call(env) @app.call(env) ensure conn = ActiveRecord::Base.connection conn.close if conn.respond_to?(:close) end end Camping.use ActiveRecordCloser class SchemaInfo < Base end def self.V(n) @final = [n, @final.to_f].max m = (@migrations ||= []) Class.new(ActiveRecord::Migration[6.1]) do meta_def(:version) { n } meta_def(:inherited) { |k| m << k } end end def self.create_schema(opts = {}) opts[:assume] ||= 0 opts[:version] ||= @final if @migrations unless SchemaInfo.table_exists? ActiveRecord::Schema.define do create_table SchemaInfo.table_name do |t| t.column :version, :float end end end si = SchemaInfo.first || SchemaInfo.new(:version => opts[:assume]) if si.version < opts[:version] @migrations.sort_by { |m| m.version }.each do |k| k.migrate(:up) if si.version < k.version and k.version <= opts[:version] k.migrate(:down) if si.version > k.version and k.version > opts[:version] end si.update(:version => opts[:version]) end end end } module Camping module Models A = ActiveRecord # Base is an alias for ActiveRecord::Base. The big warning I'm going to give you # about this: *Base overloads table_name_prefix.* This means that if you have a # model class Blog::Models::Post, it's table name will be blog_posts. # # ActiveRecord is not loaded if you never reference this class. The minute you # use the ActiveRecord or Camping::Models::Base class, then the ActiveRecord library # is loaded. Base = A::Base # The default prefix for Camping model classes is the topmost module name lowercase # and followed with an underscore. # # Tepee::Models::Page.table_name_prefix # #=> "tepee_pages" # def Base.table_name_prefix "#{name[/\w+/]}_".downcase.sub(/^(#{A}|camping)_/i,'') end module_eval $AR_EXTRAS end end Camping::S.sub!(/autoload\s*:Base\s*,\s*['"]camping\/ar['"]/, $AR_EXTRAS) Camping::Apps.each do |c| c::Models.module_eval $AR_EXTRAS.gsub('Camping', c.to_s) end camping-2.3/lib/camping/commands.rb000066400000000000000000000054471427044526700173330ustar00rootroot00000000000000module Camping module CommandsHelpers RouteCollection = Struct.new(:routes) class RouteCollection # Displays formatted routes from a route collection # Assumes that Route structs are stored in :routes. def display current_app, current_method = "", "" puts "App VERB Route" routes.each { |r| if current_app != r.app.to_s current_app = r.app.to_s current_method = "" puts "-----------------------------------" puts r.app_header end puts r.padded_message true } end end # Route Struct, for making and formatting a route. Route = Struct.new(:http_method, :controller, :app, :url) class Route def to_s "#{http_method}: #{url}" end # pad the controller name to be the right length, if we can. def padded_message(with_method = false) "#{pad}#{(with_method ? http_method.to_s.upcase.ljust(pad.length, " ") : pad)}#{replace_reg url}" end def app_header "#{app.to_s}" end def controller_header "#{pad}#{app.to_s}::#{controller.to_s}" end protected def http_methods ["get", "post", "put", "patch", "delete"] end def replace_reg(pattern = "") xstr = "([^/]+)"; nstr = "(\d+)" pattern = pattern.gsub(xstr, ":string").gsub("(\\d+)", ":integer") unless pattern == "/" pattern end def pad " " end end class RoutesParser def self.parse(app) new(app).parse end def initialize(app = Camping) @parent_app, @routes = app, [] end def parse @parent_app.routes collected_routes = [] make_routes = -> (a) { a::X.constants.map { |c| k = a::X.const_get(c) im = k.instance_methods(false).map!(&:to_s) methods = im & ["get", "post", "put", "patch", "delete"] if k.respond_to?:urls methods.each { |m| k.urls.each { |u| collected_routes.append Camping::CommandsHelpers::Route.new(m,c,a.to_s,u) } } end } } if @parent_app == Camping @parent_app::Apps.each {|a| make_routes.(a) } else make_routes.(@parent_app) end routes_collection = Camping::CommandsHelpers::RouteCollection.new(collected_routes) end end end class Commands # A helper method to spit out Routes for an application def self.routes(theApp = Camping, silent = false) routes = Camping::CommandsHelpers::RoutesParser.parse theApp routes.display unless silent == true return routes end end end camping-2.3/lib/camping/mab.rb000066400000000000000000000015651427044526700162660ustar00rootroot00000000000000class MissingLibrary < Exception #:nodoc: all end begin require 'mab' rescue LoadError => e raise MissingLibrary, "Mab could not be loaded (is it installed?): #{e.message}" end $MAB_CODE = %q{ module Mab include ::Mab::Mixin::HTML5 include Views alias << text! def xhtml(*a, &b) warn "xhtml_strict is no longer supported (or an active standard); using HTML5 instead" html(*a, &b) end def xhtml_strict(*a, &b) xhtml(*a, &b) end def xhtml_transitional(*a, &b) xhtml(*a, &b) end def xhtml_frameset(*a, &b) xhtml(*a, &b) end def helpers() self end def html(*) doctype!; super end def mab_done(tag) h=tag._attributes [:href,:action,:src].map{|a|h[a]&&=self/h[a]} end end } Camping::S.sub!(/autoload\s*:Mab\s*,\s*['"]camping\/mab['"]/, $MAB_CODE) Camping::Apps.each do |c| c.module_eval $MAB_CODE end camping-2.3/lib/camping/reloader.rb000066400000000000000000000075251427044526700173260ustar00rootroot00000000000000module Camping # == The Camping Reloader # # Camping apps are generally small and predictable. Many Camping apps are # contained within a single file. Larger apps are split into a handful of # other Ruby libraries within the same directory. # # Since Camping apps (and their dependencies) are loaded with Ruby's require # method, there is a record of them in $LOADED_FEATURES. Which leaves a # perfect space for this class to manage auto-reloading an app if any of its # immediate dependencies changes. # # == Wrapping Your Apps # # Since bin/camping and the Camping::Server class already use the Reloader, # you probably don't need to hack it on your own. But, if you're rolling your # own situation, here's how. # # Rather than this: # # require 'yourapp' # # Use this: # # require 'camping/reloader' # reloader = Camping::Reloader.new('/path/to/yourapp.rb') # blog = reloader.apps[:Blog] # wiki = reloader.apps[:Wiki] # # The blog and wiki objects will behave exactly like your # Blog and Wiki, but they will update themselves if yourapp.rb changes. # # You can also give Reloader more than one script. class Reloader attr_reader :file def initialize(file, &blk) @file = file @mtime = Time.at(0) @requires = [] @apps = {} @callback = blk end def name @name ||= begin base = @file.dup base = File.dirname(base) if base =~ /\bconfig\.ru$/ base.sub!(/\.[^.]+/, '') File.basename(base).to_sym end end # Loads the apps available in this script. Use apps to get # the loaded apps. def load_apps(old_apps) all_requires = $LOADED_FEATURES.dup all_apps = Camping::Apps.dup load_file ensure @requires = [] dirs = [] new_apps = Camping::Apps - all_apps @apps = new_apps.inject({}) do |hash, app| if file = app.options[:__FILE__] full = File.expand_path(file) @requires << [file, full] dirs << full.sub(/\.[^.]+$/, '') end key = app.name.to_sym hash[key] = app if !old_apps.include?(key) @callback.call(app) if @callback app.create if app.respond_to?(:create) end hash end ($LOADED_FEATURES - all_requires).each do |req| full = full_path(req) @requires << [req, full] if dirs.any? { |x| full.index(x) == 0 } end @mtime = mtime self end def load_file if @file =~ /\.ru$/ @app,_ = Rack::Builder.parse_file(@file) else load(@file) end end # Removes all the apps defined in this script. def remove_apps @requires.each do |(path, full)| $LOADED_FEATURES.delete(path) end @apps.each do |name, app| Camping::Apps.delete(app) Object.send :remove_const, name end.dup ensure @apps.clear end # Reloads the file if needed. No harm is done by calling this multiple # times, so feel free call just to be sure. def reload return if @mtime >= mtime rescue nil reload! end def reload! load_apps(remove_apps) end # Checks if both scripts watches the same file. def ==(other) @file == other.file end def apps if @app { name => @app } else @apps end end private def mtime @requires.map do |(path, full)| File.mtime(full) end.reject {|t| t > Time.now }.max || Time.now end # Figures out the full path of a required file. def full_path(req) return req if File.exist?(req) dir = $LOAD_PATH.detect { |l| File.exist?(File.join(l, req)) } if dir File.expand_path(req, File.expand_path(dir)) else req end end end end camping-2.3/lib/camping/server.rb000066400000000000000000000207271427044526700170360ustar00rootroot00000000000000require 'irb' require 'erb' require 'rack' require 'camping/reloader' require 'camping/commands' # == The Camping Server (for development) # # Camping includes a pretty nifty server which is built for development. # It follows these rules: # # * Load all Camping apps in a file. # * Mount those apps according to their name. (e.g. Blog is mounted at /blog.) # * Run each app's create method upon startup. # * Reload the app if its modification time changes. # * Reload the app if it requires any files under the same directory and one # of their modification times changes. # * Support the X-Sendfile header. # # Run it like this: # # camping blog.rb # Mounts Blog at / # # And visit http://localhost:3301/ in your browser. module Camping class Server < Rack::Server class Options if home = ENV['HOME'] # POSIX DB = File.join(home, '.camping.db') RC = File.join(home, '.campingrc') elsif home = ENV['APPDATA'] # MSWIN DB = File.join(home, 'Camping.db') RC = File.join(home, 'Campingrc') else DB = nil RC = nil end HOME = File.expand_path(home) + '/' def parse!(args) args = args.dup options = {} opt_parser = OptionParser.new("", 24, ' ') do |opts| opts.banner = "Usage: camping my-camping-app.rb" opts.define_head "#{File.basename($0)}, the microframework ON-button for ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" opts.separator "" opts.separator "Specific options:" opts.on("-h", "--host HOSTNAME", "Host for web server to bind to (default is all IPs)") { |v| options[:Host] = v } opts.on("-p", "--port NUM", "Port for web server (defaults to 3301)") { |v| options[:Port] = v } db = DB.sub(HOME, '~/') if DB opts.on("-d", "--database FILE", "SQLite3 database path (defaults to #{db ? db : ''})") { |db_path| options[:database] = db_path } opts.on("-C", "--console", "Run in console mode with IRB") { options[:server] = "console" } server_list = ["thin", "webrick", "console"] opts.on("-s", "--server NAME", "Server to force (#{server_list.join(', ')})") { |v| options[:server] = v } opts.separator "" opts.separator "Common options:" # No argument, shows at tail. This will print an options summary. # Try it and see! opts.on("-?", "--help", "Show this message") do puts opts exit end # Another typical switch to print the version. opts.on("-m", "--mounting", "Shows Mounting Guide") do puts "Mounting Guide" puts "" puts "To mount your horse, hop up on the side and put it." exit end # Another typical switch to print the version. opts.on("-v", "--version", "Show version") do puts Gem.loaded_specs['camping'].version exit end # Show Routes opts.on("-r", "--routes", "Show Routes") { options[:routes] = true } # Crude start at a generator # opts.separator "" # opts.separator "Generators:" # # generator_list = ["controller", "model", "helper"] # opts.on("-g", "--generator NAME", # "generators (#{generator_list.join(', ')})") { |g| # options[:generator] = g # options[:args] = args # } # # opts.separator "" end opt_parser.parse!(args) # Crude start at a generator command parser thing # if options[:generator] != nil # puts "someone wants to generate." # puts args # Camping::Commands.new(options) # exit # end if args.empty? args << "camp.rb" end options[:script] = args.shift options end end def initialize(*) super @reloader = Camping::Reloader.new(options[:script]) do |app| if !app.options.has_key?(:dynamic_templates) app.options[:dynamic_templates] = true end if !Camping::Models.autoload?(:Base) && options[:database] Camping::Models::Base.establish_connection( :adapter => 'sqlite3', :database => options[:database] ) end end end def opt_parser Options.new end def default_options super.merge({ :Port => 3301, :database => Options::DB }) end def middleware h = super h["development"] << [XSendfile] h end def start # If routes option was chosen to short circut here if options[:routes] == true @reloader.reload! r = @reloader eval("self", TOPLEVEL_BINDING).meta_def(:reload!) { r.reload!; nil } ARGV.clear Camping::Commands.routes exit end if options[:server] == "console" puts "** Starting console" @reloader.reload! r = @reloader eval("self", TOPLEVEL_BINDING).meta_def(:reload!) { r.reload!; nil } ARGV.clear IRB.start exit else name = server.name[/\w+$/] puts "** Starting #{name} on #{options[:Host]}:#{options[:Port]}" super end end # defines the public directory to be /public def public_dir File.expand_path('../public', @reloader.file) end # add the public directory as a Rack app serving files first, then the # current value of self, which is our camping apps, as an app. def app Rack::Cascade.new([Rack::Files.new(public_dir), self], [405, 404, 403]) end # path_matches? # accepts a regular expression string # in our case our apps and controllers def path_matches?(path, *reg) p = T.(path) reg.each do |r| return true if Regexp.new(T.(r)).match?(p) && p == T.(r) end false end # Ensure trailing slash lambda T = -> (u) { if u.last != "/" u << "/" end return u } # call(env) res # == How routing works # # The first app added using Camping.goes is set at the root, we walk through # the defined routes of the first app to see if there is a match. # With no match we then walk through every other defined app. # When we reach a matching route we call that app and Camping's router # handles the rest. # # Mounting apps at different directories is now explicit by setting the # url_prefix option: # # camping.goes :Nuts # Mounts Nuts at / # module Auth # set :url_prefix, "auth/" # end # camping.goes :Auth # Mounts Auth at /auth/ # camping.goes :Blog # Mounts Blog at / # # Note that routes that you set explicitly with R are not prefixed. This # us explicit control over routes: # # module Auth::Controllers # class Whatever < R '/thing/' # Mounts at /thing/ # def get # render :some_view # end # end # end # def call(env) @reloader.reload apps = @reloader.apps # our switch statement iterates through possible app outcomes, no apps # loaded, one app loaded, or multiple apps loaded. case apps.length when 0 [200, {'Content-Type' => 'text/html'}, ["I'm sorry but no apps were found."]] when 1 apps.values.first.call(env) # When we have one else # 2 and up get special treatment apps.each do |name, app| app.routes.each do |r| if (path_matches?(env['PATH_INFO'], r)) return app.call(env) next end end end # Just return the first app if we didn't find a match. return apps.values.first.call(env) end end class XSendfile def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) if key = headers.keys.grep(/X-Sendfile/i).first filename = headers[key] content = open(filename,'rb') { | io | io.read} headers['Content-Length'] = size(content).to_s body = [content] end return status, headers, body end if "".respond_to?(:bytesize) def size(str) str.bytesize end else def size(str) str.size end end end end end camping-2.3/lib/camping/session.rb000066400000000000000000000021201427044526700171760ustar00rootroot00000000000000module Camping # == Getting Started # # To get sessions working for your application: # 1. require 'camping/session' # 2. Define a secret (and keep it secret): set :secret, "SECRET!" # 3. Mixin the module: include Camping::Session # 4. Throughout your application, use the @state var like a hash # to store your application's data. # # require 'camping/session' # 1 # # module Nuts # set :secret, "Oh yeah!" # 2 # include Camping::Session # 3 # end # # == Other backends # # Camping only ships with session-cookies. However, the @state # variable is simply a shortcut for @env['rack.session']. Therefore # you can also use any middleware which sets this variable: # # module Nuts # use Rack::Session::Memcache # end module Session def self.included(app) key = "#{app}.state".downcase secret = app.options[:secret] || [__FILE__, File.mtime(__FILE__)].join(":") app.use Rack::Session::Cookie, :key => key, :secret => secret end end end camping-2.3/lib/camping/template.rb000066400000000000000000000005561427044526700173410ustar00rootroot00000000000000class MissingLibrary < Exception #:nodoc: all end begin require 'tilt' rescue LoadError => e raise MissingLibrary, "Tilt could not be loaded (is it installed?): #{e.message}" end $TILT_CODE = %{ Template = Tilt } Camping::S.sub!(/autoload\s*:Template\s*,\s*['"]camping\/template['"]/, $TILT_CODE) Camping::Apps.each do |c| c.module_eval $TILT_CODE end camping-2.3/lib/campingtrip.md000066400000000000000000000271031427044526700164170ustar00rootroot00000000000000# How does Camping work? This is an academic document written to help people, but mostly me, understand what Camping is doing and in what sequence. Why? Because I want to make camping better, but how do you make it better unless you understand what you've got? # Start Camping starts off with some simple code you type: `camping nuts.rb` into your terminal and the camping gem is loaded and executed. This assumes that you've installed camping via ruby gems: `gem install camping`. Gems are then given a binary command you can use on the command line, in our case **camping**. The camping command accepts a file as an argument and maybe some options. This command calls a *binary* that's found the camping gem, this is what it looks like: ```ruby #!/usr/bin/env ruby $:.unshift File.dirname(__FILE__) + "/../lib" require 'camping' require 'camping/server' begin Camping::Server.start rescue OptionParser::ParseError => ex STDERR.puts "!! #{ex.message}" puts "** use `#{File.basename($0)} --help` for more details..." exit 1 end ``` First wee see `$:.unshift File.dirname(__FILE__) + "/../lib"`, `$:`, is a global variable, that contains the loadpath for scripts. Every ruby file is a script, you may be more familiar with `$LOAD_PATH` which is an alias for the `$:` global. Next `unshift` is an array method that prepends an item to the beginning of an array. `File` is a builtin ruby class that lets you work with files. `File.dirname(file)` returns a string of the complete file path of the file given, except for the file's name. In our case we're using `__FILE__` to return the current file name, which is the binary file in the camping gem. the last portion: `+ "/../lib"` appends the string to the directory path that we just got. All of this is done to ensure that the `lib` folder where all of camping's code resides is added to the script load path. Next we require camping: ```ruby require 'camping' require 'camping/server' ``` Which loads camping and it's server code in to the current script context. ```ruby Camping::Server.start ``` The above code finally Starts the camping server. So let's take a look at the server: ```ruby require 'irb' require 'erb' require 'rack' require 'camping/reloader' require 'camping/commands' # == The Camping Server (for development) # # Camping includes a pretty nifty server which is built for development. # It follows these rules: # # * Load all Camping apps in a file. # * Mount those apps according to their name. (e.g. Blog is mounted at /blog.) # * Run each app's create method upon startup. # * Reload the app if its modification time changes. # * Reload the app if it requires any files under the same directory and one # of their modification times changes. # * Support the X-Sendfile header. # # Run it like this: # # camping blog.rb # Mounts Blog at / # # And visit http://localhost:3301/ in your browser. module Camping class Server < Rack::Server class Options if home = ENV['HOME'] # POSIX DB = File.join(home, '.camping.db') RC = File.join(home, '.campingrc') elsif home = ENV['APPDATA'] # MSWIN DB = File.join(home, 'Camping.db') RC = File.join(home, 'Campingrc') else DB = nil RC = nil end HOME = File.expand_path(home) + '/' def parse!(args) args = args.dup options = {} opt_parser = OptionParser.new("", 24, ' ') do |opts| opts.banner = "Usage: camping my-camping-app.rb" opts.define_head "#{File.basename($0)}, the microframework ON-button for ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]" opts.separator "" opts.separator "Specific options:" opts.on("-h", "--host HOSTNAME", "Host for web server to bind to (default is all IPs)") { |v| options[:Host] = v } opts.on("-p", "--port NUM", "Port for web server (defaults to 3301)") { |v| options[:Port] = v } db = DB.sub(HOME, '~/') if DB opts.on("-d", "--database FILE", "SQLite3 database path (defaults to #{db ? db : ''})") { |db_path| options[:database] = db_path } opts.on("-C", "--console", "Run in console mode with IRB") { options[:server] = "console" } server_list = ["thin", "webrick", "console"] opts.on("-s", "--server NAME", "Server to force (#{server_list.join(', ')})") { |v| options[:server] = v } opts.separator "" opts.separator "Common options:" # No argument, shows at tail. This will print an options summary. # Try it and see! opts.on("-?", "--help", "Show this message") do puts opts exit end # Another typical switch to print the version. opts.on("-m", "--mounting", "Shows Mounting Guide") do puts "Mounting Guide" puts "" puts "To mount your horse, hop up on the side and put it." exit end # Another typical switch to print the version. opts.on("-v", "--version", "Show version") do puts Gem.loaded_specs['camping'].version exit end end opt_parser.parse!(args) # If no Arguments were called. if args.empty? args << "cabin.rb" # adds cabin.rb as a default camping entrance file end # Parses the first argument as the script to load into the server. options[:script] = args.shift options end end def initialize(*) super @reloader = Camping::Reloader.new(options[:script]) do |app| if !app.options.has_key?(:dynamic_templates) app.options[:dynamic_templates] = true end if !Camping::Models.autoload?(:Base) && options[:database] Camping::Models::Base.establish_connection( :adapter => 'sqlite3', :database => options[:database] ) end end end def opt_parser Options.new end def default_options super.merge({ :Port => 3301, :database => Options::DB }) end def middleware h = super h["development"] << [XSendfile] h end def start if options[:server] == "console" puts "** Starting console" @reloader.reload! r = @reloader eval("self", TOPLEVEL_BINDING).meta_def(:reload!) { r.reload!; nil } ARGV.clear IRB.start exit else name = server.name[/\w+$/] puts "** Starting #{name} on #{options[:Host]}:#{options[:Port]}" super end end # defines the public directory to be /public def public_dir File.expand_path('../public', @reloader.file) end # add the public directory as a Rack app serving files first, then the # current value of self, which is our camping apps, as an app. def app Rack::Cascade.new([Rack::Files.new(public_dir), self], [405, 404, 403]) end # path_matches? # accepts a regular expression string # in our case our apps and controllers def path_matches?(path, *reg) reg.each do |r| return true if Regexp.new(r).match? path end false end # call(env) res # == How routing works # # The first app added using Camping.goes is set at the root, we walk through # the defined routes of the first app to see if there is a match. # With no match we then walk through every other defined app. # Each subsequent app defined is loaded at a directory named after them: # # camping.goes :Nuts # Mounts Nuts at / # camping.goes :Auth # Mounts Auth at /auth/ # camping.goes :Blog # Mounts Blog at /blog/ # def call(env) @reloader.reload apps = @reloader.apps # our switch statement iterates through possible app outcomes, no apps # loaded, one app loaded, or multiple apps loaded. case apps.length when 0 [200, {'Content-Type' => 'text/html'}, ["I'm sorry but no apps were found."]] when 1 apps.values.first.call(env) # When we have one else # 2 and up get special treatment count = 0 apps.each do |name, app| if count == 0 app.routes.each do |r| if (path_matches?(env['PATH_INFO'], r)) next end return app.call(env) unless !(path_matches?(env['PATH_INFO'], r)) end else mount = name.to_s.downcase case env["PATH_INFO"] when %r{^/#{mount}} env["SCRIPT_NAME"] = env["SCRIPT_NAME"] + $& env["PATH_INFO"] = $' return app.call(env) when %r{^/code/#{mount}} return [200, {'Content-Type' => 'text/plain', 'X-Sendfile' => @reloader.file}, []] end end count += 1 end # Just return the first app if we didn't find a match. return apps.values.first.call(env) end end class XSendfile def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) if key = headers.keys.grep(/X-Sendfile/i).first filename = headers[key] content = open(filename,'rb') { | io | io.read} headers['Content-Length'] = size(content).to_s body = [content] end return status, headers, body end if "".respond_to?(:bytesize) def size(str) str.bytesize end else def size(str) str.size end end end end end ``` The beginning of Server loads the required code to get Camping started, and then opens the `Camping` module: ```ruby module Camping class Server < Rack::Server end end ``` `Server` inherits from `Rack::Server`. Camping is Rack based to give ourselves a predictable interface for our web server code. Consequentally a lot of utilities useful for webservers are just baked into Rack, It gives Camping the chance to do what it does best, magic! The first class declared in `Server` is called `Options`. It's in charge of parsing the command line options supplied to camping, and then supplying those options as a hash for the program further down the line. This class is declared inside of the `Server` class so that we can encapsulate the behaviour of options within the server. Which is pretty nice. Next is initialize: ```ruby def initialize(*) super @reloader = Camping::Reloader.new(options[:script]) do |app| if !app.options.has_key?(:dynamic_templates) app.options[:dynamic_templates] = true end if !Camping::Models.autoload?(:Base) && options[:database] Camping::Models::Base.establish_connection( :adapter => 'sqlite3', :database => options[:database] ) end end end ``` `initialize` is the method called whenever you instantiate a new object of a class. You'll notice that the line of code in the method is a call to `super`. Because `Camping::Server` is a subclass of `Rack::Server`, and to get things rolling we first call `Rack::Server`'s initialize. Afterwards we setup the reloader, and optionally include the database. When you call super naked like that it passes along whatever arguments were sent to the method that called super. In our case It's a splat: `initialize(*)`, so everything is sent along. Remember earlier from the Camping binary where we start the server? : `Camping::Server.start`, You may notice that this is a call to a class method `start`, but we don't declare any class methods in `Camping::Server` only instance methods. camping-2.3/setup.rb000066400000000000000000001036651427044526700145070ustar00rootroot00000000000000# # setup.rb # # Copyright (c) 2000-2005 Minero Aoki # # This program is free software. # You can distribute/modify this program under the terms of # the GNU LGPL, Lesser General Public License version 2.1. # unless Enumerable.method_defined?(:map) # Ruby 1.4.6 module Enumerable alias map collect end end unless File.respond_to?(:read) # Ruby 1.6 def File.read(fname) open(fname) {|f| return f.read } end end unless Errno.const_defined?(:ENOTEMPTY) # Windows? module Errno class ENOTEMPTY # We do not raise this exception, implementation is not needed. end end end def File.binread(fname) open(fname, 'rb') {|f| return f.read } end # for corrupted Windows' stat(2) def File.dir?(path) File.directory?((path[-1,1] == '/') ? path : path + '/') end class ConfigTable include Enumerable def initialize(rbconfig) @rbconfig = rbconfig @items = [] @table = {} # options @install_prefix = nil @config_opt = nil @verbose = true @no_harm = false @libsrc_pattern = '*.rb' end attr_accessor :install_prefix attr_accessor :config_opt attr_writer :verbose def verbose? @verbose end attr_writer :no_harm def no_harm? @no_harm end attr_accessor :libsrc_pattern def [](key) lookup(key).resolve(self) end def []=(key, val) lookup(key).set val end def names @items.map {|i| i.name } end def each(&block) @items.each(&block) end def key?(name) @table.key?(name) end def lookup(name) @table[name] or setup_rb_error "no such config item: #{name}" end def add(item) @items.push item @table[item.name] = item end def remove(name) item = lookup(name) @items.delete_if {|i| i.name == name } @table.delete_if {|name, i| i.name == name } item end def load_script(path, inst = nil) if File.file?(path) MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path end end def savefile '.config' end def load_savefile begin File.foreach(savefile()) do |line| k, v = *line.split(/=/, 2) self[k] = v.strip end rescue Errno::ENOENT setup_rb_error $!.message + "\n#{File.basename($0)} config first" end end def save @items.each {|i| i.value } File.open(savefile(), 'w') {|f| @items.each do |i| f.printf "%s=%s\n", i.name, i.value if i.value? and i.value end } end def load_standard_entries standard_entries(@rbconfig).each do |ent| add ent end end def standard_entries(rbconfig) c = rbconfig rubypath = c['bindir'] + '/' + c['ruby_install_name'] major = c['MAJOR'].to_i minor = c['MINOR'].to_i teeny = c['TEENY'].to_i version = "#{major}.#{minor}" # ruby ver. >= 1.4.4? newpath_p = ((major >= 2) or ((major == 1) and ((minor >= 5) or ((minor == 4) and (teeny >= 4))))) if c['rubylibdir'] # V > 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = c['rubylibdir'] librubyverarch = c['archdir'] siteruby = c['sitedir'] siterubyver = c['sitelibdir'] siterubyverarch = c['sitearchdir'] elsif newpath_p # 1.4.4 <= V <= 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = c['sitedir'] siterubyver = "$siteruby/#{version}" siterubyverarch = "$siterubyver/#{c['arch']}" else # V < 1.4.4 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" siterubyver = siteruby siterubyverarch = "$siterubyver/#{c['arch']}" end parameterize = lambda {|path| path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') } if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } makeprog = arg.sub(/'/, '').split(/=/, 2)[1] else makeprog = 'make' end [ ExecItem.new('installdirs', 'std/site/home', 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ {|val, table| case val when 'std' table['rbdir'] = '$librubyver' table['sodir'] = '$librubyverarch' when 'site' table['rbdir'] = '$siterubyver' table['sodir'] = '$siterubyverarch' when 'home' setup_rb_error '$HOME was not set' unless ENV['HOME'] table['prefix'] = ENV['HOME'] table['rbdir'] = '$libdir/ruby' table['sodir'] = '$libdir/ruby' end }, PathItem.new('prefix', 'path', c['prefix'], 'path prefix of target environment'), PathItem.new('bindir', 'path', parameterize.call(c['bindir']), 'the directory for commands'), PathItem.new('libdir', 'path', parameterize.call(c['libdir']), 'the directory for libraries'), PathItem.new('datadir', 'path', parameterize.call(c['datadir']), 'the directory for shared data'), PathItem.new('mandir', 'path', parameterize.call(c['mandir']), 'the directory for man pages'), PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), 'the directory for system configuration files'), PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), 'the directory for local state data'), PathItem.new('libruby', 'path', libruby, 'the directory for ruby libraries'), PathItem.new('librubyver', 'path', librubyver, 'the directory for standard ruby libraries'), PathItem.new('librubyverarch', 'path', librubyverarch, 'the directory for standard ruby extensions'), PathItem.new('siteruby', 'path', siteruby, 'the directory for version-independent aux ruby libraries'), PathItem.new('siterubyver', 'path', siterubyver, 'the directory for aux ruby libraries'), PathItem.new('siterubyverarch', 'path', siterubyverarch, 'the directory for aux ruby binaries'), PathItem.new('rbdir', 'path', '$siterubyver', 'the directory for ruby scripts'), PathItem.new('sodir', 'path', '$siterubyverarch', 'the directory for ruby extentions'), PathItem.new('rubypath', 'path', rubypath, 'the path to set to #! line'), ProgramItem.new('rubyprog', 'name', rubypath, 'the ruby program using for installation'), ProgramItem.new('makeprog', 'name', makeprog, 'the make program to compile ruby extentions'), SelectItem.new('shebang', 'all/ruby/never', 'ruby', 'shebang line (#!) editing mode'), BoolItem.new('without-ext', 'yes/no', 'no', 'does not compile/install ruby extentions') ] end private :standard_entries def load_multipackage_entries multipackage_entries().each do |ent| add ent end end def multipackage_entries [ PackageSelectionItem.new('with', 'name,name...', '', 'ALL', 'package names that you want to install'), PackageSelectionItem.new('without', 'name,name...', '', 'NONE', 'package names that you do not want to install') ] end private :multipackage_entries ALIASES = { 'std-ruby' => 'librubyver', 'stdruby' => 'librubyver', 'rubylibdir' => 'librubyver', 'archdir' => 'librubyverarch', 'site-ruby-common' => 'siteruby', # For backward compatibility 'site-ruby' => 'siterubyver', # For backward compatibility 'bin-dir' => 'bindir', 'bin-dir' => 'bindir', 'rb-dir' => 'rbdir', 'so-dir' => 'sodir', 'data-dir' => 'datadir', 'ruby-path' => 'rubypath', 'ruby-prog' => 'rubyprog', 'ruby' => 'rubyprog', 'make-prog' => 'makeprog', 'make' => 'makeprog' } def fixup ALIASES.each do |ali, name| @table[ali] = @table[name] end @items.freeze @table.freeze @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ end def parse_opt(opt) m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" m.to_a[1,2] end def dllext @rbconfig['DLEXT'] end def value_config?(name) lookup(name).value? end class Item def initialize(name, template, default, desc) @name = name.freeze @template = template @value = default @default = default @description = desc end attr_reader :name attr_reader :description attr_accessor :default alias help_default default def help_opt "--#{@name}=#{@template}" end def value? true end def value @value end def resolve(table) @value.gsub(%r<\$([^/]+)>) { table[$1] } end def set(val) @value = check(val) end private def check(val) setup_rb_error "config: --#{name} requires argument" unless val val end end class BoolItem < Item def config_type 'bool' end def help_opt "--#{@name}" end private def check(val) return 'yes' unless val unless /\A(y(es)?|n(o)?|t(rue)?|f(alse))\z/i =~ val setup_rb_error "config: --#{@name} accepts only yes/no for argument" end (/\Ay(es)?|\At(rue)/i =~ value) ? 'yes' : 'no' end end class PathItem < Item def config_type 'path' end private def check(path) setup_rb_error "config: --#{@name} requires argument" unless path path[0,1] == '$' ? path : File.expand_path(path) end end class ProgramItem < Item def config_type 'program' end end class SelectItem < Item def initialize(name, selection, default, desc) super @ok = selection.split('/') end def config_type 'select' end private def check(val) unless @ok.include?(val.strip) setup_rb_error "config: use --#{@name}=#{@template} (#{val})" end val.strip end end class ExecItem < Item def initialize(name, selection, desc, &block) super name, selection, nil, desc @ok = selection.split('/') @action = block end def config_type 'exec' end def value? false end def resolve(table) setup_rb_error "$#{name()} wrongly used as option value" end undef set def evaluate(val, table) v = val.strip.downcase unless @ok.include?(v) setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" end @action.call v, table end end class PackageSelectionItem < Item def initialize(name, template, default, help_default, desc) super name, template, default, desc @help_default = help_default end attr_reader :help_default def config_type 'package' end private def check(val) unless File.dir?("packages/#{val}") setup_rb_error "config: no such package: #{val}" end val end end class MetaConfigEnvironment def intiailize(config, installer) @config = config @installer = installer end def config_names @config.names end def config?(name) @config.key?(name) end def bool_config?(name) @config.lookup(name).config_type == 'bool' end def path_config?(name) @config.lookup(name).config_type == 'path' end def value_config?(name) @config.lookup(name).config_type != 'exec' end def add_config(item) @config.add item end def add_bool_config(name, default, desc) @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) end def add_path_config(name, default, desc) @config.add PathItem.new(name, 'path', default, desc) end def set_config_default(name, default) @config.lookup(name).default = default end def remove_config(name) @config.remove(name) end # For only multipackage def packages raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer @installer.packages end # For only multipackage def declare_packages(list) raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer @installer.packages = list end end end # class ConfigTable # This module requires: #verbose?, #no_harm? module FileOperations def mkdir_p(dirname, prefix = nil) dirname = prefix + File.expand_path(dirname) if prefix $stderr.puts "mkdir -p #{dirname}" if verbose? return if no_harm? # Does not check '/', it's too abnormal. dirs = File.expand_path(dirname).split(%r<(?=/)>) if /\A[a-z]:\z/i =~ dirs[0] disk = dirs.shift dirs[0] = disk + dirs[0] end dirs.each_index do |idx| path = dirs[0..idx].join('') Dir.mkdir path unless File.dir?(path) end end def rm_f(path) $stderr.puts "rm -f #{path}" if verbose? return if no_harm? force_remove_file path end def rm_rf(path) $stderr.puts "rm -rf #{path}" if verbose? return if no_harm? remove_tree path end def remove_tree(path) if File.symlink?(path) remove_file path elsif File.dir?(path) remove_tree0 path else force_remove_file path end end def remove_tree0(path) Dir.foreach(path) do |ent| next if ent == '.' next if ent == '..' entpath = "#{path}/#{ent}" if File.symlink?(entpath) remove_file entpath elsif File.dir?(entpath) remove_tree0 entpath else force_remove_file entpath end end begin Dir.rmdir path rescue Errno::ENOTEMPTY # directory may not be empty end end def move_file(src, dest) force_remove_file dest begin File.rename src, dest rescue File.open(dest, 'wb') {|f| f.write File.binread(src) } File.chmod File.stat(src).mode, dest File.unlink src end end def force_remove_file(path) begin remove_file path rescue end end def remove_file(path) File.chmod 0777, path File.unlink path end def install(from, dest, mode, prefix = nil) $stderr.puts "install #{from} #{dest}" if verbose? return if no_harm? realdest = prefix ? prefix + File.expand_path(dest) : dest realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) str = File.binread(from) if diff?(str, realdest) verbose_off { rm_f realdest if File.exist?(realdest) } File.open(realdest, 'wb') {|f| f.write str } File.chmod mode, realdest File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| if prefix f.puts realdest.sub(prefix, '') else f.puts realdest end } end end def diff?(new_content, path) return true unless File.exist?(path) new_content != File.binread(path) end def command(*args) $stderr.puts args.join(' ') if verbose? system(*args) or raise RuntimeError, "system(#{args.map{|a| a.inspect }.join(' ')}) failed" end def ruby(*args) command config('rubyprog'), *args end def make(task = nil) command(*[config('makeprog'), task].compact) end def extdir?(dir) File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") end def files_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.file?("#{dir}/#{ent}") } } end DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) def directories_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT } end end # This module requires: #srcdir_root, #objdir_root, #relpath module HookScriptAPI def get_config(key) @config[key] end alias config get_config # obsolete: use metaconfig to change configuration def set_config(key, val) @config[key] = val end # # srcdir/objdir (works only in the package directory) # def curr_srcdir "#{srcdir_root()}/#{relpath()}" end def curr_objdir "#{objdir_root()}/#{relpath()}" end def srcfile(path) "#{curr_srcdir()}/#{path}" end def srcexist?(path) File.exist?(srcfile(path)) end def srcdirectory?(path) File.dir?(srcfile(path)) end def srcfile?(path) File.file?(srcfile(path)) end def srcentries(path = '.') Dir.open("#{curr_srcdir()}/#{path}") {|d| return d.to_a - %w(. ..) } end def srcfiles(path = '.') srcentries(path).select {|fname| File.file?(File.join(curr_srcdir(), path, fname)) } end def srcdirectories(path = '.') srcentries(path).select {|fname| File.dir?(File.join(curr_srcdir(), path, fname)) } end end class ToplevelInstaller Version = '3.4.0' Copyright = 'Copyright (c) 2000-2005 Minero Aoki' TASKS = [ [ 'all', 'do config, setup, then install' ], [ 'config', 'saves your configurations' ], [ 'show', 'shows current configuration' ], [ 'setup', 'compiles ruby extentions and others' ], [ 'install', 'installs files' ], [ 'test', 'run all tests in test/' ], [ 'clean', "does `make clean' for each extention" ], [ 'distclean',"does `make distclean' for each extention" ] ] def ToplevelInstaller.invoke config = ConfigTable.new(load_rbconfig()) config.load_standard_entries config.load_multipackage_entries if multipackage? config.fixup klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) klass.new(File.dirname($0), config).invoke end def ToplevelInstaller.multipackage? File.dir?(File.dirname($0) + '/packages') end def ToplevelInstaller.load_rbconfig if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } ARGV.delete(arg) load File.expand_path(arg.split(/=/, 2)[1]) $".push 'rbconfig.rb' else require 'rbconfig' end ::Config::CONFIG end def initialize(ardir_root, config) @ardir = File.expand_path(ardir_root) @config = config # cache @valid_task_re = nil end def config(key) @config[key] end def inspect "#<#{self.class} #{__id__()}>" end def invoke run_metaconfigs case task = parsearg_global() when nil, 'all' parsearg_config init_installers exec_config exec_setup exec_install else case task when 'config', 'test' ; when 'clean', 'distclean' @config.load_savefile if File.exist?(@config.savefile) else @config.load_savefile end __send__ "parsearg_#{task}" init_installers __send__ "exec_#{task}" end end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig" end def init_installers @installer = Installer.new(@config, @ardir, File.expand_path('.')) end # # Hook Script API bases # def srcdir_root @ardir end def objdir_root '.' end def relpath '.' end # # Option Parsing # def parsearg_global while arg = ARGV.shift case arg when /\A\w+\z/ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) return arg when '-q', '--quiet' @config.verbose = false when '--verbose' @config.verbose = true when '--help' print_usage $stdout exit 0 when '--version' puts "#{File.basename($0)} version #{Version}" exit 0 when '--copyright' puts Copyright exit 0 else setup_rb_error "unknown global option '#{arg}'" end end nil end def valid_task?(t) valid_task_re() =~ t end def valid_task_re @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ end def parsearg_no_options unless ARGV.empty? setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" end end alias parsearg_show parsearg_no_options alias parsearg_setup parsearg_no_options alias parsearg_test parsearg_no_options alias parsearg_clean parsearg_no_options alias parsearg_distclean parsearg_no_options def parsearg_config evalopt = [] set = [] @config.config_opt = [] while i = ARGV.shift if /\A--?\z/ =~ i @config.config_opt = ARGV.dup break end name, value = *@config.parse_opt(i) if @config.value_config?(name) @config[name] = value else evalopt.push [name, value] end set.push name end evalopt.each do |name, value| @config.lookup(name).evaluate value, @config end # Check if configuration is valid set.each do |n| @config[n] if @config.value_config?(n) end end def parsearg_install @config.no_harm = false @config.install_prefix = '' while a = ARGV.shift case a when '--no-harm' @config.no_harm = true when /\A--prefix=/ path = a.split(/=/, 2)[1] path = File.expand_path(path) unless path[0,1] == '/' @config.install_prefix = path else setup_rb_error "install: unknown option #{a}" end end end def print_usage(out) out.puts 'Typical Installation Procedure:' out.puts " $ ruby #{File.basename $0} config" out.puts " $ ruby #{File.basename $0} setup" out.puts " # ruby #{File.basename $0} install (may require root privilege)" out.puts out.puts 'Detailed Usage:' out.puts " ruby #{File.basename $0} " out.puts " ruby #{File.basename $0} [] []" fmt = " %-24s %s\n" out.puts out.puts 'Global options:' out.printf fmt, '-q,--quiet', 'suppress message outputs' out.printf fmt, ' --verbose', 'output messages verbosely' out.printf fmt, ' --help', 'print this message' out.printf fmt, ' --version', 'print version and quit' out.printf fmt, ' --copyright', 'print copyright and quit' out.puts out.puts 'Tasks:' TASKS.each do |name, desc| out.printf fmt, name, desc end fmt = " %-24s %s [%s]\n" out.puts out.puts 'Options for CONFIG or ALL:' @config.each do |item| out.printf fmt, item.help_opt, item.description, item.help_default end out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" out.puts out.puts 'Options for INSTALL:' out.printf fmt, '--no-harm', 'only display what to do if given', 'off' out.printf fmt, '--prefix=path', 'install path prefix', '' out.puts end # # Task Handlers # def exec_config @installer.exec_config @config.save # must be final end def exec_setup @installer.exec_setup end def exec_install @installer.exec_install end def exec_test @installer.exec_test end def exec_show @config.each do |i| printf "%-20s %s\n", i.name, i.value if i.value? end end def exec_clean @installer.exec_clean end def exec_distclean @installer.exec_distclean end end # class ToplevelInstaller class ToplevelInstallerMulti < ToplevelInstaller include FileOperations def initialize(ardir_root, config) super @packages = directories_of("#{@ardir}/packages") raise 'no package exists' if @packages.empty? @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig", self @packages.each do |name| @config.load_script "#{@ardir}/packages/#{name}/metaconfig" end end attr_reader :packages def packages=(list) raise 'package list is empty' if list.empty? list.each do |name| raise "directory packages/#{name} does not exist"\ unless File.dir?("#{@ardir}/packages/#{name}") end @packages = list end def init_installers @installers = {} @packages.each do |pack| @installers[pack] = Installer.new(@config, "#{@ardir}/packages/#{pack}", "packages/#{pack}") end with = extract_selection(config('with')) without = extract_selection(config('without')) @selected = @installers.keys.select {|name| (with.empty? or with.include?(name)) \ and not without.include?(name) } end def extract_selection(list) a = list.split(/,/) a.each do |name| setup_rb_error "no such package: #{name}" unless @installers.key?(name) end a end def print_usage(f) super f.puts 'Inluded packages:' f.puts ' ' + @packages.sort.join(' ') f.puts end # # Task Handlers # def exec_config run_hook 'pre-config' each_selected_installers {|inst| inst.exec_config } run_hook 'post-config' @config.save # must be final end def exec_setup run_hook 'pre-setup' each_selected_installers {|inst| inst.exec_setup } run_hook 'post-setup' end def exec_install run_hook 'pre-install' each_selected_installers {|inst| inst.exec_install } run_hook 'post-install' end def exec_test run_hook 'pre-test' each_selected_installers {|inst| inst.exec_test } run_hook 'post-test' end def exec_clean rm_f @config.savefile run_hook 'pre-clean' each_selected_installers {|inst| inst.exec_clean } run_hook 'post-clean' end def exec_distclean rm_f @config.savefile run_hook 'pre-distclean' each_selected_installers {|inst| inst.exec_distclean } run_hook 'post-distclean' end # # lib # def each_selected_installers Dir.mkdir 'packages' unless File.dir?('packages') @selected.each do |pack| $stderr.puts "Processing the package `#{pack}' ..." if verbose? Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") Dir.chdir "packages/#{pack}" yield @installers[pack] Dir.chdir '../..' end end def run_hook(id) @root_installer.run_hook id end # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end end # class ToplevelInstallerMulti class Installer FILETYPES = %w( bin lib ext data conf man ) include FileOperations include HookScriptAPI def initialize(config, srcroot, objroot) @config = config @srcdir = File.expand_path(srcroot) @objdir = File.expand_path(objroot) @currdir = '.' end def inspect "#<#{self.class} #{File.basename(@srcdir)}>" end # # Hook Script API base methods # def srcdir_root @srcdir end def objdir_root @objdir end def relpath @currdir end # # Config Access # # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end def verbose_off begin save, @config.verbose = @config.verbose?, false yield ensure @config.verbose = save end end # # TASK config # def exec_config exec_task_traverse 'config' end def config_dir_bin(rel) end def config_dir_lib(rel) end def config_dir_man(rel) end def config_dir_ext(rel) extconf if extdir?(curr_srcdir()) end def extconf ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt end def config_dir_data(rel) end def config_dir_conf(rel) end # # TASK setup # def exec_setup exec_task_traverse 'setup' end def setup_dir_bin(rel) files_of(curr_srcdir()).each do |fname| adjust_shebang "#{curr_srcdir()}/#{fname}" end end def adjust_shebang(path) return if no_harm? tmpfile = File.basename(path) + '.tmp' begin File.open(path, 'rb') {|r| first = r.gets return unless File.basename(first.sub(/\A\#!/, '').split[0].to_s) == 'ruby' $stderr.puts "adjusting shebang: #{File.basename(path)}" if verbose? File.open(tmpfile, 'wb') {|w| w.print first.sub(/\A\#!\s*\S+/, '#! ' + config('rubypath')) w.write r.read } } move_file tmpfile, File.basename(path) ensure File.unlink tmpfile if File.exist?(tmpfile) end end def setup_dir_lib(rel) end def setup_dir_man(rel) end def setup_dir_ext(rel) make if extdir?(curr_srcdir()) end def setup_dir_data(rel) end def setup_dir_conf(rel) end # # TASK install # def exec_install rm_f 'InstalledFiles' exec_task_traverse 'install' end def install_dir_bin(rel) install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 end def install_dir_lib(rel) install_files rubyscripts(), "#{config('rbdir')}/#{rel}", 0644 end def install_dir_ext(rel) return unless extdir?(curr_srcdir()) install_files rubyextentions('.'), "#{config('sodir')}/#{File.dirname(rel)}", 0555 end def install_dir_data(rel) install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 end def install_dir_conf(rel) # FIXME: should not remove current config files # (rename previous file to .old/.org) install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 end def install_dir_man(rel) install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 end def install_files(list, dest, mode) mkdir_p dest, @config.install_prefix list.each do |fname| install fname, dest, mode, @config.install_prefix end end def rubyscripts glob_select(@config.libsrc_pattern, targetfiles()) end def rubyextentions(dir) ents = glob_select("*.#{@config.dllext}", targetfiles()) if ents.empty? setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" end ents end def targetfiles mapdir(existfiles() - hookfiles()) end def mapdir(ents) ents.map {|ent| if File.exist?(ent) then ent # objdir else "#{curr_srcdir()}/#{ent}" # srcdir end } end # picked up many entries from cvs-1.11.1/src/ignore.c JUNK_FILES = %w( core RCSLOG tags TAGS .make.state .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb *~ *.old *.bak *.BAK *.orig *.rej _$* *$ *.org *.in .* ) def existfiles glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) end def hookfiles %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| %w( config setup install clean ).map {|t| sprintf(fmt, t) } }.flatten end def glob_select(pat, ents) re = globs2re([pat]) ents.select {|ent| re =~ ent } end def glob_reject(pats, ents) re = globs2re(pats) ents.reject {|ent| re =~ ent } end GLOB2REGEX = { '.' => '\.', '$' => '\$', '#' => '\#', '*' => '.*' } def globs2re(pats) /\A(?:#{ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') })\z/ end # # TASK test # TESTDIR = 'test' def exec_test unless File.directory?('test') $stderr.puts 'no test in this package' if verbose? return end $stderr.puts 'Running tests...' if verbose? require 'test/unit' runner = Test::Unit::AutoRunner.new(true) runner.to_run << TESTDIR runner.run end # # TASK clean # def exec_clean exec_task_traverse 'clean' rm_f @config.savefile rm_f 'InstalledFiles' end def clean_dir_bin(rel) end def clean_dir_lib(rel) end def clean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'clean' if File.file?('Makefile') end def clean_dir_data(rel) end def clean_dir_conf(rel) end # # TASK distclean # def exec_distclean exec_task_traverse 'distclean' rm_f @config.savefile rm_f 'InstalledFiles' end def distclean_dir_bin(rel) end def distclean_dir_lib(rel) end def distclean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'distclean' if File.file?('Makefile') end def distclean_dir_data(rel) end def distclean_dir_conf(rel) end # # lib # def exec_task_traverse(task) run_hook "pre-#{task}" FILETYPES.each do |type| if config('without-ext') == 'yes' and type == 'ext' $stderr.puts 'skipping ext/* by user option' if verbose? next end traverse task, type, "#{task}_dir_#{type}" end run_hook "post-#{task}" end def traverse(task, rel, mid) dive_into(rel) { run_hook "pre-#{task}" __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') directories_of(curr_srcdir()).each do |d| traverse task, "#{rel}/#{d}", mid end run_hook "post-#{task}" } end def dive_into(rel) return unless File.dir?("#{@srcdir}/#{rel}") dir = File.basename(rel) Dir.mkdir dir unless File.dir?(dir) prevdir = Dir.pwd Dir.chdir dir $stderr.puts '---> ' + rel if verbose? @currdir = rel yield Dir.chdir prevdir $stderr.puts '<--- ' + rel if verbose? @currdir = File.dirname(rel) end def run_hook(id) path = [ "#{curr_srcdir()}/#{id}", "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } return unless path begin instance_eval File.read(path), path, 1 rescue raise if $DEBUG setup_rb_error "hook #{path} failed:\n" + $!.message end end end # class Installer class SetupError < StandardError; end def setup_rb_error(msg) raise SetupError, msg end if $0 == __FILE__ begin ToplevelInstaller.invoke rescue SetupError raise if $DEBUG $stderr.puts $!.message $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." exit 1 end end camping-2.3/test/000077500000000000000000000000001427044526700137665ustar00rootroot00000000000000camping-2.3/test/app_camping_gear.rb000066400000000000000000000036411427044526700175730ustar00rootroot00000000000000require 'test_helper' require 'camping' Camping.goes :Packing module Camping module Gear # Basically copied from Cuba, will probably modify later. module CSRF # Package Class Methods module ClassMethods # define class methods def secret_token @_secret_token ||= SecureRandom.base64(32) end def erase_token @_secret_token = nil end def set_secret(secret) @_secret_token = secret end end # Run a setup routine with this Gear. def self.setup(app) @app = app @app.set :secret_token, "top_secret_code" end # Adds an instance method csrf def csrf @csrf ||= Camping::Gear::CSRF::Helper.new(@state, @request) end class Helper attr_accessor :req attr_accessor :state def initialize(state, request) @state = state @req = request end end end end end module Packing pack Camping::Gear::CSRF end class Packing::Test < TestCase def test_gear_packed list = Packing::G assert (list.length == 1), "Gear was not packed! Gear: #{list.length}" end def test_right_gear_packed csrf_gear = Packing::G[0].to_s assert (csrf_gear == "Camping::Gear::CSRF"), "The correct Gear was not packed! Gear: #{csrf_gear}" end def test_instance_methods_packed im = Packing.instance_methods.map(&:to_s) assert (im.include? "csrf"), "Gear instance methods were not included: #{im}" end def test_class_methods_packed [:secret_token, :erase_token, :set_secret].each { |sym| assert (Packing.methods.include? sym), "Gear class methods were not packed, missing #{sym.to_s}." } end def test_setup_callback secret = Packing.options[:secret_token] assert (secret == "top_secret_code"), "Gear setup callback failed: \"#{secret}\" should be \"top_secret_code\"." end end camping-2.3/test/app_cookies.rb000066400000000000000000000021531427044526700166100ustar00rootroot00000000000000require 'test_helper' require 'camping' Camping.goes :Cookies module Cookies::Controllers class One def get @cookies.simple = '42' @cookies.set :complex, '43' @cookies.set :past, 'past', :expires => Time.now - 5 render :show end end class Two def get render :show end end class Old def get @cookies.simple = '42' @cookies.complex = { :value => '43' } @cookies.past = { :value => 'past', :expires => Time.now - 5 } @cookies.past.class.name end end end module Cookies::Views def show @cookies.values_at('simple', 'complex', 'past').inspect end end class Cookies::Test < TestCase def test_cookies get '/one' assert_body '["42", "43", "past"]' get '/two' assert_body '["42", "43", nil]' end def test_backward_compatible get '/old' assert_body 'Hash' get '/two' assert_body '["42", "43", nil]' end def test_path get '/one', {}, 'SCRIPT_NAME' => '/mnt' assert_body '["42", "43", "past"]' assert_equal 3, last_response.headers["Set-Cookie"].scan('path=/mnt/').size end end camping-2.3/test/app_file.rb000066400000000000000000000013701427044526700160730ustar00rootroot00000000000000require 'test_helper' require 'camping' Camping.goes :FileSource module FileSource::Controllers class Index def get FileSource.options[:__FILE__] end end end class FileSource::Test < TestCase def test_source get '/' assert_body __FILE__ end def test_file get '/style.css' assert_body "* { margin: 0; padding: 0 }" assert_equal "text/css", last_response.headers['Content-Type'] get '/test.foo' assert_body "Hello" assert_equal "text/html", last_response.headers['Content-Type'] get '/test' assert_body "No extension" assert_equal "text/html", last_response.headers['Content-Type'] end end __END__ @@ /style.css * { margin: 0; padding: 0 } @@ /test.foo Hello @@ /test No extension camping-2.3/test/app_helpers.rb000066400000000000000000000013231427044526700166140ustar00rootroot00000000000000require 'test_helper' require 'camping' Camping.goes :Helpers module Helpers::Helpers def frontpage R(Index) end def current_user User.new end end module Helpers::Models class User def name 'Bob' end end end module Helpers::Controllers class Index def get URL('/').to_s end end class Model def get current_user.name end end class Users def get frontpage end end end class Helpers::Test < TestCase def test_models get '/model' assert_body "Bob" end def test_controllers get '/users' assert_body "/" end def test_url get '/', {}, 'PATH_INFO' => '' assert_body "http://example.org/" end end camping-2.3/test/app_inline_templates.rb000066400000000000000000000007721427044526700205150ustar00rootroot00000000000000require 'test_helper' require 'camping' Camping.goes :Inline module Inline::Controllers class Index def get @world = "World" render :index end end class UserX def get(name) @name = name render :user end end end class Inline::Test < TestCase def test_inline get '/' assert_body "Hello World" get '/user/Bluebie' assert_body "My name is Bluebie" end end __END__ @@ index.erb Hello <%= @world %> @@ user.erb My name is <%= @name %> camping-2.3/test/app_markup.rb000066400000000000000000000025461427044526700164610ustar00rootroot00000000000000require 'test_helper' require 'camping' Camping.goes :Markup module Markup::Controllers class Index def get render :index end end class NoLayout def get render :index, :layout => false end end class AutoPrepend def get mab do img :src => '/hello.png' end end end class Compat < R '/compat/(.*?)' def get(type) mab do send(type) do body { h1 'Nice' } end end end end class CompatHelpers def get mab do helpers.R CompatHelpers end end end end module Markup::Views def index h1 "Welcome!" end def layout self << '' html do head do title "Web Page" end body { yield } end end end class Markup::Test < TestCase def test_render get '/' assert_body %r{\A} assert_body %r{

Welcome!

} assert_body %r{Web Page} end def test_no_layout get '/no/layout' assert_body %r{

Welcome!

} assert_reverse do assert_body %r{Web Page} end end def test_auto_prepend get '/auto/prepend', {}, 'SCRIPT_NAME' => '/mount' assert_body '' end def test_compat_helpers get '/compat/helpers' assert_body '/compat/helpers' end end camping-2.3/test/app_migrations.rb000066400000000000000000000005171427044526700173320ustar00rootroot00000000000000require 'test_helper' begin load File.expand_path('../apps/migrations.rb', __FILE__) ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:') class Migrations::Test < TestCase def test_create Migrations.create end end rescue MissingLibrary warn "Skipping migration tests" end camping-2.3/test/app_partials.rb000066400000000000000000000033021427044526700167700ustar00rootroot00000000000000require 'test_helper' require 'camping' Camping.goes :Partials Camping.goes :TiltPartials module Partials::Controllers class Index def get render :index end end class Partial def get render :_partial end end class Nolayout def get render :index, :layout => false end end class Forcelayout def get render :_partial, :layout => true end end class Nested def get render :nested end end end # Copy over all controllers module TiltPartials::Controllers Partials::Controllers.constants.each do |const| const_set(const, Partials::Controllers.const_get(const).dup) end end module Partials::Views def layout body do yield end end def index h1 "Index" _partial end def _partial p "Partial" end def nested h1 "Nested" regular end def regular p "Regular" end end class Partials::Test < TestCase def test_underscore_partial get '/' assert_body "

Index

Partial

" end def test_underscore_partial_only get '/partial' assert_body "

Partial

" end def test_nolayout get '/nolayout' assert_body "

Index

Partial

" end def test_forcelayout get '/forcelayout' assert_body "

Partial

" end def test_netsted get '/nested' assert_body "

Nested

Regular

" end end class TiltPartials::Test < Partials::Test end __END__ @@ layout.str #{yield.strip} @@ index.str

Index

#{render(:_partial).strip} @@ _partial.str

Partial

@@ nested.str

Nested

#{render(:regular).strip} @@ regular.str

Regular

camping-2.3/test/app_reloader.rb000066400000000000000000000024451427044526700167550ustar00rootroot00000000000000require 'test_helper' require 'fileutils' require 'camping/reloader' $counter = 0 module TestCaseReloader def reloader @reloader ||= Camping::Reloader.new(file) end def setup super reloader.reload! assert Object.const_defined?(:Reloader), "Reloader didn't load app" end def teardown super assert Object.const_defined?(:Reloader), "Test removed app" reloader.remove_apps assert !Object.const_defined?(:Reloader), "Reloader didn't remove app" end end class TestReloader < TestCase include TestCaseReloader BASE = File.expand_path('../apps/reloader', __FILE__) def file; BASE + '.rb' end def setup $counter = 0 super end def test_counter assert_equal 1, $counter end def test_forced_reload reloader.reload! assert_equal 2, $counter end def test_mtime_reload reloader.reload assert_equal 1, $counter FileUtils.touch(BASE + '.rb') sleep 1 reloader.reload assert_equal 2, $counter FileUtils.touch(BASE + '/reload_me.rb') sleep 1 reloader.reload assert_equal 3, $counter end end class TestConfigRu < TestReloader BASE = File.expand_path('../apps/reloader', __FILE__) def file; BASE + '/config.ru' end def test_name assert_equal Reloader, reloader.apps[:reloader] end end camping-2.3/test/app_route_generating.rb000066400000000000000000000015451427044526700205210ustar00rootroot00000000000000require 'test_helper' require 'camping' require 'camping/commands' Camping.goes :Routes module Routes::Controllers class Index def get R(Style) end end class Style < R '/style\.css' end class PageX def get end def post end end end class Routes::Test < TestCase def test_backslash get '/' assert_body '/style.css' end def test_routes_helper collection = Camping::Commands.routes Camping::Apps[9], true routes = collection.routes.map(&:to_s) assert_equal routes.count, 3, "Routes are not numbered correctly" assert (routes.include? "get: /page/([^/]+)"), "Routes do not include: [get: /page/([^/]+)]" assert (routes.include? "post: /page/([^/]+)"), "Routes do not include: [post: /page/([^/]+)]" assert (routes.include? "get: /"), "Routes do not include: [get: /]" end end camping-2.3/test/app_sessions.rb000066400000000000000000000011631427044526700170220ustar00rootroot00000000000000require 'test_helper' require 'camping' require 'camping/session' Camping.goes :Sessions module Sessions include Camping::Session end module Sessions::Controllers class One def get @state.clear @state.one = 42 redirect R(Two) end end class Two def get @state.two = 56 redirect R(Three) end end class Three def get @state.three = 99 @state.values_at("one", "two", "three").inspect end end end class Sessions::Test < TestCase def test_session get '/one' follow_redirect! follow_redirect! assert_body "[42, 56, 99]" end end camping-2.3/test/app_simple.rb000066400000000000000000000032461427044526700164510ustar00rootroot00000000000000require 'test_helper' require 'camping' Camping.goes :Simple module Simple::Controllers class Index def get "Hello World!" end def post "Hello Post!" end def custom "Hello Custom!" end end class PostN def get(id) "Post ##{id}" end end class MultipleComplexX def get(str) "Complex: #{str}" end end class DateNNN def get(year, month, day) [year, month, day] * "-" end end class Regexp < R '/ohmy/([a-f]+)' def get(value) value end end class Optional < R '/optional', '/optional/([^/]+)' def get(value = "default") "Optional: #{value}" end end class Weird def get redirect MultipleComplexX, 'hello%#/world' end end end class Simple::Test < TestCase def test_index get '/' assert_body "Hello World!" assert_equal "text/html", last_response.headers['Content-Type'] post '/' assert_body "Hello Post!" end def test_post get '/post/1' assert_body "Post #1" get '/post/2' assert_body "Post #2" get '/post/2-oh-no' assert_status 404 end def test_complex get '/multiple/complex/Hello' assert_body "Complex: Hello" end def test_date get '/date/2010/04/01' assert_body "2010-04-01" end def test_regexp get '/ohmy/cafebabe' assert_body "cafebabe" get '/ohmy/CAFEBABE' assert_status 404 end def test_optional get '/optional' assert_body "Optional: default" get '/optional/override' assert_body "Optional: override" end def test_weird get '/weird' follow_redirect! assert_body 'Complex: hello%#/world' end end camping-2.3/test/apps/000077500000000000000000000000001427044526700147315ustar00rootroot00000000000000camping-2.3/test/apps/env_debug.rb000066400000000000000000000023261427044526700172170ustar00rootroot00000000000000#!/usr/bin/env ruby begin require "rubygems" rescue LoadError end require "camping" Camping.goes :EnvDebug module EnvDebug module Controllers class ShowEnv < R '/', '/(.*)' def get(extra = nil) @extra = extra render :show_env end alias post get end end module Views def layout html do head{ title C } body do ul do li{ a "show env", :href=>R(ShowEnv)} end p { yield } end end end def _print_hash(hash) hash.keys.sort.each do |key| value = hash[key] pre "%30s: %s" % [key.inspect, value.inspect] end end def show_env b "extra: #{@extra.inspect}" h2 "@status : #{@status.inspect}" h2 "@method : #{@method.inspect}" h2 "@root : #{@root.inspect}" h2 "@env :" _print_hash @env h2 "@input : " _print_hash @input h2 "@headers :" _print_hash @headers end end end # For CGI if $0 == __FILE__ EnvDebug.create if EnvDebug.respond_to? :create if ARGV.any? require 'camping/fastcgi' #Dir.chdir('/var/camping/blog/') Camping::FastCGI.start(EnvDebug) else puts EnvDebug.run end end camping-2.3/test/apps/forms.rb000066400000000000000000000043011427044526700164020ustar00rootroot00000000000000require "camping" Camping.goes :Forms module Forms module Controllers class Index < R '/' def get; render :index end end class GetForm def get; render :get_form; end end class GetFormResult def get; render :form_result; end end class PostForm def get; render :post_form; end def post; render :form_result; end end class FileForm def get; render :file_form; end def post; render :form_result; end end end module Views def layout html do head{ title C } body do ul do li{ a "index", :href=>R(Index)} li{ a "get form", :href=>R(GetForm)} li{ a "post form", :href=>R(PostForm)} li{ a "file form", :href=>R(FileForm)} end p { yield } end end end def form_result p @input.inspect end def index h1 "Welcome on the Camping test app" end def get_form form :action=>R(GetFormResult), :method=>:get do label "Give me your name!", :for=>:name input :type=>:text, :name=>:name; br input :type=>:text, :name=>"arr[]"; br input :type=>:text, :name=>"arr[]"; br input :type=>:text, :name=>"hash[x]"; br input :type=>:text, :name=>"hash[y]"; br input :type=>:submit end end def post_form form :action=>R(PostForm), :method=>:post do label "Give me your name!", :for=>:name input :type=>:text, :name=>:name; br input :type=>:text, :name=>"arr[]"; br input :type=>:text, :name=>"arr[]"; br input :type=>:text, :name=>"hash[x]"; br input :type=>:text, :name=>"hash[y]"; br input :type=>:submit end end def file_form form :action=>R(FileForm), :method=>:post, :enctype=>"multipart/form-data" do input :type=>:text, :name=>"arr" input :type=>:text, :name=>"arr"; br input :type=>:file, :name=>"first_file"; br input :type=>:file, :name=>"files[]"; br input :type=>:file, :name=>"files[]"; br input :type=>:submit end end end end # For CGI if $0 == __FILE__ Forms.create Forms.run end camping-2.3/test/apps/forward_to_other_controller.rb000066400000000000000000000021441427044526700230710ustar00rootroot00000000000000require 'camping' Camping.goes :ForwardToOtherController module ForwardToOtherController module Controllers class Index < R '/' attr_accessor :msg def get puts "msg=#{@msg}" puts "input=#{input.inspect}" render :index end end class Start < R '/start' def get render :start end end class Welcome < R '/welcome' def post if input.name == '_why' msg = "Wow you're back _why! This is front-page news!" r *ForwardToOtherController.get(:Index, :msg => msg, :input => input ) end @result = "Welcome #{input.name}!" render :welcome end end end module Views def index h1 @msg if @msg && !@msg.empty? a "Start", :href=> "/start" end def start h3 "Start" form :action=>R(Welcome), :method=>:post do label "Who are you?", :for=>:name div "Note: type _why if you want to test the forward logic otherwise just type your name", :style=>"color:green;font-size:8pt;" input :type=>:text, :name=>:name; br input :type=>:submit end end def welcome div "#{@result}" end end end camping-2.3/test/apps/migrations.rb000066400000000000000000000043331427044526700174350ustar00rootroot00000000000000require "camping" Camping.goes :Migrations module Migrations module Models class BadDude < Base; end class TableCreation < V 1.0 def self.up puts "FORCE THE TABLES" create_table BadDude.table_name, :force=>true do |t| t.string :name, :limit => 255 t.integer :bad t.timestamps end end def self.down drop_table BadDude.table_name end end class StartingDudes < V 1.3 def self.up puts "There is only one way to make sure Bruce is the baddest" BadDude.create :name => "Bruce", :bad => 1 BadDude.create :name => "Bruce", :bad => 2 BadDude.create :name => "Bruce", :bad => 5 end def self.down BadDude.delete_by_name "Bruce" end end class WeNeedMoreDudes < V 2.7 def self.up puts "Maybe a non Bruce would help our worst case scenario planning" BadDude.create :name => "Bob", :bad => 3 BadDude.create :name => "Samantha", :bad => 3 end def self.down BadDude.delete_by_name "Bob" BadDude.delete_by_name "Samantha" end end class NoIMeanWeNeedBadderDudes < V 3.14159 def self.up puts "just in case things get ugly" sam = BadDude.find_by_name "Samantha" sam.bad = 9001 sam.save end def self.down sam = BadDude.find_by_name "Samantha" sam.bad = 3 sam.save end end class WaitWeShouldDoThisEarlier < V 3.11 def self.up puts "for workgroups" bruce = BadDude.find_by_name "Bob" bruce.bad = 45 bruce.save end def self.down bruce = BadDude.find_by_name "Bob" bruce.bad = 3 bruce.save end end end module Controllers class Bad < R '/(\d+)?' def get enough @dudes = BadDude.all :conditions => ["bad >= ?", enough.to_i] @howbad = enough render :savethepresident end end end module Views def savethepresident h1.ohnoes "The President Has Been Kidnapped By #{@howbad} Ninjas!" if @dudes.empty? diveq2 "None of the dudes are bad enough to rescue him, We are doomed!" else div "Please get the following dudes:" ul.dudes do @dudes.each do |dude| li.dude dude.name end end end end end end def Migrations.create Migrations::Models.create_schema end camping-2.3/test/apps/misc.rb000066400000000000000000000035131427044526700162130ustar00rootroot00000000000000require "camping" Camping.goes :Misc module Misc module Controllers class Index < R '/' def get; render :index end end class RenderPartial def get; render :_partial; end end class Xsendfile def get @headers["X-Sendfile"] = File.expand_path(__FILE__) "You shouldn't get this text" end end class Links < R '/links', '/links/(\w+)/with/(\d+)/args' def get(*args); render :links; end end class Redirect def get; redirect(RR) end end class RR def get; render :rr; end end class BadLinks def get; render :bad_links; end end class BadMethod; end end module Views def layout html do head{ title C } body do ul do li{ a "index", :href=>R(Index)} li{ a "render partial", :href=>R(RenderPartial)} li{ a "X-Sendfile", :href=>R(Xsendfile)} li{ a "Links", :href=>R(Links)} li{ a "BadLinks", :href=>R(BadLinks)} li{ a "Redirect", :href=>R(Redirect)} li{ a "BadMethod", :href=>R(BadMethod)} end p { yield } end end end def _partial a "go back to index", :href=>R(Index) end def index h1 "Welcome on the Camping test app" end def links a "plain", :href=>R(Links); br a "with args and hash", :href=>R(Links, "moo", 3, :with=>"Hash"); br a "with args and mult. hash", :href=>R(Links, "hoi", 8, :with=>"multiple", 3=>"hash"); br # TODO : with R(nil) a "bad arity", :href=>R(RR, :moo) a "bad args", :href=>R(Links, 3, "moo") end def rr p "got redirected" end end end # For CGI if $0 == __FILE__ Misc.create Misc.run end camping-2.3/test/apps/reloader.rb000066400000000000000000000001561427044526700170550ustar00rootroot00000000000000require 'camping' Camping.goes :Reloader $LOAD_PATH << File.dirname(__FILE__) require 'reloader/reload_me' camping-2.3/test/apps/reloader/000077500000000000000000000000001427044526700165265ustar00rootroot00000000000000camping-2.3/test/apps/reloader/config.ru000066400000000000000000000001201427044526700203340ustar00rootroot00000000000000$LOAD_PATH << File.dirname(__FILE__) + '/../' require 'reloader' run Reloader camping-2.3/test/apps/reloader/reload_me.rb000066400000000000000000000000171427044526700210000ustar00rootroot00000000000000$counter += 1 camping-2.3/test/apps/reloader_indirect.rb000066400000000000000000000000711427044526700207320ustar00rootroot00000000000000$LOAD_PATH << File.dirname(__FILE__) require 'reloader' camping-2.3/test/apps/sessions.rb000066400000000000000000000013561427044526700171310ustar00rootroot00000000000000#!/usr/bin/env ruby require "rubygems" require "camping" Camping.goes :Sessions require 'camping/session' module Sessions include Camping::Session module Controllers class One < R('/') def get @state = C::H['one',rand(100)] puts "1:" + @state.inspect redirect R(Two) end end class Two < R('/steptwo') def get @state['two'] = "This is in two" puts "2:" + @state.inspect redirect R(Three) end end class Three < R('/stepthree') def get @state['three'] = "This is in three" puts "3:" + @state.inspect return "Accumulated state across redirects: #{@state.inspect}" redirect R(Three) end end end end camping-2.3/test/test_helper.rb000066400000000000000000000020161427044526700166300ustar00rootroot00000000000000$:.unshift File.dirname(__FILE__) + '/../lib' $VERBOSE = nil begin require 'rubygems' rescue LoadError end if ENV['ABRIDGED'] require 'camping' else require 'camping-unabridged' end require 'minitest/autorun' require 'rack/test' require "minitest/reporters" Minitest::Reporters.use! [Minitest::Reporters::DefaultReporter.new(:color => true)] class TestCase < MiniTest::Test include Rack::Test::Methods def self.inherited(mod) mod.app = Object.const_get(mod.to_s[/\w+/]) super end class << self attr_accessor :app end def body() last_response.body end def app() self.class.app end def assert_reverse begin yield rescue Exception else assert false, "Block didn't fail" end end def assert_body(str) case str when Regexp assert_match(str, last_response.body.strip) else assert_equal(str.to_s, last_response.body.strip) end end def assert_status(code) assert_equal(code, last_response.status) end def test_silly; end end