diff options
author | Matth?us G. Chajdas <dev@anteru.net> | 2019-11-10 13:56:53 +0100 |
---|---|---|
committer | Matth?us G. Chajdas <dev@anteru.net> | 2019-11-10 13:56:53 +0100 |
commit | 1dd3124a9770e11b6684e5dd1e6bc15a0aa3bc67 (patch) | |
tree | 87a171383266dd1f64196589af081bc2f8e497c3 /tests/examplefiles/example.rb | |
parent | f1c080e184dc1bbc36eaa7cd729ff3a499de568a (diff) | |
download | pygments-master.tar.gz |
Diffstat (limited to 'tests/examplefiles/example.rb')
-rw-r--r-- | tests/examplefiles/example.rb | 1852 |
1 files changed, 0 insertions, 1852 deletions
diff --git a/tests/examplefiles/example.rb b/tests/examplefiles/example.rb deleted file mode 100644 index 93f8dc2b..00000000 --- a/tests/examplefiles/example.rb +++ /dev/null @@ -1,1852 +0,0 @@ -module CodeRay - module Scanners - -class Ruby < Scanner - - RESERVED_WORDS = [ - 'and', 'def', 'end', 'in', 'or', 'unless', 'begin', - 'defined?', 'ensure', 'module', 'redo', 'super', 'until', - 'BEGIN', 'break', 'do', 'next', 'rescue', 'then', - 'when', 'END', 'case', 'else', 'for', 'retry', - 'while', 'alias', 'class', 'elsif', 'if', 'not', 'return', - 'undef', 'yield', - ] - - DEF_KEYWORDS = ['def'] - MODULE_KEYWORDS = ['class', 'module'] - DEF_NEW_STATE = WordList.new(:initial). - add(DEF_KEYWORDS, :def_expected). - add(MODULE_KEYWORDS, :module_expected) - - WORDS_ALLOWING_REGEXP = [ - 'and', 'or', 'not', 'while', 'until', 'unless', 'if', 'elsif', 'when' - ] - REGEXP_ALLOWED = WordList.new(false). - add(WORDS_ALLOWING_REGEXP, :set) - - PREDEFINED_CONSTANTS = [ - 'nil', 'true', 'false', 'self', - 'DATA', 'ARGV', 'ARGF', '__FILE__', '__LINE__', - ] - - IDENT_KIND = WordList.new(:ident). - add(RESERVED_WORDS, :reserved). - add(PREDEFINED_CONSTANTS, :pre_constant) - - METHOD_NAME = / #{IDENT} [?!]? /xo - METHOD_NAME_EX = / - #{METHOD_NAME} # common methods: split, foo=, empty?, gsub! - | \*\*? # multiplication and power - | [-+~]@? # plus, minus - | [\/%&|^`] # division, modulo or format strings, &and, |or, ^xor, `system` - | \[\]=? # array getter and setter - | <=?>? | >=? # comparison, rocket operator - | << | >> # append or shift left, shift right - | ===? # simple equality and case equality - /ox - GLOBAL_VARIABLE = / \$ (?: #{IDENT} | \d+ | [~&+`'=\/,;_.<>!@0$?*":F\\] | -[a-zA-Z_0-9] ) /ox - - DOUBLEQ = / " [^"\#\\]* (?: (?: \#\{.*?\} | \#(?:$")? | \\. ) [^"\#\\]* )* "? /ox - SINGLEQ = / ' [^'\\]* (?: \\. [^'\\]* )* '? /ox - STRING = / #{SINGLEQ} | #{DOUBLEQ} /ox - SHELL = / ` [^`\#\\]* (?: (?: \#\{.*?\} | \#(?:$`)? | \\. ) [^`\#\\]* )* `? /ox - REGEXP = / \/ [^\/\#\\]* (?: (?: \#\{.*?\} | \#(?:$\/)? | \\. ) [^\/\#\\]* )* \/? /ox - - DECIMAL = /\d+(?:_\d+)*/ # doesn't recognize 09 as octal error - OCTAL = /0_?[0-7]+(?:_[0-7]+)*/ - HEXADECIMAL = /0x[0-9A-Fa-f]+(?:_[0-9A-Fa-f]+)*/ - BINARY = /0b[01]+(?:_[01]+)*/ - - EXPONENT = / [eE] [+-]? #{DECIMAL} /ox - FLOAT = / #{DECIMAL} (?: #{EXPONENT} | \. #{DECIMAL} #{EXPONENT}? ) / - INTEGER = /#{OCTAL}|#{HEXADECIMAL}|#{BINARY}|#{DECIMAL}/ - - def reset - super - @regexp_allowed = false - end - - def next_token - return if @scanner.eos? - - kind = :error - if @scanner.scan(/\s+/) # in every state - kind = :space - @regexp_allowed = :set if @regexp_allowed or @scanner.matched.index(?\n) # delayed flag setting - - elsif @state == :def_expected - if @scanner.scan(/ (?: (?:#{IDENT}(?:\.|::))* | (?:@@?|$)? #{IDENT}(?:\.|::) ) #{METHOD_NAME_EX} /ox) - kind = :method - @state = :initial - else - @scanner.getch - end - @state = :initial - - elsif @state == :module_expected - if @scanner.scan(/<</) - kind = :operator - else - if @scanner.scan(/ (?: #{IDENT} (?:\.|::))* #{IDENT} /ox) - kind = :method - else - @scanner.getch - end - @state = :initial - end - - elsif # state == :initial - # IDENTIFIERS, KEYWORDS - if @scanner.scan(GLOBAL_VARIABLE) - kind = :global_variable - elsif @scanner.scan(/ @@ #{IDENT} /ox) - kind = :class_variable - elsif @scanner.scan(/ @ #{IDENT} /ox) - kind = :instance_variable - elsif @scanner.scan(/ __END__\n ( (?!\#CODE\#) .* )? | \#[^\n]* | =begin(?=\s).*? \n=end(?=\s|\z)(?:[^\n]*)? /mx) - kind = :comment - elsif @scanner.scan(METHOD_NAME) - if @last_token_dot - kind = :ident - else - matched = @scanner.matched - kind = IDENT_KIND[matched] - if kind == :ident and matched =~ /^[A-Z]/ - kind = :constant - elsif kind == :reserved - @state = DEF_NEW_STATE[matched] - @regexp_allowed = REGEXP_ALLOWED[matched] - end - end - - elsif @scanner.scan(STRING) - kind = :string - elsif @scanner.scan(SHELL) - kind = :shell - elsif @scanner.scan(/<< - (?: - ([a-zA-Z_0-9]+) - (?: .*? ^\1$ | .* ) - | - -([a-zA-Z_0-9]+) - (?: .*? ^\s*\2$ | .* ) - | - (["\'`]) (.+?) \3 - (?: .*? ^\4$ | .* ) - | - - (["\'`]) (.+?) \5 - (?: .*? ^\s*\6$ | .* ) - ) - /mxo) - kind = :string - elsif @scanner.scan(/\//) and @regexp_allowed - @scanner.unscan - @scanner.scan(REGEXP) - kind = :regexp -/%(?:[Qqxrw](?:\([^)#\\\\]*(?:(?:#\{.*?\}|#|\\\\.)[^)#\\\\]*)*\)?|\[[^\]#\\\\]*(?:(?:#\{.*?\}|#|\\\\.)[^\]#\\\\]*)*\]?|\{[^}#\\\\]*(?:(?:#\{.*?\}|#|\\\\.)[^}#\\\\]*)*\}?|<[^>#\\\\]*(?:(?:#\{.*?\}|#|\\\\.)[^>#\\\\]*)*>?|([^a-zA-Z\\\\])(?:(?!\1)[^#\\\\])*(?:(?:#\{.*?\}|#|\\\\.)(?:(?!\1)[^#\\\\])*)*\1?)|\([^)#\\\\]*(?:(?:#\{.*?\}|#|\\\\.)[^)#\\\\]*)*\)?|\[[^\]#\\\\]*(?:(?:#\{.*?\}|#|\\\\.)[^\]#\\\\]*)*\]?|\{[^}#\\\\]*(?:(?:#\{.*?\}|#|\\\\.)[^}#\\\\]*)*\}?|<[^>#\\\\]*(?:(?:#\{.*?\}|#|\\\\.)[^>#\\\\]*)*>?|([^a-zA-Z\s\\\\])(?:(?!\2)[^#\\\\])*(?:(?:#\{.*?\}|#|\\\\.)(?:(?!\2)[^#\\\\])*)*\2?|\\\\[^#\\\\]*(?:(?:#\{.*?\}|#)[^#\\\\]*)*\\\\?)/ - elsif @scanner.scan(/:(?:#{GLOBAL_VARIABLE}|#{METHOD_NAME_EX}|#{STRING})/ox) - kind = :symbol - elsif @scanner.scan(/ - \? (?: - [^\s\\] - | - \\ (?:M-\\C-|C-\\M-|M-\\c|c\\M-|c|C-|M-))? (?: \\ (?: . | [0-7]{3} | x[0-9A-Fa-f][0-9A-Fa-f] ) - ) - /mox) - kind = :integer - - elsif @scanner.scan(/ [-+*\/%=<>;,|&!()\[\]{}~?] | \.\.?\.? | ::? /x) - kind = :operator - @regexp_allowed = :set if @scanner.matched[-1,1] =~ /[~=!<>|&^,\(\[+\-\/\*%]\z/ - elsif @scanner.scan(FLOAT) - kind = :float - elsif @scanner.scan(INTEGER) - kind = :integer - else - @scanner.getch - end - end - - token = Token.new @scanner.matched, kind - - if kind == :regexp - token.text << @scanner.scan(/[eimnosux]*/) - end - - @regexp_allowed = (@regexp_allowed == :set) # delayed flag setting - - token - end -end - -register Ruby, 'ruby', 'rb' - - end -end -class Set - include Enumerable - - # Creates a new set containing the given objects. - def self.[](*ary) - new(ary) - end - - # Creates a new set containing the elements of the given enumerable - # object. - # - # If a block is given, the elements of enum are preprocessed by the - # given block. - def initialize(enum = nil, &block) # :yields: o - @hash ||= Hash.new - - enum.nil? and return - - if block - enum.each { |o| add(block[o]) } - else - merge(enum) - end - end - - # Copy internal hash. - def initialize_copy(orig) - @hash = orig.instance_eval{@hash}.dup - end - - # Returns the number of elements. - def size - @hash.size - end - alias length size - - # Returns true if the set contains no elements. - def empty? - @hash.empty? - end - - # Removes all elements and returns self. - def clear - @hash.clear - self - end - - # Replaces the contents of the set with the contents of the given - # enumerable object and returns self. - def replace(enum) - if enum.class == self.class - @hash.replace(enum.instance_eval { @hash }) - else - enum.is_a?(Enumerable) or raise ArgumentError, "value must be enumerable" - clear - enum.each { |o| add(o) } - end - - self - end - - # Converts the set to an array. The order of elements is uncertain. - def to_a - @hash.keys - end - - def flatten_merge(set, seen = Set.new) - set.each { |e| - if e.is_a?(Set) - if seen.include?(e_id = e.object_id) - raise ArgumentError, "tried to flatten recursive Set" - end - - seen.add(e_id) - flatten_merge(e, seen) - seen.delete(e_id) - else - add(e) - end - } - - self - end - protected :flatten_merge - - # Returns a new set that is a copy of the set, flattening each - # containing set recursively. - def flatten - self.class.new.flatten_merge(self) - end - - # Equivalent to Set#flatten, but replaces the receiver with the - # result in place. Returns nil if no modifications were made. - def flatten! - if detect { |e| e.is_a?(Set) } - replace(flatten()) - else - nil - end - end - - # Returns true if the set contains the given object. - def include?(o) - @hash.include?(o) - end - alias member? include? - - # Returns true if the set is a superset of the given set. - def superset?(set) - set.is_a?(Set) or raise ArgumentError, "value must be a set" - return false if size < set.size - set.all? { |o| include?(o) } - end - - # Returns true if the set is a proper superset of the given set. - def proper_superset?(set) - set.is_a?(Set) or raise ArgumentError, "value must be a set" - return false if size <= set.size - set.all? { |o| include?(o) } - end - - # Returns true if the set is a subset of the given set. - def subset?(set) - set.is_a?(Set) or raise ArgumentError, "value must be a set" - return false if set.size < size - all? { |o| set.include?(o) } - end - - # Returns true if the set is a proper subset of the given set. - def proper_subset?(set) - set.is_a?(Set) or raise ArgumentError, "value must be a set" - return false if set.size <= size - all? { |o| set.include?(o) } - end - - # Calls the given block once for each element in the set, passing - # the element as parameter. - def each - @hash.each_key { |o| yield(o) } - self - end - - # Adds the given object to the set and returns self. Use +merge+ to - # add several elements at once. - def add(o) - @hash[o] = true - self - end - alias << add - - # Adds the given object to the set and returns self. If the - # object is already in the set, returns nil. - def add?(o) - if include?(o) - nil - else - add(o) - end - end - - # Deletes the given object from the set and returns self. Use +subtract+ to - # delete several items at once. - def delete(o) - @hash.delete(o) - self - end - - # Deletes the given object from the set and returns self. If the - # object is not in the set, returns nil. - def delete?(o) - if include?(o) - delete(o) - else - nil - end - end - - # Deletes every element of the set for which block evaluates to - # true, and returns self. - def delete_if - @hash.delete_if { |o,| yield(o) } - self - end - - # Do collect() destructively. - def collect! - set = self.class.new - each { |o| set << yield(o) } - replace(set) - end - alias map! collect! - - # Equivalent to Set#delete_if, but returns nil if no changes were - # made. - def reject! - n = size - delete_if { |o| yield(o) } - size == n ? nil : self - end - - # Merges the elements of the given enumerable object to the set and - # returns self. - def merge(enum) - if enum.is_a?(Set) - @hash.update(enum.instance_eval { @hash }) - else - enum.is_a?(Enumerable) or raise ArgumentError, "value must be enumerable" - enum.each { |o| add(o) } - end - - self - end - - # Deletes every element that appears in the given enumerable object - # and returns self. - def subtract(enum) - enum.is_a?(Enumerable) or raise ArgumentError, "value must be enumerable" - enum.each { |o| delete(o) } - self - end - - # Returns a new set built by merging the set and the elements of the - # given enumerable object. - def |(enum) - enum.is_a?(Enumerable) or raise ArgumentError, "value must be enumerable" - dup.merge(enum) - end - alias + | ## - alias union | ## - - # Returns a new set built by duplicating the set, removing every - # element that appears in the given enumerable object. - def -(enum) - enum.is_a?(Enumerable) or raise ArgumentError, "value must be enumerable" - dup.subtract(enum) - end - alias difference - ## - - # Returns a new array containing elements common to the set and the - # given enumerable object. - def &(enum) - enum.is_a?(Enumerable) or raise ArgumentError, "value must be enumerable" - n = self.class.new - enum.each { |o| n.add(o) if include?(o) } - n - end - alias intersection & ## - - # Returns a new array containing elements exclusive between the set - # and the given enumerable object. (set ^ enum) is equivalent to - # ((set | enum) - (set & enum)). - def ^(enum) - enum.is_a?(Enumerable) or raise ArgumentError, "value must be enumerable" - n = dup - enum.each { |o| if n.include?(o) then n.delete(o) else n.add(o) end } - n - end - - # Returns true if two sets are equal. The equality of each couple - # of elements is defined according to Object#eql?. - def ==(set) - equal?(set) and return true - - set.is_a?(Set) && size == set.size or return false - - hash = @hash.dup - set.all? { |o| hash.include?(o) } - end - - def hash # :nodoc: - @hash.hash - end - - def eql?(o) # :nodoc: - return false unless o.is_a?(Set) - @hash.eql?(o.instance_eval{@hash}) - end - - # Classifies the set by the return value of the given block and - # returns a hash of {value => set of elements} pairs. The block is - # called once for each element of the set, passing the element as - # parameter. - # - # e.g.: - # - # require 'set' - # files = Set.new(Dir.glob("*.rb")) - # hash = files.classify { |f| File.mtime(f).year } - # p hash # => {2000=>#<Set: {"a.rb", "b.rb"}>, - # # 2001=>#<Set: {"c.rb", "d.rb", "e.rb"}>, - # # 2002=>#<Set: {"f.rb"}>} - def classify # :yields: o - h = {} - - each { |i| - x = yield(i) - (h[x] ||= self.class.new).add(i) - } - - h - end - - # Divides the set into a set of subsets according to the commonality - # defined by the given block. - # - # If the arity of the block is 2, elements o1 and o2 are in common - # if block.call(o1, o2) is true. Otherwise, elements o1 and o2 are - # in common if block.call(o1) == block.call(o2). - # - # e.g.: - # - # require 'set' - # numbers = Set[1, 3, 4, 6, 9, 10, 11] - # set = numbers.divide { |i,j| (i - j).abs == 1 } - # p set # => #<Set: {#<Set: {1}>, - # # #<Set: {11, 9, 10}>, - # # #<Set: {3, 4}>, - # # #<Set: {6}>}> - def divide(&func) - if func.arity == 2 - require 'tsort' - - class << dig = {} # :nodoc: - include TSort - - alias tsort_each_node each_key - def tsort_each_child(node, &block) - fetch(node).each(&block) - end - end - - each { |u| - dig[u] = a = [] - each{ |v| func.call(u, v) and a << v } - } - - set = Set.new() - dig.each_strongly_connected_component { |css| - set.add(self.class.new(css)) - } - set - else - Set.new(classify(&func).values) - end - end - - InspectKey = :__inspect_key__ # :nodoc: - - # Returns a string containing a human-readable representation of the - # set. ("#<Set: {element1, element2, ...}>") - def inspect - ids = (Thread.current[InspectKey] ||= []) - - if ids.include?(object_id) - return sprintf('#<%s: {...}>', self.class.name) - end - - begin - ids << object_id - return sprintf('#<%s: {%s}>', self.class, to_a.inspect[1..-2]) - ensure - ids.pop - end - end - - def pretty_print(pp) # :nodoc: - pp.text sprintf('#<%s: {', self.class.name) - pp.nest(1) { - pp.seplist(self) { |o| - pp.pp o - } - } - pp.text "}>" - end - - def pretty_print_cycle(pp) # :nodoc: - pp.text sprintf('#<%s: {%s}>', self.class.name, empty? ? '' : '...') - end -end - -# SortedSet implements a set which elements are sorted in order. See Set. -class SortedSet < Set - @@setup = false - - class << self - def [](*ary) # :nodoc: - new(ary) - end - - def setup # :nodoc: - @@setup and return - - begin - require 'rbtree' - - module_eval %{ - def initialize(*args, &block) - @hash = RBTree.new - super - end - } - rescue LoadError - module_eval %{ - def initialize(*args, &block) - @keys = nil - super - end - - def clear - @keys = nil - super - end - - def replace(enum) - @keys = nil - super - end - - def add(o) - @keys = nil - @hash[o] = true - self - end - alias << add - - def delete(o) - @keys = nil - @hash.delete(o) - self - end - - def delete_if - n = @hash.size - @hash.delete_if { |o,| yield(o) } - @keys = nil if @hash.size != n - self - end - - def merge(enum) - @keys = nil - super - end - - def each - to_a.each { |o| yield(o) } - end - - def to_a - (@keys = @hash.keys).sort! unless @keys - @keys - end - } - end - - @@setup = true - end - end - - def initialize(*args, &block) # :nodoc: - SortedSet.setup - initialize(*args, &block) - end -end - -module Enumerable - # Makes a set from the enumerable object with given arguments. - def to_set(klass = Set, *args, &block) - klass.new(self, *args, &block) - end -end - -# =begin -# == RestricedSet class -# RestricedSet implements a set with restrictions defined by a given -# block. -# -# === Super class -# Set -# -# === Class Methods -# --- RestricedSet::new(enum = nil) { |o| ... } -# --- RestricedSet::new(enum = nil) { |rset, o| ... } -# Creates a new restricted set containing the elements of the given -# enumerable object. Restrictions are defined by the given block. -# -# If the block's arity is 2, it is called with the RestrictedSet -# itself and an object to see if the object is allowed to be put in -# the set. -# -# Otherwise, the block is called with an object to see if the object -# is allowed to be put in the set. -# -# === Instance Methods -# --- restriction_proc -# Returns the restriction procedure of the set. -# -# =end -# -# class RestricedSet < Set -# def initialize(*args, &block) -# @proc = block or raise ArgumentError, "missing a block" -# -# if @proc.arity == 2 -# instance_eval %{ -# def add(o) -# @hash[o] = true if @proc.call(self, o) -# self -# end -# alias << add -# -# def add?(o) -# if include?(o) || !@proc.call(self, o) -# nil -# else -# @hash[o] = true -# self -# end -# end -# -# def replace(enum) -# enum.is_a?(Enumerable) or raise ArgumentError, "value must be enumerable" -# clear -# enum.each { |o| add(o) } -# -# self -# end -# -# def merge(enum) -# enum.is_a?(Enumerable) or raise ArgumentError, "value must be enumerable" -# enum.each { |o| add(o) } -# -# self -# end -# } -# else -# instance_eval %{ -# def add(o) -# if @proc.call(o) -# @hash[o] = true -# end -# self -# end -# alias << add -# -# def add?(o) -# if include?(o) || !@proc.call(o) -# nil -# else -# @hash[o] = true -# self -# end -# end -# } -# end -# -# super(*args) -# end -# -# def restriction_proc -# @proc -# end -# end - -if $0 == __FILE__ - eval DATA.read, nil, $0, __LINE__+4 -end - -# = rweb - CGI Support Library -# -# Author:: Johannes Barre (mailto:rweb@igels.net) -# Copyright:: Copyright (c) 2003, 04 by Johannes Barre -# License:: GNU Lesser General Public License (COPYING, http://www.gnu.org/copyleft/lesser.html) -# Version:: 0.1.0 -# CVS-ID:: $Id: example.rb 39 2005-11-05 03:33:55Z murphy $ -# -# == What is Rweb? -# Rweb is a replacement for the cgi class included in the ruby distribution. -# -# == How to use -# -# === Basics -# -# This class is made to be as easy as possible to use. An example: -# -# require "rweb" -# -# web = Rweb.new -# web.out do -# web.puts "Hello world!" -# end -# -# The visitor will get a simple "Hello World!" in his browser. Please notice, -# that won't set html-tags for you, so you should better do something like this: -# -# require "rweb" -# -# web = Rweb.new -# web.out do -# web.puts "<html><body>Hello world!</body></html>" -# end -# -# === Set headers -# Of course, it's also possible to tell the browser, that the content of this -# page is plain text instead of html code: -# -# require "rweb" -# -# web = Rweb.new -# web.out do -# web.header("content-type: text/plain") -# web.puts "Hello plain world!" -# end -# -# Please remember, headers can't be set after the page content has been send. -# You have to set all nessessary headers before the first puts oder print. It's -# possible to cache the content until everything is complete. Doing it this -# way, you can set headers everywhere. -# -# If you set a header twice, the second header will replace the first one. The -# header name is not casesensitive, it will allways converted in to the -# capitalised form suggested by the w3c (http://w3.org) -# -# === Set cookies -# Setting cookies is quite easy: -# include 'rweb' -# -# web = Rweb.new -# Cookie.new("Visits", web.cookies['visits'].to_i +1) -# web.out do -# web.puts "Welcome back! You visited this page #{web.cookies['visits'].to_i +1} times" -# end -# -# See the class Cookie for more details. -# -# === Get form and cookie values -# There are four ways to submit data from the browser to the server and your -# ruby script: via GET, POST, cookies and file upload. Rweb doesn't support -# file upload by now. -# -# include 'rweb' -# -# web = Rweb.new -# web.out do -# web.print "action: #{web.get['action']} " -# web.puts "The value of the cookie 'visits' is #{web.cookies['visits']}" -# web.puts "The post parameter 'test['x']' is #{web.post['test']['x']}" -# end - -RWEB_VERSION = "0.1.0" -RWEB = "rweb/#{RWEB_VERSION}" - -#require 'rwebcookie' -> edit by bunny :-) - -class Rweb - # All parameter submitted via the GET method are available in attribute - # get. This is Hash, where every parameter is available as a key-value - # pair. - # - # If your input tag has a name like this one, it's value will be available - # as web.get["fieldname"] - # <input name="fieldname"> - # You can submit values as a Hash - # <input name="text['index']"> - # <input name="text['index2']"> - # will be available as - # web.get["text"]["index"] - # web.get["text"]["index2"] - # Integers are also possible - # <input name="int[2]"> - # <input name="int[3]['hi']> - # will be available as - # web.get["int"][2] - # web.get["int"][3]["hi"] - # If you specify no index, the lowest unused index will be used: - # <input name="int[]"><!-- First Field --> - # <input name="int[]"><!-- Second one --> - # will be available as - # web.get["int"][0] # First Field - # web.get["int"][1] # Second one - # Please notice, this doesn'd work like you might expect: - # <input name="text[index]"> - # It will not be available as web.get["text"]["index"] but - # web.get["text[index]"] - attr_reader :get - - # All parameters submitted via POST are available in the attribute post. It - # works like the get attribute. - # <input name="text[0]"> - # will be available as - # web.post["text"][0] - attr_reader :post - - # All cookies submitted by the browser are available in cookies. This is a - # Hash, where every cookie is a key-value pair. - attr_reader :cookies - - # The name of the browser identification is submitted as USER_AGENT and - # available in this attribute. - attr_reader :user_agent - - # The IP address of the client. - attr_reader :remote_addr - - # Creates a new Rweb object. This should only done once. You can set various - # options via the settings hash. - # - # "cache" => true: Everything you script send to the client will be cached - # until the end of the out block or until flush is called. This way, you - # can modify headers and cookies even after printing something to the client. - # - # "safe" => level: Changes the $SAFE attribute. By default, $SAFE will be set - # to 1. If $SAFE is already higher than this value, it won't be changed. - # - # "silend" => true: Normaly, Rweb adds automaticly a header like this - # "X-Powered-By: Rweb/x.x.x (Ruby/y.y.y)". With the silend option you can - # suppress this. - def initialize (settings = {}) - # {{{ - @header = {} - @cookies = {} - @get = {} - @post = {} - - # Internal attributes - @status = nil - @reasonPhrase = nil - @setcookies = [] - @output_started = false; - @output_allowed = false; - - @mod_ruby = false - @env = ENV.to_hash - - if defined?(MOD_RUBY) - @output_method = "mod_ruby" - @mod_ruby = true - elsif @env['SERVER_SOFTWARE'] =~ /^Microsoft-IIS/i - @output_method = "nph" - else - @output_method = "ph" - end - - unless settings.is_a?(Hash) - raise TypeError, "settings must be a Hash" - end - @settings = settings - - unless @settings.has_key?("safe") - @settings["safe"] = 1 - end - - if $SAFE < @settings["safe"] - $SAFE = @settings["safe"] - end - - unless @settings.has_key?("cache") - @settings["cache"] = false - end - - # mod_ruby sets no QUERY_STRING variable, if no GET-Parameters are given - unless @env.has_key?("QUERY_STRING") - @env["QUERY_STRING"] = "" - end - - # Now we split the QUERY_STRING by the seperators & and ; or, if - # specified, settings['get seperator'] - unless @settings.has_key?("get seperator") - get_args = @env['QUERY_STRING'].split(/[&;]/) - else - get_args = @env['QUERY_STRING'].split(@settings['get seperator']) - end - - get_args.each do | arg | - arg_key, arg_val = arg.split(/=/, 2) - arg_key = Rweb::unescape(arg_key) - arg_val = Rweb::unescape(arg_val) - - # Parse names like name[0], name['text'] or name[] - pattern = /^(.+)\[("[^\]]*"|'[^\]]*'|[0-9]*)\]$/ - keys = [] - while match = pattern.match(arg_key) - arg_key = match[1] - keys = [match[2]] + keys - end - keys = [arg_key] + keys - - akt = @get - last = nil - lastkey = nil - keys.each do |key| - if key == "" - # No key specified (like in "test[]"), so we use the - # lowerst unused Integer as key - key = 0 - while akt.has_key?(key) - key += 1 - end - elsif /^[0-9]*$/ =~ key - # If the index is numerical convert it to an Integer - key = key.to_i - elsif key[0].chr == "'" || key[0].chr == '"' - key = key[1, key.length() -2] - end - if !akt.has_key?(key) || !akt[key].class == Hash - # create an empty Hash if there isn't already one - akt[key] = {} - end - last = akt - lastkey = key - akt = akt[key] - end - last[lastkey] = arg_val - end - - if @env['REQUEST_METHOD'] == "POST" - if @env.has_key?("CONTENT_TYPE") && @env['CONTENT_TYPE'] == "application/x-www-form-urlencoded" && @env.has_key?('CONTENT_LENGTH') - unless @settings.has_key?("post seperator") - post_args = $stdin.read(@env['CONTENT_LENGTH'].to_i).split(/[&;]/) - else - post_args = $stdin.read(@env['CONTENT_LENGTH'].to_i).split(@settings['post seperator']) - end - post_args.each do | arg | - arg_key, arg_val = arg.split(/=/, 2) - arg_key = Rweb::unescape(arg_key) - arg_val = Rweb::unescape(arg_val) - - # Parse names like name[0], name['text'] or name[] - pattern = /^(.+)\[("[^\]]*"|'[^\]]*'|[0-9]*)\]$/ - keys = [] - while match = pattern.match(arg_key) - arg_key = match[1] - keys = [match[2]] + keys - end - keys = [arg_key] + keys - - akt = @post - last = nil - lastkey = nil - keys.each do |key| - if key == "" - # No key specified (like in "test[]"), so we use - # the lowerst unused Integer as key - key = 0 - while akt.has_key?(key) - key += 1 - end - elsif /^[0-9]*$/ =~ key - # If the index is numerical convert it to an Integer - key = key.to_i - elsif key[0].chr == "'" || key[0].chr == '"' - key = key[1, key.length() -2] - end - if !akt.has_key?(key) || !akt[key].class == Hash - # create an empty Hash if there isn't already one - akt[key] = {} - end - last = akt - lastkey = key - akt = akt[key] - end - last[lastkey] = arg_val - end - else - # Maybe we should print a warning here? - $stderr.print("Unidentified form data recived and discarded.") - end - end - - if @env.has_key?("HTTP_COOKIE") - cookie = @env['HTTP_COOKIE'].split(/; ?/) - cookie.each do | c | - cookie_key, cookie_val = c.split(/=/, 2) - - @cookies [Rweb::unescape(cookie_key)] = Rweb::unescape(cookie_val) - end - end - - if defined?(@env['HTTP_USER_AGENT']) - @user_agent = @env['HTTP_USER_AGENT'] - else - @user_agent = nil; - end - - if defined?(@env['REMOTE_ADDR']) - @remote_addr = @env['REMOTE_ADDR'] - else - @remote_addr = nil - end - # }}} - end - - # Prints a String to the client. If caching is enabled, the String will - # buffered until the end of the out block ends. - def print(str = "") - # {{{ - unless @output_allowed - raise "You just can write to output inside of a Rweb::out-block" - end - - if @settings["cache"] - @buffer += [str.to_s] - else - unless @output_started - sendHeaders - end - $stdout.print(str) - end - nil - # }}} - end - - # Prints a String to the client and adds a line break at the end. Please - # remember, that a line break is not visible in HTML, use the <br> HTML-Tag - # for this. If caching is enabled, the String will buffered until the end - # of the out block ends. - def puts(str = "") - # {{{ - self.print(str + "\n") - # }}} - end - - # Alias to print. - def write(str = "") - # {{{ - self.print(str) - # }}} - end - - # If caching is enabled, all cached data are send to the cliend and the - # cache emptied. - def flush - # {{{ - unless @output_allowed - raise "You can't use flush outside of a Rweb::out-block" - end - buffer = @buffer.join - - unless @output_started - sendHeaders - end - $stdout.print(buffer) - - @buffer = [] - # }}} - end - - # Sends one or more header to the client. All headers are cached just - # before body data are send to the client. If the same header are set - # twice, only the last value is send. - # - # Example: - # web.header("Last-Modified: Mon, 16 Feb 2004 20:15:41 GMT") - # web.header("Location: http://www.ruby-lang.org") - # - # You can specify more than one header at the time by doing something like - # this: - # web.header("Content-Type: text/plain\nContent-Length: 383") - # or - # web.header(["Content-Type: text/plain", "Content-Length: 383"]) - def header(str) - # {{{ - if @output_started - raise "HTTP-Headers are already send. You can't change them after output has started!" - end - unless @output_allowed - raise "You just can set headers inside of a Rweb::out-block" - end - if str.is_a?Array - str.each do | value | - self.header(value) - end - - elsif str.split(/\n/).length > 1 - str.split(/\n/).each do | value | - self.header(value) - end - - elsif str.is_a? String - str.gsub!(/\r/, "") - - if (str =~ /^HTTP\/1\.[01] [0-9]{3} ?.*$/) == 0 - pattern = /^HTTP\/1.[01] ([0-9]{3}) ?(.*)$/ - - result = pattern.match(str) - self.setstatus(result[0], result[1]) - elsif (str =~ /^status: [0-9]{3} ?.*$/i) == 0 - pattern = /^status: ([0-9]{3}) ?(.*)$/i - - result = pattern.match(str) - self.setstatus(result[0], result[1]) - else - a = str.split(/: ?/, 2) - - @header[a[0].downcase] = a[1] - end - end - # }}} - end - - # Changes the status of this page. There are several codes like "200 OK", - # "302 Found", "404 Not Found" or "500 Internal Server Error". A list of - # all codes is available at - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10 - # - # You can just send the code number, the reason phrase will be added - # automaticly with the recommendations from the w3c if not specified. If - # you set the status twice or more, only the last status will be send. - # Examples: - # web.status("401 Unauthorized") - # web.status("410 Sad but true, this lonely page is gone :(") - # web.status(206) - # web.status("400") - # - # The default status is "200 OK". If a "Location" header is set, the - # default status is "302 Found". - def status(str) - # {{{ - if @output_started - raise "HTTP-Headers are already send. You can't change them after output has started!" - end - unless @output_allowed - raise "You just can set headers inside of a Rweb::out-block" - end - if str.is_a?Integer - @status = str - elsif str.is_a?String - p1 = /^([0-9]{3}) ?(.*)$/ - p2 = /^HTTP\/1\.[01] ([0-9]{3}) ?(.*)$/ - p3 = /^status: ([0-9]{3}) ?(.*)$/i - - if (a = p1.match(str)) == nil - if (a = p2.match(str)) == nil - if (a = p3.match(str)) == nil - raise ArgumentError, "Invalid argument", caller - end - end - end - @status = a[1].to_i - if a[2] != "" - @reasonPhrase = a[2] - else - @reasonPhrase = getReasonPhrase(@status) - end - else - raise ArgumentError, "Argument of setstatus must be integer or string", caller - end - # }}} - end - - # Handles the output of your content and rescues all exceptions. Send all - # data in the block to this method. For example: - # web.out do - # web.header("Content-Type: text/plain") - # web.puts("Hello, plain world!") - # end - def out - # {{{ - @output_allowed = true - @buffer = []; # We use an array as buffer, because it's more performant :) - - begin - yield - rescue Exception => exception - $stderr.puts "Ruby exception rescued (#{exception.class}): #{exception.message}" - $stderr.puts exception.backtrace.join("\n") - - unless @output_started - self.setstatus(500) - @header = {} - end - - unless (@settings.has_key?("hide errors") and @settings["hide errors"] == true) - unless @output_started - self.header("Content-Type: text/html") - self.puts "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Strict//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" - self.puts "<html>" - self.puts "<head>" - self.puts "<title>500 Internal Server Error</title>" - self.puts "</head>" - self.puts "<body>" - end - if @header.has_key?("content-type") and (@header["content-type"] =~ /^text\/html/i) == 0 - self.puts "<h1>Internal Server Error</h1>" - self.puts "<p>The server encountered an exception and was unable to complete your request.</p>" - self.puts "<p>The exception has provided the following information:</p>" - self.puts "<pre style=\"background: #FFCCCC; border: black solid 2px; margin-left: 2cm; margin-right: 2cm; padding: 2mm;\"><b>#{exception.class}</b>: #{exception.message} <b>on</b>" - self.puts - self.puts "#{exception.backtrace.join("\n")}</pre>" - self.puts "</body>" - self.puts "</html>" - else - self.puts "The server encountered an exception and was unable to complete your request" - self.puts "The exception has provided the following information:" - self.puts "#{exception.class}: #{exception.message}" - self.puts - self.puts exception.backtrace.join("\n") - end - end - end - - if @settings["cache"] - buffer = @buffer.join - - unless @output_started - unless @header.has_key?("content-length") - self.header("content-length: #{buffer.length}") - end - - sendHeaders - end - $stdout.print(buffer) - elsif !@output_started - sendHeaders - end - @output_allowed = false; - # }}} - end - - # Decodes URL encoded data, %20 for example stands for a space. - def Rweb.unescape(str) - # {{{ - if defined? str and str.is_a? String - str.gsub!(/\+/, " ") - str.gsub(/%.{2}/) do | s | - s[1,2].hex.chr - end - end - # }}} - end - - protected - def sendHeaders - # {{{ - - Cookie.disallow # no more cookies can be set or modified - if !(@settings.has_key?("silent") and @settings["silent"] == true) and !@header.has_key?("x-powered-by") - if @mod_ruby - header("x-powered-by: #{RWEB} (Ruby/#{RUBY_VERSION}, #{MOD_RUBY})"); - else - header("x-powered-by: #{RWEB} (Ruby/#{RUBY_VERSION})"); - end - end - - if @output_method == "ph" - if ((@status == nil or @status == 200) and !@header.has_key?("content-type") and !@header.has_key?("location")) - header("content-type: text/html") - end - - if @status != nil - $stdout.print "Status: #{@status} #{@reasonPhrase}\r\n" - end - - @header.each do |key, value| - key = key *1 # "unfreeze" key :) - key[0] = key[0,1].upcase![0] - - key = key.gsub(/-[a-z]/) do |char| - "-" + char[1,1].upcase - end - - $stdout.print "#{key}: #{value}\r\n" - end - cookies = Cookie.getHttpHeader # Get all cookies as an HTTP Header - if cookies - $stdout.print cookies - end - - $stdout.print "\r\n" - - elsif @output_method == "nph" - elsif @output_method == "mod_ruby" - r = Apache.request - - if ((@status == nil or @status == 200) and !@header.has_key?("content-type") and !@header.has_key?("location")) - header("text/html") - end - - if @status != nil - r.status_line = "#{@status} #{@reasonPhrase}" - end - - r.send_http_header - @header.each do |key, value| - key = key *1 # "unfreeze" key :) - - key[0] = key[0,1].upcase![0] - key = key.gsub(/-[a-z]/) do |char| - "-" + char[1,1].upcase - end - puts "#{key}: #{value.class}" - #r.headers_out[key] = value - end - end - @output_started = true - # }}} - end - - def getReasonPhrase (status) - # {{{ - if status == 100 - "Continue" - elsif status == 101 - "Switching Protocols" - elsif status == 200 - "OK" - elsif status == 201 - "Created" - elsif status == 202 - "Accepted" - elsif status == 203 - "Non-Authoritative Information" - elsif status == 204 - "No Content" - elsif status == 205 - "Reset Content" - elsif status == 206 - "Partial Content" - elsif status == 300 - "Multiple Choices" - elsif status == 301 - "Moved Permanently" - elsif status == 302 - "Found" - elsif status == 303 - "See Other" - elsif status == 304 - "Not Modified" - elsif status == 305 - "Use Proxy" - elsif status == 307 - "Temporary Redirect" - elsif status == 400 - "Bad Request" - elsif status == 401 - "Unauthorized" - elsif status == 402 - "Payment Required" - elsif status == 403 - "Forbidden" - elsif status == 404 - "Not Found" - elsif status == 405 - "Method Not Allowed" - elsif status == 406 - "Not Acceptable" - elsif status == 407 - "Proxy Authentication Required" - elsif status == 408 - "Request Time-out" - elsif status == 409 - "Conflict" - elsif status == 410 - "Gone" - elsif status == 411 - "Length Required" - elsif status == 412 - "Precondition Failed" - elsif status == 413 - "Request Entity Too Large" - elsif status == 414 - "Request-URI Too Large" - elsif status == 415 - "Unsupported Media Type" - elsif status == 416 - "Requested range not satisfiable" - elsif status == 417 - "Expectation Failed" - elsif status == 500 - "Internal Server Error" - elsif status == 501 - "Not Implemented" - elsif status == 502 - "Bad Gateway" - elsif status == 503 - "Service Unavailable" - elsif status == 504 - "Gateway Time-out" - elsif status == 505 - "HTTP Version not supported" - else - raise "Unknown Statuscode. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1 for more information." - end - # }}} - end -end - -class Cookie - attr_reader :name, :value, :maxage, :path, :domain, :secure, :comment - - # Sets a cookie. Please see below for details of the attributes. - def initialize (name, value = nil, maxage = nil, path = nil, domain = nil, secure = false) - # {{{ - # HTTP headers (Cookies are a HTTP header) can only set, while no content - # is send. So an exception will be raised, when @@allowed is set to false - # and a new cookie has set. - unless defined?(@@allowed) - @@allowed = true - end - unless @@allowed - raise "You can't set cookies after the HTTP headers are send." - end - - unless defined?(@@list) - @@list = [] - end - @@list += [self] - - unless defined?(@@type) - @@type = "netscape" - end - - unless name.class == String - raise TypeError, "The name of a cookie must be a string", caller - end - if value.class.superclass == Integer || value.class == Float - value = value.to_s - elsif value.class != String && value != nil - raise TypeError, "The value of a cookie must be a string, integer, float or nil", caller - end - if maxage.class == Time - maxage = maxage - Time.now - elsif !maxage.class.superclass == Integer || !maxage == nil - raise TypeError, "The maxage date of a cookie must be an Integer or Time object or nil.", caller - end - unless path.class == String || path == nil - raise TypeError, "The path of a cookie must be nil or a string", caller - end - unless domain.class == String || domain == nil - raise TypeError, "The value of a cookie must be nil or a string", caller - end - unless secure == true || secure == false - raise TypeError, "The secure field of a cookie must be true or false", caller - end - - @name, @value, @maxage, @path, @domain, @secure = name, value, maxage, path, domain, secure - @comment = nil - # }}} - end - - # Modifies the value of this cookie. The information you want to store. If the - # value is nil, the cookie will be deleted by the client. - # - # This attribute can be a String, Integer or Float object or nil. - def value=(value) - # {{{ - if value.class.superclass == Integer || value.class == Float - value = value.to_s - elsif value.class != String && value != nil - raise TypeError, "The value of a cookie must be a string, integer, float or nil", caller - end - @value = value - # }}} - end - - # Modifies the maxage of this cookie. This attribute defines the lifetime of - # the cookie, in seconds. A value of 0 means the cookie should be discarded - # imediatly. If it set to nil, the cookie will be deleted when the browser - # will be closed. - # - # Attention: This is different from other implementations like PHP, where you - # gives the seconds since 1/1/1970 0:00:00 GMT. - # - # This attribute must be an Integer or Time object or nil. - def maxage=(maxage) - # {{{ - if maxage.class == Time - maxage = maxage - Time.now - elsif maxage.class.superclass == Integer || !maxage == nil - raise TypeError, "The maxage of a cookie must be an Interger or Time object or nil.", caller - end - @maxage = maxage - # }}} - end - - # Modifies the path value of this cookie. The client will send this cookie - # only, if the requested document is this directory or a subdirectory of it. - # - # The value of the attribute must be a String object or nil. - def path=(path) - # {{{ - unless path.class == String || path == nil - raise TypeError, "The path of a cookie must be nil or a string", caller - end - @path = path - # }}} - end - - # Modifies the domain value of this cookie. The client will send this cookie - # only if it's connected with this domain (or a subdomain, if the first - # character is a dot like in ".ruby-lang.org") - # - # The value of this attribute must be a String or nil. - def domain=(domain) - # {{{ - unless domain.class == String || domain == nil - raise TypeError, "The domain of a cookie must be a String or nil.", caller - end - @domain = domain - # }}} - end - - # Modifies the secure flag of this cookie. If it's true, the client will only - # send this cookie if it is secured connected with us. - # - # The value od this attribute has to be true or false. - def secure=(secure) - # {{{ - unless secure == true || secure == false - raise TypeError, "The secure field of a cookie must be true or false", caller - end - @secure = secure - # }}} - end - - # Modifies the comment value of this cookie. The comment won't be send, if - # type is "netscape". - def comment=(comment) - # {{{ - unless comment.class == String || comment == nil - raise TypeError, "The comment of a cookie must be a string or nil", caller - end - @comment = comment - # }}} - end - - # Changes the type of all cookies. - # Allowed values are RFC2109 and netscape (default). - def Cookie.type=(type) - # {{{ - unless @@allowed - raise "The cookies are allready send, so you can't change the type anymore." - end - unless type.downcase == "rfc2109" && type.downcase == "netscape" - raise "The type of the cookies must be \"RFC2109\" or \"netscape\"." - end - @@type = type; - # }}} - end - - # After sending this message, no cookies can be set or modified. Use it, when - # HTTP-Headers are send. Rweb does this for you. - def Cookie.disallow - # {{{ - @@allowed = false - true - # }}} - end - - # Returns a HTTP header (type String) with all cookies. Rweb does this for - # you. - def Cookie.getHttpHeader - # {{{ - if defined?(@@list) - if @@type == "netscape" - str = "" - @@list.each do |cookie| - if cookie.value == nil - cookie.maxage = 0 - cookie.value = "" - end - # TODO: Name and value should be escaped! - str += "Set-Cookie: #{cookie.name}=#{cookie.value}" - unless cookie.maxage == nil - expire = Time.now + cookie.maxage - expire.gmtime - str += "; Expire=#{expire.strftime("%a, %d-%b-%Y %H:%M:%S %Z")}" - end - unless cookie.domain == nil - str += "; Domain=#{cookie.domain}" - end - unless cookie.path == nil - str += "; Path=#{cookie.path}" - end - if cookie.secure - str += "; Secure" - end - str += "\r\n" - end - return str - else # type == "RFC2109" - str = "Set-Cookie: " - comma = false; - - @@list.each do |cookie| - if cookie.value == nil - cookie.maxage = 0 - cookie.value = "" - end - if comma - str += "," - end - comma = true - - str += "#{cookie.name}=\"#{cookie.value}\"" - unless cookie.maxage == nil - str += "; Max-Age=\"#{cookie.maxage}\"" - end - unless cookie.domain == nil - str += "; Domain=\"#{cookie.domain}\"" - end - unless cookie.path == nil - str += "; Path=\"#{cookie.path}\"" - end - if cookie.secure - str += "; Secure" - end - unless cookie.comment == nil - str += "; Comment=\"#{cookie.comment}\"" - end - str += "; Version=\"1\"" - end - str - end - else - false - end - # }}} - end -end - -require 'strscan' - -module BBCode - DEBUG = true - - use 'encoder', 'tags', 'tagstack', 'smileys' - -=begin - The Parser class takes care of the encoding. - It scans the given BBCode (as plain text), finds tags - and smilies and also makes links of urls in text. - - Normal text is send directly to the encoder. - - If a tag was found, an instance of a Tag subclass is created - to handle the case. - - The @tagstack manages tag nesting and ensures valid HTML. -=end - - class Parser - class Attribute - # flatten and use only one empty_arg - def self.create attr - attr = flatten attr - return @@empty_attr if attr.empty? - new attr - end - - private_class_method :new - - # remove leading and trailing whitespace; concat lines - def self.flatten attr - attr.strip.gsub(/\n/, ' ') - # -> ^ and $ can only match at begin and end now - end - - ATTRIBUTE_SCAN = / - (?!$) # don't match at end - \s* - ( # $1 = key - [^=\s\]"\\]* - (?: - (?: \\. | "[^"\\]*(?:\\.[^"\\]*)*"? ) - [^=\s\]"\\]* - )* - ) - (?: - = - ( # $2 = value - [^\s\]"\\]* - (?: - (?: \\. | "[^"\\]*(?:\\.[^"\\]*)*"? ) - [^\s\]"\\]* - )* - )? - )? - \s* - /x - - def self.parse source - source = source.dup - # empty_tag: the tag looks like [... /] - # slice!: this deletes the \s*/] at the end - # \s+ because [url=http://rubybb.org/forum/] is NOT an empty tag. - # In RubyBBCode, you can use [url=http://rubybb.org/forum/ /], and this has to be - # interpreted correctly. - empty_tag = source.sub!(/^:/, '=') or source.slice!(/\/$/) - debug 'PARSE: ' + source.inspect + ' => ' + empty_tag.inspect - #-> we have now an attr that's EITHER empty OR begins and ends with non-whitespace. - - attr = Hash.new - attr[:flags] = [] - source.scan(ATTRIBUTE_SCAN) { |key, value| - if not value - attr[:flags] << unescape(key) - else - next if value.empty? and key.empty? - attr[unescape(key)] = unescape(value) - end - } - debug attr.inspect - - return empty_tag, attr - end - - def self.unescape_char esc - esc[1] - end - - def self.unquote qt - qt[1..-1].chomp('"').gsub(/\\./) { |esc| unescape_char esc } - end - - def self.unescape str - str.gsub(/ (\\.) | (" [^"\\]* (?:\\.[^"\\]*)* "?) /x) { - if $1 - unescape_char $1 - else - unquote $2 - end - } - end - - include Enumerable - def each &block - @args.each(&block) - end - - attr_reader :source, :args, :value - - def initialize source - @source = source - debug 'Attribute#new(%p)' % source - @empty_tag, @attr = Attribute.parse source - @value = @attr[''].to_s - end - - def empty? - self == @@empty_attr - end - - def empty_tag? - @empty_tag - end - - def [] *keys - res = @attr[*keys] - end - - def flags - attr[:flags] - end - - def to_s - @attr - end - - def inspect - 'ATTR[' + @attr.inspect + (@empty_tag ? ' | empty tag' : '') + ']' - end - end - class Attribute - @@empty_attr = new '' - end - end - |