diff options
| author | Joshua Peek <josh@joshpeek.com> | 2012-07-23 15:52:49 -0500 |
|---|---|---|
| committer | Joshua Peek <josh@joshpeek.com> | 2012-07-23 15:52:49 -0500 |
| commit | 7b6caa0f6c7603aabb5e6ed657e24aaddbeafc78 (patch) | |
| tree | ec0522eae75bb04ae73d28ae3f599682a2bfdaeb /samples/Ruby | |
| parent | 314f0e485227915931db361be14d308a210bedea (diff) | |
Rename samples subdirectories
Diffstat (limited to 'samples/Ruby')
| -rw-r--r-- | samples/Ruby/filenames/Capfile | 3 | ||||
| -rw-r--r-- | samples/Ruby/filenames/Rakefile | 3 | ||||
| -rw-r--r-- | samples/Ruby/foo.rb | 2 | ||||
| -rw-r--r-- | samples/Ruby/formula.rb | 703 | ||||
| -rw-r--r-- | samples/Ruby/grit.rb | 2 | ||||
| -rw-r--r-- | samples/Ruby/inflector.rb | 323 | ||||
| -rw-r--r-- | samples/Ruby/jekyll.rb | 138 | ||||
| -rwxr-xr-x | samples/Ruby/macruby-script | 2 | ||||
| -rw-r--r-- | samples/Ruby/resque.rb | 385 | ||||
| -rwxr-xr-x | samples/Ruby/script.rake | 4 | ||||
| -rwxr-xr-x | samples/Ruby/script.rb | 2 | ||||
| -rw-r--r-- | samples/Ruby/script2.rb | 2 | ||||
| -rw-r--r-- | samples/Ruby/sinatra.rb | 1775 | ||||
| -rw-r--r-- | samples/Ruby/wrong_shebang.rb | 2 |
14 files changed, 3346 insertions, 0 deletions
diff --git a/samples/Ruby/filenames/Capfile b/samples/Ruby/filenames/Capfile new file mode 100644 index 0000000..96291a7 --- /dev/null +++ b/samples/Ruby/filenames/Capfile @@ -0,0 +1,3 @@ +load 'deploy' +Dir['vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) } +load 'config/deploy' diff --git a/samples/Ruby/filenames/Rakefile b/samples/Ruby/filenames/Rakefile new file mode 100644 index 0000000..9d51ff7 --- /dev/null +++ b/samples/Ruby/filenames/Rakefile @@ -0,0 +1,3 @@ +task :default do + puts "Rake" +end diff --git a/samples/Ruby/foo.rb b/samples/Ruby/foo.rb new file mode 100644 index 0000000..85026ed --- /dev/null +++ b/samples/Ruby/foo.rb @@ -0,0 +1,2 @@ +module Foo +end diff --git a/samples/Ruby/formula.rb b/samples/Ruby/formula.rb new file mode 100644 index 0000000..541ee65 --- /dev/null +++ b/samples/Ruby/formula.rb @@ -0,0 +1,703 @@ +require 'download_strategy' +require 'dependencies' +require 'formula_support' +require 'hardware' +require 'bottles' +require 'extend/fileutils' +require 'patches' +require 'compilers' + +# Derive and define at least @url, see Library/Formula for examples +class Formula + include FileUtils + + attr_reader :name, :path, :url, :version, :homepage, :specs, :downloader + attr_reader :standard, :unstable, :head + attr_reader :bottle_version, :bottle_url, :bottle_sha1 + + # The build folder, usually in /tmp. + # Will only be non-nil during the stage method. + attr_reader :buildpath + + # Homebrew determines the name + def initialize name='__UNKNOWN__', path=nil + set_instance_variable 'homepage' + set_instance_variable 'url' + set_instance_variable 'bottle_version' + set_instance_variable 'bottle_url' + set_instance_variable 'bottle_sha1' + set_instance_variable 'head' + set_instance_variable 'specs' + set_instance_variable 'standard' + set_instance_variable 'unstable' + + if @head and (not @url or ARGV.build_head?) + @url = @head + @version = 'HEAD' + @spec_to_use = @unstable + else + if @standard.nil? + @spec_to_use = SoftwareSpecification.new(@url, @specs) + else + @spec_to_use = @standard + end + end + + raise "No url provided for formula #{name}" if @url.nil? + @name = name + validate_variable :name + + # If we got an explicit path, use that, else determine from the name + @path = path.nil? ? self.class.path(name) : Pathname.new(path) + + # Use a provided version, if any + set_instance_variable 'version' + # Otherwise detect the version from the URL + @version ||= @spec_to_use.detect_version + # Only validate if a version was set; GitHubGistFormula needs to get + # the URL to determine the version + validate_variable :version if @version + + CHECKSUM_TYPES.each { |type| set_instance_variable type } + + @downloader = download_strategy.new @spec_to_use.url, name, version, @spec_to_use.specs + + @bottle_url ||= bottle_base_url + bottle_filename(self) if @bottle_sha1 + end + + # if the dir is there, but it's empty we consider it not installed + def installed? + return installed_prefix.children.length > 0 + rescue + return false + end + + def explicitly_requested? + # `ARGV.formulae` will throw an exception if it comes up with an empty list. + # FIXME: `ARGV.formulae` shouldn't be throwing exceptions, see issue #8823 + return false if ARGV.named.empty? + ARGV.formulae.include? self + end + + def linked_keg + HOMEBREW_REPOSITORY/'Library/LinkedKegs'/@name + end + + def installed_prefix + head_prefix = HOMEBREW_CELLAR+@name+'HEAD' + if @version == 'HEAD' || head_prefix.directory? + head_prefix + else + prefix + end + end + + def prefix + validate_variable :name + validate_variable :version + HOMEBREW_CELLAR+@name+@version + end + def rack; prefix.parent end + + def bin; prefix+'bin' end + def doc; prefix+'share/doc'+name end + def include; prefix+'include' end + def info; prefix+'share/info' end + def lib; prefix+'lib' end + def libexec; prefix+'libexec' end + def man; prefix+'share/man' end + def man1; man+'man1' end + def man2; man+'man2' end + def man3; man+'man3' end + def man4; man+'man4' end + def man5; man+'man5' end + def man6; man+'man6' end + def man7; man+'man7' end + def man8; man+'man8' end + def sbin; prefix+'sbin' end + def share; prefix+'share' end + + # configuration needs to be preserved past upgrades + def etc; HOMEBREW_PREFIX+'etc' end + # generally we don't want var stuff inside the keg + def var; HOMEBREW_PREFIX+'var' end + + # plist name, i.e. the name of the launchd service + def plist_name; 'homebrew.mxcl.'+name end + def plist_path; prefix+(plist_name+'.plist') end + + # Use the @spec_to_use to detect the download strategy. + # Can be overriden to force a custom download strategy + def download_strategy + @spec_to_use.download_strategy + end + + def cached_download + @downloader.cached_location + end + + # tell the user about any caveats regarding this package, return a string + def caveats; nil end + + # any e.g. configure options for this package + def options; [] end + + # patches are automatically applied after extracting the tarball + # return an array of strings, or if you need a patch level other than -p1 + # return a Hash eg. + # { + # :p0 => ['http://foo.com/patch1', 'http://foo.com/patch2'], + # :p1 => 'http://bar.com/patch2', + # :p2 => ['http://moo.com/patch5', 'http://moo.com/patch6'] + # } + # The final option is to return DATA, then put a diff after __END__. You + # can still return a Hash with DATA as the value for a patch level key. + def patches; end + + # rarely, you don't want your library symlinked into the main prefix + # see gettext.rb for an example + def keg_only? + self.class.keg_only_reason || false + end + + def fails_with? cc + return false if self.class.cc_failures.nil? + cc = Compiler.new(cc) unless cc.is_a? Compiler + return self.class.cc_failures.find do |failure| + next unless failure.compiler == cc.name + failure.build.zero? or failure.build >= cc.build + end + end + + # sometimes the clean process breaks things + # skip cleaning paths in a formula with a class method like this: + # skip_clean [bin+"foo", lib+"bar"] + # redefining skip_clean? now deprecated + def skip_clean? path + return true if self.class.skip_clean_all? + to_check = path.relative_path_from(prefix).to_s + self.class.skip_clean_paths.include? to_check + end + + # yields self with current working directory set to the uncompressed tarball + def brew + validate_variable :name + validate_variable :version + + stage do + begin + patch + # we allow formulas to do anything they want to the Ruby process + # so load any deps before this point! And exit asap afterwards + yield self + rescue Interrupt, RuntimeError, SystemCallError => e + puts if Interrupt === e # don't print next to the ^C + unless ARGV.debug? + %w(config.log CMakeCache.txt).select{|f| File.exist? f}.each do |f| + HOMEBREW_LOGS.install f + puts "#{f} was copied to #{HOMEBREW_LOGS}" + end + raise + end + onoe e.inspect + puts e.backtrace + + ohai "Rescuing build..." + if (e.was_running_configure? rescue false) and File.exist? 'config.log' + puts "It looks like an autotools configure failed." + puts "Gist 'config.log' and any error output when reporting an issue." + puts + end + + puts "When you exit this shell Homebrew will attempt to finalise the installation." + puts "If nothing is installed or the shell exits with a non-zero error code," + puts "Homebrew will abort. The installation prefix is:" + puts prefix + interactive_shell self + end + end + end + + def == b + name == b.name + end + def eql? b + self == b and self.class.equal? b.class + end + def hash + name.hash + end + def <=> b + name <=> b.name + end + def to_s + name + end + + # Standard parameters for CMake builds. + # Using Build Type "None" tells cmake to use our CFLAGS,etc. settings. + # Setting it to Release would ignore our flags. + # Setting CMAKE_FIND_FRAMEWORK to "LAST" tells CMake to search for our + # libraries before trying to utilize Frameworks, many of which will be from + # 3rd party installs. + # Note: there isn't a std_autotools variant because autotools is a lot + # less consistent and the standard parameters are more memorable. + def std_cmake_args + %W[ + -DCMAKE_INSTALL_PREFIX=#{prefix} + -DCMAKE_BUILD_TYPE=None + -DCMAKE_FIND_FRAMEWORK=LAST + -Wno-dev + ] + end + + def self.class_s name + #remove invalid characters and then camelcase it + name.capitalize.gsub(/[-_.\s]([a-zA-Z0-9])/) { $1.upcase } \ + .gsub('+', 'x') + end + + # an array of all Formula names + def self.names + Dir["#{HOMEBREW_REPOSITORY}/Library/Formula/*.rb"].map{ |f| File.basename f, '.rb' }.sort + end + + # an array of all Formula, instantiated + def self.all + map{ |f| f } + end + def self.map + rv = [] + each{ |f| rv << yield(f) } + rv + end + def self.each + names.each do |n| + begin + yield Formula.factory(n) + rescue + # Don't let one broken formula break commands. But do complain. + onoe "Formula #{n} will not import." + end + end + end + + def inspect + name + end + + def self.aliases + Dir["#{HOMEBREW_REPOSITORY}/Library/Aliases/*"].map{ |f| File.basename f }.sort + end + + def self.canonical_name name + name = name.to_s if name.kind_of? Pathname + + formula_with_that_name = HOMEBREW_REPOSITORY+"Library/Formula/#{name}.rb" + possible_alias = HOMEBREW_REPOSITORY+"Library/Aliases/#{name}" + possible_cached_formula = HOMEBREW_CACHE_FORMULA+"#{name}.rb" + + if name.include? "/" + if name =~ %r{(.+)/(.+)/(.+)} + tapd = HOMEBREW_REPOSITORY/"Library/Taps"/"#$1-#$2".downcase + tapd.find_formula do |relative_pathname| + return "#{tapd}/#{relative_pathname}" if relative_pathname.stem.to_s == $3 + end if tapd.directory? + end + # Otherwise don't resolve paths or URLs + name + elsif formula_with_that_name.file? and formula_with_that_name.readable? + name + elsif possible_alias.file? + possible_alias.realpath.basename('.rb').to_s + elsif possible_cached_formula.file? + possible_cached_formula.to_s + else + name + end + end + + def self.factory name + # If an instance of Formula is passed, just return it + return name if name.kind_of? Formula + + # Otherwise, convert to String in case a Pathname comes in + name = name.to_s + + # If a URL is passed, download to the cache and install + if name =~ %r[(https?|ftp)://] + url = name + name = Pathname.new(name).basename + target_file = HOMEBREW_CACHE_FORMULA+name + name = name.basename(".rb").to_s + + HOMEBREW_CACHE_FORMULA.mkpath + FileUtils.rm target_file, :force => true + curl url, '-o', target_file + + require target_file + install_type = :from_url + else + name = Formula.canonical_name(name) + # If name was a path or mapped to a cached formula + if name.include? "/" + require name + + # require allows filenames to drop the .rb extension, but everything else + # in our codebase will require an exact and fullpath. + name = "#{name}.rb" unless name =~ /\.rb$/ + + path = Pathname.new(name) + name = path.stem + install_type = :from_path + target_file = path.to_s + else + # For names, map to the path and then require + require Formula.path(name) + install_type = :from_name + end + end + + begin + klass_name = self.class_s(name) + klass = Object.const_get klass_name + rescue NameError + # TODO really this text should be encoded into the exception + # and only shown if the UI deems it correct to show it + onoe "class \"#{klass_name}\" expected but not found in #{name}.rb" + puts "Double-check the name of the class in that formula." + raise LoadError + end + + return klass.new(name) if install_type == :from_name + return klass.new(name, target_file) + rescue LoadError + raise FormulaUnavailableError.new(name) + end + + def tap + if path.realpath.to_s =~ %r{#{HOMEBREW_REPOSITORY}/Library/Taps/(\w+)-(\w+)} + "#$1/#$2" + else + # remotely installed formula are not mxcl/master but this will do for now + "mxcl/master" + end + end + + def self.path name + HOMEBREW_REPOSITORY+"Library/Formula/#{name.downcase}.rb" + end + + def mirrors; self.class.mirrors or []; end + + def deps; self.class.dependencies.deps; end + def external_deps; self.class.dependencies.external_deps; end + + # deps are in an installable order + # which means if a depends on b then b will be ordered before a in this list + def recursive_deps + Formula.expand_deps(self).flatten.uniq + end + + def self.expand_deps f + f.deps.map do |dep| + f_dep = Formula.factory dep.to_s + expand_deps(f_dep) << f_dep + end + end + +protected + + # Pretty titles the command and buffers stdout/stderr + # Throws if there's an error + def system cmd, *args + # remove "boring" arguments so that the important ones are more likely to + # be shown considering that we trim long ohai lines to the terminal width + pretty_args = args.dup + pretty_args.delete "--disable-dependency-tracking" if cmd == "./configure" and not ARGV.verbose? + ohai "#{cmd} #{pretty_args*' '}".strip + + removed_ENV_variables = case if args.empty? then cmd.split(' ').first else cmd end + when "xcodebuild" + ENV.remove_cc_etc + end + + if ARGV.verbose? + safe_system cmd, *args + else + rd, wr = IO.pipe + pid = fork do + rd.close + $stdout.reopen wr + $stderr.reopen wr + args.collect!{|arg| arg.to_s} + exec(cmd, *args) rescue nil + exit! 1 # never gets here unless exec threw or failed + end + wr.close + out = '' + out << rd.read until rd.eof? + Process.wait + unless $?.success? + puts out + raise + end + end + + removed_ENV_variables.each do |key, value| + ENV[key] = value # ENV.kind_of? Hash # => false + end if removed_ENV_variables + + rescue + raise BuildError.new(self, cmd, args, $?) + end + +public + + # For brew-fetch and others. + def fetch + if install_bottle? self + downloader = CurlBottleDownloadStrategy.new bottle_url, name, version, nil + mirror_list = [] + else + downloader = @downloader + # Don't attempt mirrors if this install is not pointed at a "stable" URL. + # This can happen when options like `--HEAD` are invoked. + mirror_list = @spec_to_use == @standard ? mirrors : [] + end + + # Ensure the cache exists + HOMEBREW_CACHE.mkpath + + begin + fetched = downloader.fetch + rescue CurlDownloadStrategyError => e + raise e if mirror_list.empty? + puts "Trying a mirror..." + url, specs = mirror_list.shift.values_at :url, :specs + downloader = download_strategy.new url, name, version, specs + retry + end + + return fetched, downloader + end + + # Detect which type of checksum is being used, or nil if none + def checksum_type + CHECKSUM_TYPES.detect { |type| instance_variable_defined?("@#{type}") } + end + + # For FormulaInstaller. + def verify_download_integrity fn, *args + require 'digest' + if args.length != 2 + type = checksum_type || :md5 + supplied = instance_variable_get("@#{type}") + # Convert symbol to readable string + type = type.to_s.upcase + else + supplied, type = args + end + + hasher = Digest.const_get(type) + hash = fn.incremental_hash(hasher) + + if supplied and not supplied.empty? + message = <<-EOF +#{type} mismatch +Expected: #{supplied} +Got: #{hash} +Archive: #{fn} +(To retry an incomplete download, remove the file above.) +EOF + raise message unless supplied.upcase == hash.upcase + else + opoo "Cannot verify package integrity" + puts "The formula did not provide a download checksum" + puts "For your reference the #{type} is: #{hash}" + end + end + +private + + CHECKSUM_TYPES=[:md5, :sha1, :sha256].freeze + + def stage + fetched, downloader = fetch + verify_download_integrity fetched if fetched.kind_of? Pathname + mktemp do + downloader.stage + # Set path after the downloader changes the working folder. + @buildpath = Pathname.pwd + yield + @buildpath = nil + end + end + + def patch + patch_list = Patches.new(patches) + return if patch_list.empty? + + if patch_list.external_patches? + ohai "Downloading patches" + patch_list.download! + end + + ohai "Patching" + patch_list.each do |p| + case p.compression + when :gzip then safe_system "/usr/bin/gunzip", p.compressed_filename + when :bzip2 then safe_system "/usr/bin/bunzip2", p.compressed_filename + end + # -f means don't prompt the user if there are errors; just exit with non-zero status + safe_system '/usr/bin/patch', '-f', *(p.patch_args) + end + end + + def validate_variable name + v = instance_variable_get("@#{name}") + raise "Invalid @#{name}" if v.to_s.empty? or v =~ /\s/ + end + + def set_instance_variable(type) + return if instance_variable_defined? "@#{type}" + class_value = self.class.send(type) + instance_variable_set("@#{type}", class_value) if class_value + end + + def self.method_added method + raise 'You cannot override Formula.brew' if method == :brew + end + + class << self + # The methods below define the formula DSL. + attr_reader :standard, :unstable + + def self.attr_rw(*attrs) + attrs.each do |attr| + class_eval %Q{ + def #{attr}(val=nil) + val.nil? ? @#{attr} : @#{attr} = val + end + } + end + end + + attr_rw :version, :homepage, :mirrors, :specs + attr_rw :keg_only_reason, :skip_clean_all, :cc_failures + attr_rw :bottle_version, :bottle_url, :bottle_sha1 + attr_rw(*CHECKSUM_TYPES) + + def head val=nil, specs=nil + return @head if val.nil? + @unstable = SoftwareSpecification.new(val, specs) + @head = val + @specs = specs + end + + def url val=nil, specs=nil + return @url if val.nil? + @standard = SoftwareSpecification.new(val, specs) + @url = val + @specs = specs + end + + def stable &block + raise "url and md5 must be specified in a block" unless block_given? + instance_eval(&block) unless ARGV.build_devel? or ARGV.build_head? + end + + def devel &block + raise "url and md5 must be specified in a block" unless block_given? + if ARGV.build_devel? + @mirrors = nil # clear out mirrors from the stable release + instance_eval(&block) + end + end + + def bottle url=nil, &block + return unless block_given? + + bottle_block = Class.new do + def self.version version + @version = version + end + + def self.url url + @url = url + end + + def self.sha1 sha1 + case sha1 + when Hash + key, value = sha1.shift + @sha1 = key if value == MacOS.cat + when String + @sha1 = sha1 if MacOS.lion? + end + end + + def self.data + @version = 0 unless @version + return @version, @url, @sha1 if @sha1 && @url + return @version, nil, @sha1 if @sha1 + end + end + + bottle_block.instance_eval(&block) + @bottle_version, @bottle_url, @bottle_sha1 = bottle_block.data + end + + def mirror val, specs=nil + @mirrors ||= [] + @mirrors << {:url => val, :specs => specs} + # Added the uniq after some inspection with Pry---seems `mirror` gets + # called three times. The first two times only one copy of the input is + # left in `@mirrors`. On the final call, two copies are present. This + # happens with `@deps` as well. Odd. + @mirrors.uniq! + end + + def dependencies + @dependencies ||= DependencyCollector.new + end + + def depends_on dep + dependencies.add(dep) + end + + def skip_clean paths + if paths == :all + @skip_clean_all = true + return + end + @skip_clean_paths ||= [] + [paths].flatten.each do |p| + @skip_clean_paths << p.to_s unless @skip_clean_paths.include? p.to_s + end + end + + def skip_clean_all? + @skip_clean_all + end + + def skip_clean_paths + @skip_clean_paths or [] + end + + def keg_only reason, explanation=nil + @keg_only_reason = KegOnlyReason.new(reason, explanation.to_s.chomp) + end + + def fails_with compiler, &block + @cc_failures ||= CompilerFailures.new + @cc_failures << if block_given? + CompilerFailure.new(compiler, &block) + else + CompilerFailure.new(compiler) + end + end + end +end + +require 'formula_specialties' diff --git a/samples/Ruby/grit.rb b/samples/Ruby/grit.rb new file mode 100644 index 0000000..fc84753 --- /dev/null +++ b/samples/Ruby/grit.rb @@ -0,0 +1,2 @@ +module Grit +end diff --git a/samples/Ruby/inflector.rb b/samples/Ruby/inflector.rb new file mode 100644 index 0000000..2acc6dd --- /dev/null +++ b/samples/Ruby/inflector.rb @@ -0,0 +1,323 @@ +# encoding: utf-8 + +require 'active_support/inflector/inflections' + +module ActiveSupport + # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without, + # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept + # in inflections.rb. + # + # The Rails core team has stated patches for the inflections library will not be accepted + # in order to avoid breaking legacy applications which may be relying on errant inflections. + # If you discover an incorrect inflection and require it for your application, you'll need + # to correct it yourself (explained below). + module Inflector + extend self + + # Returns the plural form of the word in the string. + # + # "post".pluralize # => "posts" + # "octopus".pluralize # => "octopi" + # "sheep".pluralize # => "sheep" + # "words".pluralize # => "words" + # "CamelOctopus".pluralize # => "CamelOctopi" + def pluralize(word) + apply_inflections(word, inflections.plurals) + end + + # The reverse of +pluralize+, returns the singular form of a word in a string. + # + # "posts".singularize # => "post" + # "octopi".singularize # => "octopus" + # "sheep".singularize # => "sheep" + # "word".singularize # => "word" + # "CamelOctopi".singularize # => "CamelOctopus" + def singularize(word) + apply_inflections(word, inflections.singulars) + end + + # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+ + # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase. + # + # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces. + # + # "active_model".camelize # => "ActiveModel" + # "active_model".camelize(:lower) # => "activeModel" + # "active_model/errors".camelize # => "ActiveModel::Errors" + # "active_model/errors".camelize(:lower) # => "activeModel::Errors" + # + # As a rule of thumb you can think of +camelize+ as the inverse of +underscore+, + # though there are cases where that does not hold: + # + # "SSLError".underscore.camelize # => "SslError" + def camelize(term, uppercase_first_letter = true) + string = term.to_s + if uppercase_first_letter + string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize } + else + string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase } + end + string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }.gsub('/', '::') + end + + # Makes an underscored, lowercase form from the expression in the string. + # + # Changes '::' to '/' to convert namespaces to paths. + # + # "ActiveModel".underscore # => "active_model" + # "ActiveModel::Errors".underscore # => "active_model/errors" + # + # As a rule of thumb you can think of +underscore+ as the inverse of +camelize+, + # though there are cases where that does not hold: + # + # "SSLError".underscore.camelize # => "SslError" + def underscore(camel_cased_word) + word = camel_cased_word.to_s.dup + word.gsub!('::', '/') + word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" } + word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2') + word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') + word.tr!("-", "_") + word.downcase! + word + end + + # Capitalizes the first word and turns underscores into spaces and strips a + # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output. + # + # "employee_salary" # => "Employee salary" + # "author_id" # => "Author" + def humanize(lower_case_and_underscored_word) + result = lower_case_and_underscored_word.to_s.dup + inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) } + result.gsub!(/_id$/, "") + result.tr!('_', ' ') + result.gsub(/([a-z\d]*)/i) { |match| + "#{inflections.acronyms[match] || match.downcase}" + }.gsub(/^\w/) { $&.upcase } + end + + # Capitalizes all the words and replaces some characters in the string to create + # a nicer looking title. +titleize+ is meant for creating pretty output. It is not + # used in the Rails internals. + # + # +titleize+ is also aliased as +titlecase+. + # + # "man from the boondocks".titleize # => "Man From The Boondocks" + # "x-men: the last stand".titleize # => "X Men: The Last Stand" + # "TheManWithoutAPast".titleize # => "The Man Without A Past" + # "raiders_of_the_lost_ark".titleize # => "Raiders Of The Lost Ark" + def titleize(word) + humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.capitalize } + end + + # Create the name of a table like Rails does for models to table names. This method + # uses the +pluralize+ method on the last word in the string. + # + # "RawScaledScorer".tableize # => "raw_scaled_scorers" + # "egg_and_ham".tableize # => "egg_and_hams" + # "fancyCategory".tableize # => "fancy_categories" + def tableize(class_name) + pluralize(underscore(class_name)) + end + + # Create a class name from a plural table name like Rails does for table names to models. + # Note that this returns a string and not a Class. (To convert to an actual class + # follow +classify+ with +constantize+.) + # + # "egg_and_hams".classify # => "EggAndHam" + # "posts".classify # => "Post" + # + # Singular names are not handled correctly: + # "business".classify # => "Busines" + def classify(table_name) + # strip out any leading schema name + camelize(singularize(table_name.to_s.sub(/.*\./, ''))) + end + + # Replaces underscores with dashes in the string. + # + # "puni_puni".dasherize # => "puni-puni" + def dasherize(underscored_word) + underscored_word.tr('_', '-') + end + + # Removes the module part from the expression in the string: + # + # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections" + # "Inflections".demodulize # => "Inflections" + # + # See also +deconstantize+. + def demodulize(path) + path = path.to_s + if i = path.rindex('::') + path[(i+2)..-1] + else + path + end + end + + # Removes the rightmost segment from the constant expression in the string: + # + # "Net::HTTP".deconstantize # => "Net" + # "::Net::HTTP".deconstantize # => "::Net" + # "String".deconstantize # => "" + # "::String".deconstantize # => "" + # "".deconstantize # => "" + # + # See also +demodulize+. + def deconstantize(path) + path.to_s[0...(path.rindex('::') || 0)] # implementation based on the one in facets' Module#spacename + end + + # Creates a foreign key name from a class name. + # +separate_class_name_and_id_with_underscore+ sets whether + # the method should put '_' between the name and 'id'. + # + # "Message".foreign_key # => "message_id" + # "Message".foreign_key(false) # => "messageid" + # "Admin::Post".foreign_key # => "post_id" + def foreign_key(class_name, separate_class_name_and_id_with_underscore = true) + underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id") + end + + # Tries to find a constant with the name specified in the argument string: + # + # "Module".constantize # => Module + # "Test::Unit".constantize # => Test::Unit + # + # The name is assumed to be the one of a top-level constant, no matter whether + # it starts with "::" or not. No lexical context is taken into account: + # + # C = 'outside' + # module M + # C = 'inside' + # C # => 'inside' + # "C".constantize # => 'outside', same as ::C + # end + # + # NameError is raised when the name is not in CamelCase or the constant is + # unknown. + def constantize(camel_cased_word) + names = camel_cased_word.split('::') + names.shift if names.empty? || names.first.empty? + + names.inject(Object) do |constant, name| + if constant == Object + constant.const_get(name) + else + candidate = constant.const_get(name) + next candidate if constant.const_defined?(name, false) + next candidate unless Object.const_defined?(name) + + # Go down the ancestors to check it it's owned + # directly before we reach Object or the end of ancestors. + constant = constant.ancestors.inject do |const, ancestor| + break const if ancestor == Object + break ancestor if ancestor.const_defined?(name, false) + const + end + + # owner is in Object, so raise + constant.const_get(name, false) + end + end + end + + # Tries to find a constant with the name specified in the argument string: + # + # "Module".safe_constantize # => Module + # "Test::Unit".safe_constantize # => Test::Unit + # + # The name is assumed to be the one of a top-level constant, no matter whether + # it starts with "::" or not. No lexical context is taken into account: + # + # C = 'outside' + # module M + # C = 'inside' + # C # => 'inside' + # "C".safe_constantize # => 'outside', same as ::C + # end + # + # nil is returned when the name is not in CamelCase or the constant (or part of it) is + # unknown. + # + # "blargle".safe_constantize # => nil + # "UnknownModule".safe_constantize # => nil + # "UnknownModule::Foo::Bar".safe_constantize # => nil + # + def safe_constantize(camel_cased_word) + begin + constantize(camel_cased_word) + rescue NameError => e + raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ || + e.name.to_s == camel_cased_word.to_s + rescue ArgumentError => e + raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/ + end + end + + # Returns the suffix that should be added to a number to denote the position + # in an ordered sequence such as 1st, 2nd, 3rd, 4th. + # + # ordinal(1) # => "st" + # ordinal(2) # => "nd" + # ordinal(1002) # => "nd" + # ordinal(1003) # => "rd" + # ordinal(-11) # => "th" + # ordinal(-1021) # => "st" + def ordinal(number) + if (11..13).include?(number.to_i.abs % 100) + "th" + else + case number.to_i.abs % 10 + when 1; "st" + when 2; "nd" + when 3; "rd" + else "th" + end + end + end + + # Turns a number into an ordinal string used to denote the position in an + # ordered sequence such as 1st, 2nd, 3rd, 4th. + # + # ordinalize(1) # => "1st" + # ordinalize(2) # => "2nd" + # ordinalize(1002) # => "1002nd" + # ordinalize(1003) # => "1003rd" + # ordinalize(-11) # => "-11th" + # ordinalize(-1021) # => "-1021st" + def ordinalize(number) + "#{number}#{ordinal(number)}" + end + + private + + # Mount a regular expression that will match part by part of the constant. + # For instance, Foo::Bar::Baz will generate Foo(::Bar(::Baz)?)? + def const_regexp(camel_cased_word) #:nodoc: + parts = camel_cased_word.split("::") + last = parts.pop + + parts.reverse.inject(last) do |acc, part| + part.empty? ? acc : "#{part}(::#{acc})?" + end + end + + # Applies inflection rules for +singularize+ and +pluralize+. + # + # apply_inflections("post", inflections.plurals) # => "posts" + # apply_inflections("posts", inflections.singulars) # => "post" + def apply_inflections(word, rules) + result = word.to_s.dup + + if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/]) + result + else + rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) } + result + end + end + end +end diff --git a/samples/Ruby/jekyll.rb b/samples/Ruby/jekyll.rb new file mode 100644 index 0000000..fd455fb --- /dev/null +++ b/samples/Ruby/jekyll.rb @@ -0,0 +1,138 @@ +$:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed + +# Require all of the Ruby files in the given directory. +# +# path - The String relative path from here to the directory. +# +# Returns nothing. +def require_all(path) + glob = File.join(File.dirname(__FILE__), path, '*.rb') + Dir[glob].each do |f| + require f + end +end + +# rubygems +require 'rubygems' + +# stdlib +require 'fileutils' +require 'time' +require 'yaml' +require 'English' + +# 3rd party +require 'liquid' +require 'maruku' +require 'albino' + +# internal requires +require 'jekyll/core_ext' +require 'jekyll/site' +require 'jekyll/convertible' +require 'jekyll/layout' +require 'jekyll/page' +require 'jekyll/post' +require 'jekyll/filters' +require 'jekyll/static_file' +require 'jekyll/errors' + +# extensions +require 'jekyll/plugin' +require 'jekyll/converter' +require 'jekyll/generator' +require_all 'jekyll/converters' +require_all 'jekyll/generators' +require_all 'jekyll/tags' + +module Jekyll + VERSION = '0.11.2' + + # Default options. Overriden by values in _config.yml or command-line opts. + # (Strings rather symbols used for compatability with YAML). + DEFAULTS = { + 'safe' => false, + 'auto' => false, + 'server' => false, + 'server_port' => 4000, + + 'source' => Dir.pwd, + 'destination' => File.join(Dir.pwd, '_site'), + 'plugins' => File.join(Dir.pwd, '_plugins'), + + 'future' => true, + 'lsi' => false, + 'pygments' => false, + 'markdown' => 'maruku', + 'permalink' => 'date', + 'include' => ['.htaccess'], + 'paginate_path' => 'page:num', + + 'markdown_ext' => 'markdown,mkd,mkdn,md', + 'textile_ext' => 'textile', + + 'maruku' => { + 'use_tex' => false, + 'use_divs' => false, + 'png_engine' => 'blahtex', + 'png_dir' => 'images/latex', + 'png_url' => '/images/latex' + }, + 'rdiscount' => { + 'extensions' => [] + }, + 'redcarpet' => { + 'extensions' => [] + }, + 'kramdown' => { + 'auto_ids' => true, + 'footnote_nr' => 1, + 'entity_output' => 'as_char', + 'toc_levels' => '1..6', + 'smart_quotes' => 'lsquo,rsquo,ldquo,rdquo', + 'use_coderay' => false, + + 'coderay' => { + 'coderay_wrap' => 'div', + 'coderay_line_numbers' => 'inline', + 'coderay_line_number_start' => 1, + 'coderay_tab_width' => 4, + 'coderay_bold_every' => 10, + 'coderay_css' => 'style' + } + }, + 'redcloth' => { + 'hard_breaks' => true + } + } + + # Public: Generate a Jekyll configuration Hash by merging the default + # options with anything in _config.yml, and adding the given options on top. + # + # override - A Hash of config directives that override any options in both + # the defaults and the config file. See Jekyll::DEFAULTS for a + # list of option names and their defaults. + # + # Returns the final configuration Hash. + def self.configuration(override) + # _config.yml may override default source location, but until + # then, we need to know where to look for _config.yml + source = override['source'] || Jekyll::DEFAULTS['source'] + + # Get configuration from <source>/_config.yml + config_file = File.join(source, '_config.yml') + begin + config = YAML.load_file(config_file) + raise "Invalid configuration - #{config_file}" if !config.is_a?(Hash) + $stdout.puts "Configuration from #{config_file}" + rescue => err + $stderr.puts "WARNING: Could not read configuration. " + + "Using defaults (and options)." + $stderr.puts "\t" + err.to_s + config = {} + end + + # Merge DEFAULTS < _config.yml < override + Jekyll::DEFAULTS.deep_merge(config).deep_merge(override) + end +end diff --git a/samples/Ruby/macruby-script b/samples/Ruby/macruby-script new file mode 100755 index 0000000..491be11 --- /dev/null +++ b/samples/Ruby/macruby-script @@ -0,0 +1,2 @@ +#!/usr/bin/env macruby +puts "MacRuby" diff --git a/samples/Ruby/resque.rb b/samples/Ruby/resque.rb new file mode 100644 index 0000000..1e36524 --- /dev/null +++ b/samples/Ruby/resque.rb @@ -0,0 +1,385 @@ +require 'redis/namespace' + +require 'resque/version' + +require 'resque/errors' + +require 'resque/failure' +require 'resque/failure/base' + +require 'resque/helpers' +require 'resque/stat' +require 'resque/job' +require 'resque/worker' +require 'resque/plugin' +require 'resque/queue' +require 'resque/multi_queue' +require 'resque/coder' +require 'resque/multi_json_coder' + +module Resque + include Helpers + extend self + + # Accepts: + # 1. A 'hostname:port' String + # 2. A 'hostname:port:db' String (to select the Redis db) + # 3. A 'hostname:port/namespace' String (to set the Redis namespace) + # 4. A Redis URL String 'redis://host:port' + # 5. An instance of `Redis`, `Redis::Client`, `Redis::DistRedis`, + # or `Redis::Namespace`. + def redis=(server) + case server + when String + if server =~ /redis\:\/\// + redis = Redis.connect(:url => server, :thread_safe => true) + else + server, namespace = server.split('/', 2) + host, port, db = server.split(':') + redis = Redis.new(:host => host, :port => port, + :thread_safe => true, :db => db) + end + namespace ||= :resque + + @redis = Redis::Namespace.new(namespace, :redis => redis) + when Redis::Namespace + @redis = server + else + @redis = Redis::Namespace.new(:resque, :redis => server) + end + @queues = Hash.new { |h,name| + h[name] = Resque::Queue.new(name, @redis, coder) + } + end + + # Encapsulation of encode/decode. Overwrite this to use it across Resque. + # This defaults to MultiJson for backwards compatibilty. + def coder + @coder ||= MultiJsonCoder.new + end + attr_writer :coder + + # Returns the current Redis connection. If none has been created, will + # create a new one. + def redis + return @redis if @redis + self.redis = Redis.respond_to?(:connect) ? Redis.connect : "localhost:6379" + self.redis + end + + def redis_id + # support 1.x versions of redis-rb + if redis.respond_to?(:server) + redis.server + elsif redis.respond_to?(:nodes) # distributed + redis.nodes.map { |n| n.id }.join(', ') + else + redis.client.id + end + end + + # The `before_first_fork` hook will be run in the **parent** process + # only once, before forking to run the first job. Be careful- any + # changes you make will be permanent for the lifespan of the + # worker. + # + # Call with a block to set the hook. + # Call with no arguments to return the hook. + def before_first_fork(&block) + block ? (@before_first_fork = block) : @before_first_fork + end + + # Set a proc that will be called in the parent process before the + # worker forks for the first time. + attr_writer :before_first_fork + + # The `before_fork` hook will be run in the **parent** process + # before every job, so be careful- any changes you make will be + # permanent for the lifespan of the worker. + # + # Call with a block to set the hook. + # Call with no arguments to return the hook. + def before_fork(&block) + block ? (@before_fork = block) : @before_fork + end + + # Set the before_fork proc. + attr_writer :before_fork + + # The `after_fork` hook will be run in the child process and is passed + # the current job. Any changes you make, therefore, will only live as + # long as the job currently being processed. + # + # Call with a block to set the hook. + # Call with no arguments to return the hook. + def after_fork(&block) + block ? (@after_fork = block) : @after_fork + end + + # Set the after_fork proc. + attr_writer :after_fork + + def to_s + "Resque Client connected to #{redis_id}" + end + + attr_accessor :inline + + # If 'inline' is true Resque will call #perform method inline + # without queuing it into Redis and without any Resque callbacks. + # The 'inline' is false Resque jobs will be put in queue regularly. + alias :inline? :inline + + # + # queue manipulation + # + + # Pushes a job onto a queue. Queue name should be a string and the + # item should be any JSON-able Ruby object. + # + # Resque works generally expect the `item` to be a hash with the following + # keys: + # + # class - The String name of the job to run. + # args - An Array of arguments to pass the job. Usually passed + # via `class.to_class.perform(*args)`. + # + # Example + # + # Resque.push('archive', :class => 'Archive', :args => [ 35, 'tar' ]) + # + # Returns nothing + def push(queue, item) + queue(queue) << item + end + + # Pops a job off a queue. Queue name should be a string. + # + # Returns a Ruby object. + def pop(queue) + begin + queue(queue).pop(true) + rescue ThreadError + nil + end + end + + # Returns an integer representing the size of a queue. + # Queue name should be a string. + def size(queue) + queue(queue).size + end + + # Returns an array of items currently queued. Queue name should be + # a string. + # + # start and count should be integer and can be used for pagination. + # start is the item to begin, count is how many items to return. + # + # To get the 3rd page of a 30 item, paginatied list one would use: + # Resque.peek('my_list', 59, 30) + def peek(queue, start = 0, count = 1) + queue(queue).slice start, count + end + + # Does the dirty work of fetching a range of items from a Redis list + # and converting them into Ruby objects. + def list_range(key, start = 0, count = 1) + if count == 1 + decode redis.lindex(key, start) + else + Array(redis.lrange(key, start, start+count-1)).map do |item| + decode item + end + end + end + + # Returns an array of all known Resque queues as strings. + def queues + Array(redis.smembers(:queues)) + end + + # Given a queue name, completely deletes the queue. + def remove_queue(queue) + queue(queue).destroy + @queues.delete(queue.to_s) + end + + # Return the Resque::Queue object for a given name + def queue(name) + @queues[name.to_s] + end + + + # + # job shortcuts + # + + # This method can be used to conveniently add a job to a queue. + # It assumes the class you're passing it is a real Ruby class (not + # a string or reference) which either: + # + # a) has a @queue ivar set + # b) responds to `queue` + # + # If either of those conditions are met, it will use the value obtained + # from performing one of the above operations to determine the queue. + # + # If no queue can be inferred this method will raise a `Resque::NoQueueError` + # + # Returns true if the job was queued, nil if the job was rejected by a + # before_enqueue hook. + # + # This method is considered part of the `stable` API. + def enqueue(klass, *args) + enqueue_to(queue_from_class(klass), klass, *args) + end + + # Just like `enqueue` but allows you to specify the queue you want to + # use. Runs hooks. + # + # `queue` should be the String name of the queue you're targeting. + # + # Returns true if the job was queued, nil if the job was rejected by a + # before_enqueue hook. + # + # This method is considered part of the `stable` API. + def enqueue_to(queue, klass, *args) + # Perform before_enqueue hooks. Don't perform enqueue if any hook returns false + before_hooks = Plugin.before_enqueue_hooks(klass).collect do |hook| + klass.send(hook, *args) + end + return nil if before_hooks.any? { |result| result == false } + + Job.create(queue, klass, *args) + + Plugin.after_enqueue_hooks(klass).each do |hook| + klass.send(hook, *args) + end + + return true + end + + # This method can be used to conveniently remove a job from a queue. + # It assumes the class you're passing it is a real Ruby class (not + # a string or reference) which either: + # + # a) has a @queue ivar set + # b) responds to `queue` + # + # If either of those conditions are met, it will use the value obtained + # from performing one of the above operations to determine the queue. + # + # If no queue can be inferred this method will raise a `Resque::NoQueueError` + # + # If no args are given, this method will dequeue *all* jobs matching + # the provided class. See `Resque::Job.destroy` for more + # information. + # + # Returns the number of jobs destroyed. + # + # Example: + # + # # Removes all jobs of class `UpdateNetworkGraph` + # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph) + # + # # Removes all jobs of class `UpdateNetworkGraph` with matching args. + # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph, 'repo:135325') + # + # This method is considered part of the `stable` API. + def dequeue(klass, *args) + # Perform before_dequeue hooks. Don't perform dequeue if any hook returns false + before_hooks = Plugin.before_dequeue_hooks(klass).collect do |hook| + klass.send(hook, *args) + end + return if before_hooks.any? { |result| result == false } + + Job.destroy(queue_from_class(klass), klass, *args) + + Plugin.after_dequeue_hooks(klass).each do |hook| + klass.send(hook, *args) + end + end + + # Given a class, try to extrapolate an appropriate queue based on a + # class instance variable or `queue` method. + def queue_from_class(klass) + klass.instance_variable_get(:@queue) || + (klass.respond_to?(:queue) and klass.queue) + end + + # This method will return a `Resque::Job` object or a non-true value + # depending on whether a job can be obtained. You should pass it the + # precise name of a queue: case matters. + # + # This method is considered part of the `stable` API. + def reserve(queue) + Job.reserve(queue) + end + + # Validates if the given klass could be a valid Resque job + # + # If no queue can be inferred this method will raise a `Resque::NoQueueError` + # + # If given klass is nil this method will raise a `Resque::NoClassError` + def validate(klass, queue = nil) + queue ||= queue_from_class(klass) + + if !queue + raise NoQueueError.new("Jobs must be placed onto a queue.") + end + + if klass.to_s.empty? + raise NoClassError.new("Jobs must be given a class.") + end + end + + + # + # worker shortcuts + # + + # A shortcut to Worker.all + def workers + Worker.all + end + + # A shortcut to Worker.working + def working + Worker.working + end + + # A shortcut to unregister_worker + # useful for command line tool + def remove_worker(worker_id) + worker = Resque::Worker.find(worker_id) + worker.unregister_worker + end + + # + # stats + # + + # Returns a hash, similar to redis-rb's #info, of interesting stats. + def info + return { + :pending => queues.inject(0) { |m,k| m + size(k) }, + :processed => Stat[:processed], + :queues => queues.size, + :workers => workers.size.to_i, + :working => working.size, + :failed => Stat[:failed], + :servers => [redis_id], + :environment => ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development' + } + end + + # Returns an array of all known Resque keys in Redis. Redis' KEYS operation + # is O(N) for the keyspace, so be careful - this can be slow for big databases. + def keys + redis.keys("*").map do |key| + key.sub("#{redis.namespace}:", '') + end + end +end + diff --git a/samples/Ruby/script.rake b/samples/Ruby/script.rake new file mode 100755 index 0000000..96992aa --- /dev/null +++ b/samples/Ruby/script.rake @@ -0,0 +1,4 @@ +#!/usr/bin/env rake +task :default do + puts "Rake" +end diff --git a/samples/Ruby/script.rb b/samples/Ruby/script.rb new file mode 100755 index 0000000..01a30f8 --- /dev/null +++ b/samples/Ruby/script.rb @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +puts "Ruby" diff --git a/samples/Ruby/script2.rb b/samples/Ruby/script2.rb new file mode 100644 index 0000000..40aeb7c --- /dev/null +++ b/samples/Ruby/script2.rb @@ -0,0 +1,2 @@ +#! /usr/bin/env ruby -w -Ilib:test +echo "Ruby" diff --git a/samples/Ruby/sinatra.rb b/samples/Ruby/sinatra.rb new file mode 100644 index 0000000..36ec2a2 --- /dev/null +++ b/samples/Ruby/sinatra.rb @@ -0,0 +1,1775 @@ +# external dependencies +require 'rack' +require 'tilt' +require "rack/protection" + +# stdlib dependencies +require 'thread' +require 'time' +require 'uri' + +# other files we need +require 'sinatra/showexceptions' +require 'sinatra/version' + +module Sinatra + # The request object. See Rack::Request for more info: + # http://rack.rubyforge.org/doc/classes/Rack/Request.html + class Request < Rack::Request + # Returns an array of acceptable media types for the response + def accept + @env['sinatra.accept'] ||= begin + entries = @env['HTTP_ACCEPT'].to_s.split(',') + entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first) + end + end + + def preferred_type(*types) + return accept.first if types.empty? + types.flatten! + accept.detect do |pattern| + type = types.detect { |t| File.fnmatch(pattern, t) } + return type if type + end + end + + alias accept? preferred_type + alias secure? ssl? + + def forwarded? + @env.include? "HTTP_X_FORWARDED_HOST" + end + + def safe? + get? or head? or options? or trace? + end + + def idempotent? + safe? or put? or delete? + end + + private + + def accept_entry(entry) + type, *options = entry.delete(' ').split(';') + quality = 0 # we sort smallest first + options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' } + [type, [quality, type.count('*'), 1 - options.size]] + end + end + + # The response object. See Rack::Response and Rack::ResponseHelpers for + # more info: + # http://rack.rubyforge.org/doc/classes/Rack/Response.html + # http://rack.rubyforge.org/doc/classes/Rack/Response/Helpers.html + class Response < Rack::Response + def body=(value) + value = value.body while Rack::Response === value + @body = String === value ? [value.to_str] : value + end + + def each + block_given? ? super : enum_for(:each) + end + + def finish + if status.to_i / 100 == 1 + headers.delete "Content-Length" + headers.delete "Content-Type" + elsif Array === body and not [204, 304].include?(status.to_i) + # if some other code has already set Content-Length, don't muck with it + # currently, this would be the static file-handler + headers["Content-Length"] ||= body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s + end + + # Rack::Response#finish sometimes returns self as response body. We don't want that. + status, headers, result = super + result = body if result == self + [status, headers, result] + end + end + + # Some Rack handlers (Thin, Rainbows!) implement an extended body object protocol, however, + # some middleware (namely Rack::Lint) will break it by not mirroring the methods in question. + # This middleware will detect an extended body object and will make sure it reaches the + # handler directly. We do this here, so our middleware and middleware set up by the app will + # still be able to run. + class ExtendedRack < Struct.new(:app) + def call(env) + result, callback = app.call(env), env['async.callback'] + return result unless callback and async?(*result) + after_response { callback.call result } + setup_close(env, *result) + throw :async + end + + private + + def setup_close(env, status, header, body) + return unless body.respond_to? :close and env.include? 'async.close' + env['async.close'].callback { body.close } + env['async.close'].errback { body.close } + end + + def after_response(&block) + raise NotImplementedError, "only supports EventMachine at the moment" unless defined? EventMachine + EventMachine.next_tick(&block) + end + + def async?(status, headers, body) + return true if status == -1 + body.respond_to? :callback and body.respond_to? :errback + end + end + + # Behaves exactly like Rack::CommonLogger with the notable exception that it does nothing, + # if another CommonLogger is already in the middleware chane. + class CommonLogger < Rack::CommonLogger + def call(env) + env['sinatra.commonlogger'] ? @app.call(env) : super + end + + superclass.class_eval do + alias call_without_check call unless method_defined? :call_without_check + def call(env) + env['sinatra.commonlogger'] = true + call_without_check(env) + end + end + end + + class NotFound < NameError #:nodoc: + def http_status; 404 end + end + + # Methods available to routes, before/after filters, and views. + module Helpers + # Set or retrieve the response status code. + def status(value=nil) + response.status = value if value + response.status + end + + # Set or retrieve the response body. When a block is given, + # evaluation is deferred until the body is read with #each. + def body(value=nil, &block) + if block_given? + def block.each; yield(call) end + response.body = block + elsif value + response.body = value + else + response.body + end + end + + # Halt processing and redirect to the URI provided. + def redirect(uri, *args) + if env['HTTP_VERSION'] == 'HTTP/1.1' and env["REQUEST_METHOD"] != 'GET' + status 303 + else + status 302 + end + + # According to RFC 2616 section 14.30, "the field value consists of a + # single absolute URI" + response['Location'] = uri(uri, settings.absolute_redirects?, settings.prefixed_redirects?) + halt(*args) + end + + # Generates the absolute URI for a given path in the app. + # Takes Rack routers and reverse proxies into account. + def uri(addr = nil, absolute = true, add_script_name = true) + return addr if addr =~ /\A[A-z][A-z0-9\+\.\-]*:/ + uri = [host = ""] + if absolute + host << "http#{'s' if request.secure?}://" + if request.forwarded? or request.port != (request.secure? ? 443 : 80) + host << request.host_with_port + else + host << request.host + end + end + uri << request.script_name.to_s if add_script_name + uri << (addr ? addr : request.path_info).to_s + File.join uri + end + + alias url uri + alias to uri + + # Halt processing and return the error status provided. + def error(code, body=nil) + code, body = 500, code.to_str if code.respond_to? :to_str + response.body = body unless body.nil? + halt code + end + + # Halt processing and return a 404 Not Found. + def not_found(body=nil) + error 404, body + end + + # Set multiple response headers with Hash. + def headers(hash=nil) + response.headers.merge! hash if hash + response.headers + end + + # Access the underlying Rack session. + def session + request.session + end + + # Access shared logger object. + def logger + request.logger + end + + # Look up a media type by file extension in Rack's mime registry. + def mime_type(type) + Base.mime_type(type) + end + + # Set the Content-Type of the response body given a media type or file + # extension. + def content_type(type = nil, params={}) + return response['Content-Type'] unless type + default = params.delete :default + mime_type = mime_type(type) || default + fail "Unknown media type: %p" % type if mime_type.nil? + mime_type = mime_type.dup + unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type } + params[:charset] = params.delete('charset') || settings.default_encoding + end + params.delete :charset if mime_type.include? 'charset' + unless params.empty? + mime_type << (mime_type.include?(';') ? ', ' : ';') + mime_type << params.map { |kv| kv.join('=') }.join(', ') + end + response['Content-Type'] = mime_type + end + + # Set the Content-Disposition to "attachment" with the specified filename, + # instructing the user agents to prompt to save. + def attachment(filename=nil) + response['Content-Disposition'] = 'attachment' + if filename + params = '; filename="%s"' % File.basename(filename) + response['Content-Disposition'] << params + ext = File.extname(filename) + content_type(ext) unless response['Content-Type'] or ext.empty? + end + end + + # Use the contents of the file at +path+ as the response body. + def send_file(path, opts={}) + if opts[:type] or not response['Content-Type'] + content_type opts[:type] || File.extname(path), :default => 'application/octet-stream' + end + + if opts[:disposition] == 'attachment' || opts[:filename] + attachment opts[:filename] || path + elsif opts[:disposition] == 'inline' + response['Content-Disposition'] = 'inline' + end + + last_modified opts[:last_modified] if opts[:last_modified] + + file = Rack::File.new nil + file.path = path + result = file.serving env + result[1].each { |k,v| headers[k] ||= v } + headers['Content-Length'] = result[1]['Content-Length'] + halt opts[:status] || result[0], result[2] + rescue Errno::ENOENT + not_found + end + + # Class of the response body in case you use #stream. + # + # Three things really matter: The front and back block (back being the + # blog generating content, front the one sending it to the client) and + # the scheduler, integrating with whatever concurrency feature the Rack + # handler is using. + # + # Scheduler has to respond to defer and schedule. + class Stream + def self.schedule(*) yield end + def self.defer(*) yield end + + def initialize(scheduler = self.class, keep_open = false, &back) + @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open + @callbacks, @closed = [], false + end + + def close + return if @closed + @closed = true + @scheduler.schedule { @callbacks.each { |c| c.call }} + end + + def each(&front) + @front = front + @scheduler.defer do + begin + @back.call(self) + rescue Exception => e + @scheduler.schedule { raise e } + end + close unless @keep_open + end + end + + def <<(data) + @scheduler.schedule { @front.call(data.to_s) } + self + end + + def callback(&block) + return yield if @closed + @callbacks << block + end + + alias errback callback + end + + # Allows to start sending data to the client even though later parts of + # the response body have not yet been generated. + # + # The close parameter specifies whether Stream#close should be called + # after the block has been executed. This is only relevant for evented + # servers like Thin or Rainbows. + def stream(keep_open = false) + scheduler = env['async.callback'] ? EventMachine : Stream + current = @params.dup + body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } } + end + + # Specify response freshness policy for HTTP caches (Cache-Control header). + # Any number of non-value directives (:public, :private, :no_cache, + # :no_store, :must_revalidate, :proxy_revalidate) may be passed along with + # a Hash of value directives (:max_age, :min_stale, :s_max_age). + # + # cache_control :public, :must_revalidate, :max_age => 60 + # => Cache-Control: public, must-revalidate, max-age=60 + # + # See RFC 2616 / 14.9 for more on standard cache control directives: + # http://tools.ietf.org/html/rfc2616#section-14.9.1 + def cache_control(*values) + if values.last.kind_of?(Hash) + hash = values.pop + hash.reject! { |k,v| v == false } + hash.reject! { |k,v| values << k if v == true } + else + hash = {} + end + + values.map! { |value| value.to_s.tr('_','-') } + hash.each do |key, value| + key = key.to_s.tr('_', '-') + value = value.to_i if key == "max-age" + values << [key, value].join('=') + end + + response['Cache-Control'] = values.join(', ') if values.any? + end + + # Set the Expires header and Cache-Control/max-age directive. Amount + # can be an integer number of seconds in the future or a Time object + # indicating when the response should be considered "stale". The remaining + # "values" arguments are passed to the #cache_control helper: + # + # expires 500, :public, :must_revalidate + # => Cache-Control: public, must-revalidate, max-age=60 + # => Expires: Mon, 08 Jun 2009 08:50:17 GMT + # + def expires(amount, *values) + values << {} unless values.last.kind_of?(Hash) + + if amount.is_a? Integer + time = Time.now + amount.to_i + max_age = amount + else + time = time_for amount + max_age = time - Time.now + end + + values.last.merge!(:max_age => max_age) + cache_control(*values) + + response['Expires'] = time.httpdate + end + + # Set the last modified time of the resource (HTTP 'Last-Modified' header) + # and halt if conditional GET matches. The +time+ argument is a Time, + # DateTime, or other object that responds to +to_time+. + # + # When the current request includes an 'If-Modified-Since' header that is + # equal or later than the time specified, execution is immediately halted + # with a '304 Not Modified' response. + def last_modified(time) + return unless time + time = time_for time + response['Last-Modified'] = time.httpdate + return if env['HTTP_IF_NONE_MATCH'] + + if status == 200 and env['HTTP_IF_MODIFIED_SINCE'] + # compare based on seconds since epoch + since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i + halt 304 if since >= time.to_i + end + + if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE'] + # compare based on seconds since epoch + since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i + halt 412 if since < time.to_i + end + rescue ArgumentError + end + + # Set the response entity tag (HTTP 'ETag' header) and halt if conditional + # GET matches. The +value+ argument is an identifier that uniquely + # identifies the current version of the resource. The +kind+ argument + # indicates whether the etag should be used as a :strong (default) or :weak + # cache validator. + # + # When the current request includes an 'If-None-Match' header with a + # matching etag, execution is immediately halted. If the request method is + # GET or HEAD, a '304 Not Modified' response is sent. + def etag(value, options = {}) + # Before touching this code, please double check RFC 2616 14.24 and 14.26. + options = {:kind => options} unless Hash === options + kind = options[:kind] || :strong + new_resource = options.fetch(:new_resource) { request.post? } + + unless [:strong, :weak].include?(kind) + raise ArgumentError, ":strong or :weak expected" + end + + value = '"%s"' % value + value = 'W/' + value if kind == :weak + response['ETag'] = value + + if success? or status == 304 + if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource + halt(request.safe? ? 304 : 412) + end + + if env['HTTP_IF_MATCH'] + halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource + end + end + end + + # Sugar for redirect (example: redirect back) + def back + request.referer + end + + # whether or not the status is set to 1xx + def informational? + status.between? 100, 199 + end + + # whether or not the status is set to 2xx + def success? + status.between? 200, 299 + end + + # whether or not the status is set to 3xx + def redirect? + status.between? 300, 399 + end + + # whether or not the status is set to 4xx + def client_error? + status.between? 400, 499 + end + + # whether or not the status is set to 5xx + def server_error? + status.between? 500, 599 + end + + # whether or not the status is set to 404 + def not_found? + status == 404 + end + + # Generates a Time object from the given value. + # Used by #expires and #last_modified. + def time_for(value) + if value.respond_to? :to_time + value.to_time + elsif value.is_a? Time + value + elsif value.respond_to? :new_offset + # DateTime#to_time does the same on 1.9 + d = value.new_offset 0 + t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction + t.getlocal + elsif value.respond_to? :mday + # Date#to_time does the same on 1.9 + Time.local(value.year, value.mon, value.mday) + elsif value.is_a? Numeric + Time.at value + else + Time.parse value.to_s + end + rescue ArgumentError => boom + raise boom + rescue Exception + raise ArgumentError, "unable to convert #{value.inspect} to a Time object" + end + + private + + # Helper method checking if a ETag value list includes the current ETag. + def etag_matches?(list, new_resource = request.post?) + return !new_resource if list == '*' + list.to_s.split(/\s*,\s*/).include? response['ETag'] + end + + def with_params(temp_params) + original, @params = @params, temp_params + yield + ensure + @params = original if original + end + end + + private + + # Template rendering methods. Each method takes the name of a template + # to render as a Symbol and returns a String with the rendered output, + # as well as an optional hash with additional options. + # + # `template` is either the name or path of the template as symbol + # (Use `:'subdir/myview'` for views in subdirectories), or a string + # that will be rendered. + # + # Possible options are: + # :content_type The content type to use, same arguments as content_type. + # :layout If set to false, no layout is rendered, otherwise + # the specified layout is used (Ignored for `sass` and `less`) + # :layout_engine Engine to use for rendering the layout. + # :locals A hash with local variables that should be available + # in the template + # :scope If set, template is evaluate with the binding of the given + # object rather than the application instance. + # :views Views directory to use. + module Templates + module ContentTyped + attr_accessor :content_type + end + + def initialize + super + @default_layout = :layout + end + + def erb(template, options={}, locals={}) + render :erb, template, options, locals + end + + def erubis(template, options={}, locals={}) + warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \ + "If you have Erubis installed, it will be used automatically." + render :erubis, template, options, locals + end + + def haml(template, options={}, locals={}) + render :haml, template, options, locals + end + + def sass(template, options={}, locals={}) + options.merge! :layout => false, :default_content_type => :css + render :sass, template, options, locals + end + + def scss(template, options={}, locals={}) + options.merge! :layout => false, :default_content_type => :css + render :scss, template, options, locals + end + + def less(template, options={}, locals={}) + options.merge! :layout => false, :default_content_type => :css + render :less, template, options, locals + end + + def builder(template=nil, options={}, locals={}, &block) + options[:default_content_type] = :xml + render_ruby(:builder, template, options, locals, &block) + end + + def liquid(template, options={}, locals={}) + render :liquid, template, options, locals + end + + def markdown(template, options={}, locals={}) + render :markdown, template, options, locals + end + + def textile(template, options={}, locals={}) + render :textile, template, options, locals + end + + def rdoc(template, options={}, locals={}) + render :rdoc, template, options, locals + end + + def radius(template, options={}, locals={}) + render :radius, template, options, locals + end + + def markaby(template=nil, options={}, locals={}, &block) + render_ruby(:mab, template, options, locals, &block) + end + + def coffee(template, options={}, locals={}) + options.merge! :layout => false, :default_content_type => :js + render :coffee, template, options, locals + end + + def nokogiri(template=nil, options={}, locals={}, &block) + options[:default_content_type] = :xml + render_ruby(:nokogiri, template, options, locals, &block) + end + + def slim(template, options={}, locals={}) + render :slim, template, options, locals + end + + def creole(template, options={}, locals={}) + render :creole, template, options, locals + end + + def yajl(template, options={}, locals={}) + options[:default_content_type] = :json + render :yajl, template, options, locals + end + + # Calls the given block for every possible template file in views, + # named name.ext, where ext is registered on engine. + def find_template(views, name, engine) + yield ::File.join(views, "#{name}.#{@preferred_extension}") + Tilt.mappings.each do |ext, engines| + next unless ext != @preferred_extension and engines.include? engine + yield ::File.join(views, "#{name}.#{ext}") + end + end + + private + # logic shared between builder and nokogiri + def render_ruby(engine, template, options={}, locals={}, &block) + options, template = template, nil if template.is_a?(Hash) + template = Proc.new { block } if template.nil? + render engine, template, options, locals + end + + def render(engine, data, options={}, locals={}, &block) + # merge app-level options + options = settings.send(engine).merge(options) if settings.respond_to?(engine) + options[:outvar] ||= '@_out_buf' + options[:default_encoding] ||= settings.default_encoding + + # extract generic options + locals = options.delete(:locals) || locals || {} + views = options.delete(:views) || settings.views || "./views" + layout = options.delete(:layout) + eat_errors = layout.nil? + layout = @default_layout if layout.nil? or layout == true + content_type = options.delete(:content_type) || options.delete(:default_content_type) + layout_engine = options.delete(:layout_engine) || engine + scope = options.delete(:scope) || self + + # compile and render template + begin + layout_was = @default_layout + @default_layout = false + template = compile_template(engine, data, options, views) + output = template.render(scope, locals, &block) + ensure + @default_layout = layout_was + end + + # render layout + if layout + options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope) + catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } } + end + + output.extend(ContentTyped).content_type = content_type if content_type + output + end + + def compile_template(engine, data, options, views) + eat_errors = options.delete :eat_errors + template_cache.fetch engine, data, options do + template = Tilt[engine] + raise "Template engine not found: #{engine}" if template.nil? + + case data + when Symbol + body, path, line = settings.templates[data] + if body + body = body.call if body.respond_to?(:call) + template.new(path, line.to_i, options) { body } + else + found = false + @preferred_extension = engine.to_s + find_template(views, data, template) do |file| + path ||= file # keep the initial path rather than the last one + if found = File.exists?(file) + path = file + break + end + end + throw :layout_missing if eat_errors and not found + template.new(path, 1, options) + end + when Proc, String + body = data.is_a?(String) ? Proc.new { data } : data + path, line = settings.caller_locations.first + template.new(path, line.to_i, options, &body) + else + raise ArgumentError, "Sorry, don't know how to render #{data.inspect}." + end + end + end + end + + # Base class for all Sinatra applications and middleware. + class Base + include Rack::Utils + include Helpers + include Templates + + attr_accessor :app + attr_reader :template_cache + + def initialize(app=nil) + super() + @app = app + @template_cache = Tilt::Cache.new + yield self if block_given? + end + + # Rack call interface. + def call(env) + dup.call!(env) + end + + attr_accessor :env, :request, :response, :params + + def call!(env) # :nodoc: + @env = env + @request = Request.new(env) + @response = Response.new + @params = indifferent_params(@request.params) + template_cache.clear if settings.reload_templates + force_encoding(@params) + + @response['Content-Type'] = nil + invoke { dispatch! } + invoke { error_block!(response.status) } + + unless @response['Content-Type'] + if Array === body and body[0].respond_to? :content_type + content_type body[0].content_type + else + content_type :html + end + end + + @response.finish + end + + # Access settings defined with Base.set. + def self.settings + self + end + + # Access settings defined with Base.set. + def settings + self.class.settings + end + + def options + warn "Sinatra::Base#options is deprecated and will be removed, " \ + "use #settings instead." + settings + end + + # Exit the current block, halts any further processing + # of the request, and returns the specified response. + def halt(*response) + response = response.first if response.length == 1 + throw :halt, response + end + + # Pass control to the next matching route. + # If there are no more matching routes, Sinatra will + # return a 404 response. + def pass(&block) + throw :pass, block + end + + # Forward the request to the downstream app -- middleware only. + def forward + fail "downstream app not set" unless @app.respond_to? :call + status, headers, body = @app.call env + @response.status = status + @response.body = body + @response.headers.merge! headers + nil + end + + private + # Run filters defined on the class and all superclasses. + def filter!(type, base = settings) + filter! type, base.superclass if base.superclass.respond_to?(:filters) + base.filters[type].each { |args| process_route(*args) } + end + + # Run routes defined on the class and all superclasses. + def route!(base = settings, pass_block=nil) + if routes = base.routes[@request.request_method] + routes.each do |pattern, keys, conditions, block| + pass_block = process_route(pattern, keys, conditions) do |*args| + route_eval { block[*args] } + end + end + end + + # Run routes defined in superclass. + if base.superclass.respond_to?(:routes) + return route!(base.superclass, pass_block) + end + + route_eval(&pass_block) if pass_block + route_missing + end + + # Run a route block and throw :halt with the result. + def route_eval + throw :halt, yield + end + + # If the current request matches pattern and conditions, fill params + # with keys and call the given block. + # Revert params afterwards. + # + # Returns pass block. + def process_route(pattern, keys, conditions, block = nil, values = []) + route = @request.path_info + route = '/' if route.empty? and not settings.empty_path_info? + return unless match = pattern.match(route) + values += match.captures.to_a.map { |v| force_encoding URI.decode_www_form_component(v) if v } + + if values.any? + original, @params = params, params.merge('splat' => [], 'captures' => values) + keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v } + end + + catch(:pass) do + conditions.each { |c| throw :pass if c.bind(self).call == false } + block ? block[self, values] : yield(self, values) + end + ensure + @params = original if original + end + + # No matching route was found or all routes passed. The default + # implementation is to forward the request downstream when running + # as middleware (@app is non-nil); when no downstream app is set, raise + # a NotFound exception. Subclasses can override this method to perform + # custom route miss logic. + def route_missing + if @app + forward + else + raise NotFound + end + end + + # Attempt to serve static files from public directory. Throws :halt when + # a matching file is found, returns nil otherwise. + def static! + return if (public_dir = settings.public_folder).nil? + public_dir = File.expand_path(public_dir) + + path = File.expand_path(public_dir + unescape(request.path_info)) + return unless path.start_with?(public_dir) and File.file?(path) + + env['sinatra.static_file'] = path + cache_control(*settings.static_cache_control) if settings.static_cache_control? + send_file path, :disposition => nil + end + + # Enable string or symbol key access to the nested params hash. + def indifferent_params(object) + case object + when Hash + new_hash = indifferent_hash + object.each { |key, value| new_hash[key] = indifferent_params(value) } + new_hash + when Array + object.map { |item| indifferent_params(item) } + else + object + end + end + + # Creates a Hash with indifferent access. + def indifferent_hash + Hash.new {|hash,key| hash[key.to_s] if Symbol === key } + end + + # Run the block with 'throw :halt' support and apply result to the response. + def invoke + res = catch(:halt) { yield } + res = [res] if Fixnum === res or String === res + if Array === res and Fixnum === res.first + status(res.shift) + body(res.pop) + headers(*res) + elsif res.respond_to? :each + body res + end + nil # avoid double setting the same response tuple twice + end + + # Dispatch a request with error handling. + def dispatch! + invoke do + static! if settings.static? && (request.get? || request.head?) + filter! :before + route! + end + rescue ::Exception => boom + invoke { handle_exception!(boom) } + ensure + filter! :after unless env['sinatra.static_file'] + end + + # Error handling during requests. + def handle_exception!(boom) + @env['sinatra.error'] = boom + + if boom.respond_to? :http_status + status(boom.http_status) + elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599 + status(boom.code) + else + status(500) + end + + status(500) unless status.between? 400, 599 + + if server_error? + dump_errors! boom if settings.dump_errors? + raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler + end + + if not_found? + headers['X-Cascade'] = 'pass' + body '<h1>Not Found</h1>' + end + + res = error_block!(boom.class, boom) || error_block!(status, boom) + return res if res or not server_error? + raise boom if settings.raise_errors? or settings.show_exceptions? + error_block! Exception, boom + end + + # Find an custom error block for the key(s) specified. + def error_block!(key, *block_params) + base = settings + while base.respond_to?(:errors) + next base = base.superclass unless args_array = base.errors[key] + args_array.reverse_each do |args| + first = args == args_array.first + args += [block_params] + resp = process_route(*args) + return resp unless resp.nil? && !first + end + end + return false unless key.respond_to? :superclass and key.superclass < Exception + error_block!(key.superclass, *block_params) + end + + def dump_errors!(boom) + msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t") + @env['rack.errors'].puts(msg) + end + + class << self + attr_reader :routes, :filters, :templates, :errors + + # Removes all routes, filters, middleware and extension hooks from the + # current class (not routes/filters/... defined by its superclass). + def reset! + @conditions = [] + @routes = {} + @filters = {:before => [], :after => []} + @errors = {} + @middleware = [] + @prototype = nil + @extensions = [] + + if superclass.respond_to?(:templates) + @templates = Hash.new { |hash,key| superclass.templates[key] } + else + @templates = {} + end + end + + # Extension modules registered on this class and all superclasses. + def extensions + if superclass.respond_to?(:extensions) + (@extensions + superclass.extensions).uniq + else + @extensions + end + end + + # Middleware used in this class and all superclasses. + def middleware + if superclass.respond_to?(:middleware) + superclass.middleware + @middleware + else + @middleware + end + end + + # Sets an option to the given value. If the value is a proc, + # the proc will be called every time the option is accessed. + def set(option, value = (not_set = true), ignore_setter = false, &block) + raise ArgumentError if block and !not_set + value, not_set = block, false if block + + if not_set + raise ArgumentError unless option.respond_to?(:each) + option.each { |k,v| set(k, v) } + return self + end + + if respond_to?("#{option}=") and not ignore_setter + return __send__("#{option}=", value) + end + + setter = proc { |val| set option, val, true } + getter = proc { value } + + case value + when Proc + getter = value + when Symbol, Fixnum, FalseClass, TrueClass, NilClass + getter = value.inspect + when Hash + setter = proc do |val| + val = value.merge val if Hash === val + set option, val, true + end + end + + define_singleton_method("#{option}=", setter) if setter + define_singleton_method(option, getter) if getter + define_singleton_method("#{option}?", "!!#{option}") unless method_defined? "#{option}?" + self + end + + # Same as calling `set :option, true` for each of the given options. + def enable(*opts) + opts.each { |key| set(key, true) } + end + + # Same as calling `set :option, false` for each of the given options. + def disable(*opts) + opts.each { |key| set(key, false) } + end + + # Define a custom error handler. Optionally takes either an Exception + # class, or an HTTP status code to specify which errors should be + # handled. + def error(*codes, &block) + args = compile! "ERROR", //, block + codes = codes.map { |c| Array(c) }.flatten + codes << Exception if codes.empty? + codes.each { |c| (@errors[c] ||= []) << args } + end + + # Sugar for `error(404) { ... }` + def not_found(&block) + error 404, &block + end + + # Define a named template. The block must return the template source. + def template(name, &block) + filename, line = caller_locations.first + templates[name] = [block, filename, line.to_i] + end + + # Define the layout template. The block must return the template source. + def layout(name=:layout, &block) + template name, &block + end + + # Load embeded templates from the file; uses the caller's __FILE__ + # when no file is specified. + def inline_templates=(file=nil) + file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file + + begin + io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file) + app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2) + rescue Errno::ENOENT + app, data = nil + end + + if data + if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m + encoding = $2 + else + encoding = settings.default_encoding + end + lines = app.count("\n") + 1 + template = nil + force_encoding data, encoding + data.each_line do |line| + lines += 1 + if line =~ /^@@\s*(.*\S)\s*$/ + template = force_encoding('', encoding) + templates[$1.to_sym] = [template, file, lines] + elsif template + template << line + end + end + end + end + + # Lookup or register a mime type in Rack's mime registry. + def mime_type(type, value=nil) + return type if type.nil? || type.to_s.include?('/') + type = ".#{type}" unless type.to_s[0] == ?. + return Rack::Mime.mime_type(type, nil) unless value + Rack::Mime::MIME_TYPES[type] = value + end + + # provides all mime types matching type, including deprecated types: + # mime_types :html # => ['text/html'] + # mime_types :js # => ['application/javascript', 'text/javascript'] + def mime_types(type) + type = mime_type type + type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type] + end + + # Define a before filter; runs before all requests within the same + # context as route handlers and may access/modify the request and + # response. + def before(path = nil, options = {}, &block) + add_filter(:before, path, options, &block) + end + + # Define an after filter; runs after all requests within the same + # context as route handlers and may access/modify the request and + # response. + def after(path = nil, options = {}, &block) + add_filter(:after, path, options, &block) + end + + # add a filter + def add_filter(type, path = nil, options = {}, &block) + path, options = //, path if path.respond_to?(:each_pair) + filters[type] << compile!(type, path || //, block, options) + end + + # Add a route condition. The route is considered non-matching when the + # block returns false. + def condition(name = "#{caller.first[/`.*'/]} condition", &block) + @conditions << generate_method(name, &block) + end + + def public=(value) + warn ":public is no longer used to avoid overloading Module#public, use :public_dir instead" + set(:public_folder, value) + end + + def public_dir=(value) + self.public_folder = value + end + + def public_dir + public_folder + end + + private + # Dynamically defines a method on settings. + def define_singleton_method(name, content = Proc.new) + # replace with call to singleton_class once we're 1.9 only + (class << self; self; end).class_eval do + undef_method(name) if method_defined? name + String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content) + end + end + + # Condition for matching host name. Parameter might be String or Regexp. + def host_name(pattern) + condition { pattern === request.host } + end + + # Condition for matching user agent. Parameter should be Regexp. + # Will set params[:agent]. + def user_agent(pattern) + condition do + if request.user_agent.to_s =~ pattern + @params[:agent] = $~[1..-1] + true + else + false + end + end + end + alias_method :agent, :user_agent + + # Condition for matching mimetypes. Accepts file extensions. + def provides(*types) + types.map! { |t| mime_types(t) } + types.flatten! + condition do + if type = response['Content-Type'] + types.include? type or types.include? type[/^[^;]+/] + elsif type = request.preferred_type(types) + content_type(type) + true + else + false + end + end + end + + public + # Defining a `GET` handler also automatically defines + # a `HEAD` handler. + def get(path, opts={}, &block) + conditions = @conditions.dup + route('GET', path, opts, &block) + + @conditions = conditions + route('HEAD', path, opts, &block) + end + + def put(path, opts={}, &bk) route 'PUT', path, opts, &bk end + def post(path, opts={}, &bk) route 'POST', path, opts, &bk end + def delete(path, opts={}, &bk) route 'DELETE', path, opts, &bk end + def head(path, opts={}, &bk) route 'HEAD', path, opts, &bk end + def options(path, opts={}, &bk) route 'OPTIONS', path, opts, &bk end + def patch(path, opts={}, &bk) route 'PATCH', path, opts, &bk end + + private + def route(verb, path, options={}, &block) + # Because of self.options.host + host_name(options.delete(:host)) if options.key?(:host) + enable :empty_path_info if path == "" and empty_path_info.nil? + signature = compile!(verb, path, block, options) + (@routes[verb] ||= []) << signature + invoke_hook(:route_added, verb, path, block) + signature + end + + def invoke_hook(name, *args) + extensions.each { |e| e.send(name, *args) if e.respond_to?(name) } + end + + def generate_method(method_name, &block) + define_method(method_name, &block) + method = instance_method method_name + remove_method method_name + method + end + + def compile!(verb, path, block, options = {}) + options.each_pair { |option, args| send(option, *args) } + method_name = "#{verb} #{path}" + unbound_method = generate_method(method_name, &block) + pattern, keys = compile path + conditions, @conditions = @conditions, [] + + [ pattern, keys, conditions, block.arity != 0 ? + proc { |a,p| unbound_method.bind(a).call(*p) } : + proc { |a,p| unbound_method.bind(a).call } ] + end + + def compile(path) + keys = [] + if path.respond_to? :to_str + pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) { |c| encoded(c) } + pattern.gsub!(/((:\w+)|\*)/) do |match| + if match == "*" + keys << 'splat' + "(.*?)" + else + keys << $2[1..-1] + "([^/?#]+)" + end + end + [/^#{pattern}$/, keys] + elsif path.respond_to?(:keys) && path.respond_to?(:match) + [path, path.keys] + elsif path.respond_to?(:names) && path.respond_to?(:match) + [path, path.names] + elsif path.respond_to? :match + [path, keys] + else + raise TypeError, path + end + end + + URI = ::URI.const_defined?(:Parser) ? ::URI::Parser.new : ::URI + + def encoded(char) + enc = URI.escape(char) + enc = "(?:#{Regexp.escape enc}|#{URI.escape char, /./})" if enc == char + enc = "(?:#{enc}|#{encoded('+')})" if char == " " + enc + end + + public + # Makes the methods defined in the block and in the Modules given + # in `extensions` available to the handlers and templates + def helpers(*extensions, &block) + class_eval(&block) if block_given? + include(*extensions) if extensions.any? + end + + # Register an extension. Alternatively take a block from which an + # extension will be created and registered on the fly. + def register(*extensions, &block) + extensions << Module.new(&block) if block_given? + @extensions += extensions + extensions.each do |extension| + extend extension + extension.registered(self) if extension.respond_to?(:registered) + end + end + + def development?; environment == :development end + def production?; environment == :production end + def test?; environment == :test end + + # Set configuration options for Sinatra and/or the app. + # Allows scoping of settings for certain environments. + def configure(*envs, &block) + yield self if envs.empty? || envs.include?(environment.to_sym) + end + + # Use the specified Rack middleware + def use(middleware, *args, &block) + @prototype = nil + @middleware << [middleware, args, block] + end + + def quit!(server, handler_name) + # Use Thin's hard #stop! if available, otherwise just #stop. + server.respond_to?(:stop!) ? server.stop! : server.stop + $stderr.puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i + end + + # Run the Sinatra app as a self-hosted server using + # Thin, Puma, Mongrel, or WEBrick (in that order). If given a block, will call + # with the constructed handler once we have taken the stage. + def run!(options={}) + set options + handler = detect_rack_handler + handler_name = handler.name.gsub(/.*::/, '') + server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {} + handler.run self, server_settings.merge(:Port => port, :Host => bind) do |server| + unless handler_name =~ /cgi/i + $stderr.puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " + + "on #{port} for #{environment} with backup from #{handler_name}" + end + [:INT, :TERM].each { |sig| trap(sig) { quit!(server, handler_name) } } + server.threaded = settings.threaded if server.respond_to? :threaded= + set :running, true + yield server if block_given? + end + rescue Errno::EADDRINUSE + $stderr.puts "== Someone is already performing on port #{port}!" + end + + # The prototype instance used to process requests. + def prototype + @prototype ||= new + end + + # Create a new instance without middleware in front of it. + alias new! new unless method_defined? :new! + + # Create a new instance of the class fronted by its middleware + # pipeline. The object is guaranteed to respond to #call but may not be + # an instance of the class new was called on. + def new(*args, &bk) + instance = new!(*args, &bk) + Wrapper.new(build(instance).to_app, instance) + end + + # Creates a Rack::Builder instance with all the middleware set up and + # the given +app+ as end point. + def build(app) + builder = Rack::Builder.new + setup_default_middleware builder + setup_middleware builder + builder.run app + builder + end + + def call(env) + synchronize { prototype.call(env) } + end + + private + def setup_default_middleware(builder) + builder.use ExtendedRack + builder.use ShowExceptions if show_exceptions? + builder.use Rack::MethodOverride if method_override? + builder.use Rack::Head + setup_logging builder + setup_sessions builder + setup_protection builder + end + + def setup_middleware(builder) + middleware.each { |c,a,b| builder.use(c, *a, &b) } + end + + def setup_logging(builder) + if logging? + setup_common_logger(builder) + setup_custom_logger(builder) + elsif logging == false + setup_null_logger(builder) + end + end + + def setup_null_logger(builder) + builder.use Rack::NullLogger + end + + def setup_common_logger(builder) + builder.use Sinatra::CommonLogger + end + + def setup_custom_logger(builder) + if logging.respond_to? :to_int + builder.use Rack::Logger, logging + else + builder.use Rack::Logger + end + end + + def setup_protection(builder) + return unless protection? + options = Hash === protection ? protection.dup : {} + options[:except] = Array options[:except] + options[:except] += [:session_hijacking, :remote_token] unless sessions? + options[:reaction] ||= :drop_session + builder.use Rack::Protection, options + end + + def setup_sessions(builder) + return unless sessions? + options = {} + options[:secret] = session_secret if session_secret? + options.merge! sessions.to_hash if sessions.respond_to? :to_hash + builder.use Rack::Session::Cookie, options + end + + def detect_rack_handler + servers = Array(server) + servers.each do |server_name| + begin + return Rack::Handler.get(server_name.to_s) + rescue LoadError, NameError + end + end + fail "Server handler (#{servers.join(',')}) not found." + end + + def inherited(subclass) + subclass.reset! + subclass.set :app_file, caller_files.first unless subclass.app_file? + super + end + + @@mutex = Mutex.new + def synchronize(&block) + if lock? + @@mutex.synchronize(&block) + else + yield + end + end + + public + CALLERS_TO_IGNORE = [ # :nodoc: + /\/sinatra(\/(base|main|showexceptions))?\.rb$/, # all sinatra code + /lib\/tilt.*\.rb$/, # all tilt code + /^\(.*\)$/, # generated code + /rubygems\/custom_require\.rb$/, # rubygems require hacks + /active_support/, # active_support require hacks + /bundler(\/runtime)?\.rb/, # bundler require hacks + /<internal:/, # internal in ruby >= 1.9.2 + /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files + ] + + # contrary to what the comment said previously, rubinius never supported this + if defined?(RUBY_IGNORE_CALLERS) + warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0" + CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) + end + + # Like Kernel#caller but excluding certain magic entries and without + # line / method information; the resulting array contains filenames only. + def caller_files + cleaned_caller(1).flatten + end + + # Like caller_files, but containing Arrays rather than strings with the + # first element being the file, and the second being the line. + def caller_locations + cleaned_caller 2 + end + + private + # used for deprecation warnings + def warn(message) + super message + "\n\tfrom #{cleaned_caller.first.join(':')}" + end + + # Like Kernel#caller but excluding certain magic entries + def cleaned_caller(keep = 3) + caller(1). + map { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }. + reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } } + end + end + + # Fixes encoding issues by + # * defaulting to UTF-8 + # * casting params to Encoding.default_external + # + # The latter might not be necessary if Rack handles it one day. + # Keep an eye on Rack's LH #100. + def force_encoding(*args) settings.force_encoding(*args) end + if defined? Encoding + def self.force_encoding(data, encoding = default_encoding) + return if data == settings || data.is_a?(Tempfile) + if data.respond_to? :force_encoding + data.force_encoding(encoding).encode! + elsif data.respond_to? :each_value + data.each_value { |v| force_encoding(v, encoding) } + elsif data.respond_to? :each + data.each { |v| force_encoding(v, encoding) } + end + data + end + else + def self.force_encoding(data, *) data end + end + + reset! + + set :environment, (ENV['RACK_ENV'] || :development).to_sym + set :raise_errors, Proc.new { test? } + set :dump_errors, Proc.new { !test? } + set :show_exceptions, Proc.new { development? } + set :sessions, false + set :logging, false + set :protection, true + set :method_override, false + set :use_code, false + set :default_encoding, "utf-8" + set :add_charset, %w[javascript xml xhtml+xml json].map { |t| "application/#{t}" } + settings.add_charset << /^text\// + + # explicitly generating a session secret eagerly to play nice with preforking + begin + require 'securerandom' + set :session_secret, SecureRandom.hex(64) + rescue LoadError, NotImplementedError + # SecureRandom raises a NotImplementedError if no random device is available + set :session_secret, "%064x" % Kernel.rand(2**256-1) + end + + class << self + alias_method :methodoverride?, :method_override? + alias_method :methodoverride=, :method_override= + end + + set :run, false # start server via at-exit hook? + set :running, false # is the built-in server running now? + set :server, %w[http webrick] + set :bind, '0.0.0.0' + set :port, 4567 + + ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE + + if ruby_engine == 'macruby' + server.unshift 'controll_tower' + else + server.unshift 'mongrel' if ruby_engine.nil? + server.unshift 'puma' if ruby_engine != 'rbx' + server.unshift 'thin' if ruby_engine != 'jruby' + server.unshift 'puma' if ruby_engine == 'rbx' + server.unshift 'trinidad' if ruby_engine =='jruby' + end + + set :absolute_redirects, true + set :prefixed_redirects, false + set :empty_path_info, nil + + set :app_file, nil + set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) } + set :views, Proc.new { root && File.join(root, 'views') } + set :reload_templates, Proc.new { development? } + set :lock, false + set :threaded, true + + set :public_folder, Proc.new { root && File.join(root, 'public') } + set :static, Proc.new { public_folder && File.exist?(public_folder) } + set :static_cache_control, false + + error ::Exception do + response.status = 500 + content_type 'text/html' + '<h1>Internal Server Error</h1>' + end + + configure :development do + get '/__sinatra__/:image.png' do + filename = File.dirname(__FILE__) + "/images/#{params[:image]}.png" + content_type :png + send_file filename + end + + error NotFound do + content_type 'text/html' + + (<<-HTML).gsub(/^ {8}/, '') + <!DOCTYPE html> + <html> + <head> + <style type="text/css"> + body { text-align:center;font-family:helvetica,arial;font-size:22px; + color:#888;margin:20px} + #c {margin:0 auto;width:500px;text-align:left} + </style> + </head> + <body> + <h2>Sinatra doesn’t know this ditty.</h2> + <img src='#{uri "/__sinatra__/404.png"}'> + <div id="c"> + Try this: + <pre>#{request.request_method.downcase} '#{request.path_info}' do\n "Hello World"\nend</pre> + </div> + </body> + </html> + HTML + end + end + end + + # Execution context for classic style (top-level) applications. All + # DSL methods executed on main are delegated to this class. + # + # The Application class should not be subclassed, unless you want to + # inherit all settings, routes, handlers, and error pages from the + # top-level. Subclassing Sinatra::Base is highly recommended for + # modular applications. + class Application < Base + set :logging, Proc.new { ! test? } + set :method_override, true + set :run, Proc.new { ! test? } + set :session_secret, Proc.new { super() unless development? } + set :app_file, nil + + def self.register(*extensions, &block) #:nodoc: + added_methods = extensions.map {|m| m.public_instance_methods }.flatten + Delegator.delegate(*added_methods) + super(*extensions, &block) + end + end + + # Sinatra delegation mixin. Mixing this module into an object causes all + # methods to be delegated to the Sinatra::Application class. Used primarily + # at the top-level. + module Delegator #:nodoc: + def self.delegate(*methods) + methods.each do |method_name| + define_method(method_name) do |*args, &block| + return super(*args, &block) if respond_to? method_name + Delegator.target.send(method_name, *args, &block) + end + private method_name + end + end + + delegate :get, :patch, :put, :post, :delete, :head, :options, :template, :layout, + :before, :after, :error, :not_found, :configure, :set, :mime_type, + :enable, :disable, :use, :development?, :test?, :production?, + :helpers, :settings + + class << self + attr_accessor :target + end + + self.target = Application + end + + class Wrapper + def initialize(stack, instance) + @stack, @instance = stack, instance + end + + def settings + @instance.settings + end + + def helpers + @instance + end + + def call(env) + @stack.call(env) + end + + def inspect + "#<#{@instance.class} app_file=#{settings.app_file.inspect}>" + end + end + + # Create a new Sinatra application. The block is evaluated in the new app's + # class scope. + def self.new(base=Base, options={}, &block) + base = Class.new(base) + base.class_eval(&block) if block_given? + base + end + + # Extend the top-level DSL with the modules provided. + def self.register(*extensions, &block) + Delegator.target.register(*extensions, &block) + end + + # Include the helper modules provided in Sinatra's request context. + def self.helpers(*extensions, &block) + Delegator.target.helpers(*extensions, &block) + end + + # Use the middleware for classic applications. + def self.use(*args, &block) + Delegator.target.use(*args, &block) + end +end diff --git a/samples/Ruby/wrong_shebang.rb b/samples/Ruby/wrong_shebang.rb new file mode 100644 index 0000000..22b4804 --- /dev/null +++ b/samples/Ruby/wrong_shebang.rb @@ -0,0 +1,2 @@ +#!/usr/bin/env python +puts "Not Python" |
