Changeset 1196

Show
Ignore:
Timestamp:
01/08/08 02:35:45 (11 months ago)
Author:
sethrasmuss..@gmail.com
Message:

split up core_ext specs into multiple files, clean up Hash ext and fix some docs

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/lib/merb/core_ext/hash.rb

    r1184 r1196  
    1 #require 'hpricot' 
     1# require 'hpricot' 
    22 
    33class Hash 
    4            
    54  class << self 
    6       # Converts valid XML into a Ruby Hash structure. 
    7       # <tt>xml</tt>:: A string representation of valid XML 
    8       #  
    9       # == Typecasting 
    10       # Typecasting is performed on elements that have a "<tt>type</tt>" attribute of 
    11       # <tt>integer</tt>::  
    12       # <tt>boolean</tt>:: anything other than "true" evaluates to false 
    13       # <tt>datetime</tt>:: Returns a Time object.  See +Time+ documentation for valid Time strings 
    14       # <tt>date</tt>:: Returns a Date object.  See +Date+ documentation for valid Date strings  
    15       #  
    16       # Keys are automatically converted to +snake_case+ 
    17       # 
    18       # == Caveats 
    19       # * Mixed content tags are assumed to be text and any xml tags are kept as a String 
    20       # * Any attributes other than type on a node containing a text node will be discarded 
    21       # 
    22       # == Examples 
    23       # 
    24       # === Standard  
    25       # <user gender='m'> 
    26       #   <age type='integer'>35</age> 
    27       #   <name>Home Simpson</name> 
    28       #   <dob type='date'>1988-01-01</dob> 
    29       #   <joined-at type='datetime'>2000-04-28 23:01</joined-at> 
    30       #   <is-cool type='boolean'>true</is-cool> 
    31       # </user> 
    32       # 
    33       # evaluates to  
    34       #  
    35       # { "user" =>  
    36       #         { "gender"    => "m", 
    37       #           "age"       => 35, 
    38       #           "name"      => "Home Simpson", 
    39       #           "dob"       => DateObject( 1998-01-01 ), 
    40       #           "joined_at" => TimeObject( 2000-04-28 23:01), 
    41       #           "is_cool"   => true  
    42       #         } 
    43       #     } 
    44       # 
    45       # === Mixed Content 
    46       # <story> 
    47       #   A Quick <em>brown</em> Fox 
    48       # </story> 
    49       # 
    50       # evaluates to 
    51       # { "story" => "A Quick <em>brown</em> Fox" } 
    52       #  
    53       # === Attributes other than type on a node containing text 
    54       # <story is-good='fasle'> 
    55       #   A Quick <em>brown</em> Fox 
    56       # </story> 
    57       # 
    58       # evaluates to 
    59       # { "story" => "A Quick <em>brown</em> Fox" } 
    60       # 
    61       # <bicep unit='inches' type='integer'>60</bicep> 
    62       # 
    63       # evaluates with a typecast to an integer.  But ignores the unit attribute 
    64       # { "bicep" => 60 } 
    65        
    66       def from_xml( xml ) 
    67         ToHashParser.from_xml(xml)   
    68       end 
     5    # Converts valid XML into a Ruby Hash structure. 
     6    # <tt>xml</tt>:: A string representation of valid XML 
     7    #  
     8    # == Typecasting 
     9    # Typecasting is performed on elements that have a "<tt>type</tt>" attribute of 
     10    # <tt>integer</tt>::  
     11    # <tt>boolean</tt>:: anything other than "true" evaluates to false 
     12    # <tt>datetime</tt>:: Returns a Time object.  See +Time+ documentation for valid Time strings 
     13    # <tt>date</tt>:: Returns a Date object.  See +Date+ documentation for valid Date strings  
     14    #  
     15    # Keys are automatically converted to +snake_case+ 
     16    # 
     17    # == Caveats 
     18    # * Mixed content tags are assumed to be text and any xml tags are kept as a String 
     19    # * Any attributes other than type on a node containing a text node will be discarded 
     20    # 
     21    # == Examples 
     22    # 
     23    # === Standard  
     24    # <user gender='m'> 
     25    #   <age type='integer'>35</age> 
     26    #   <name>Home Simpson</name> 
     27    #   <dob type='date'>1988-01-01</dob> 
     28    #   <joined-at type='datetime'>2000-04-28 23:01</joined-at> 
     29    #   <is-cool type='boolean'>true</is-cool> 
     30    # </user> 
     31    # 
     32    # evaluates to  
     33    #  
     34    # { "user" =>  
     35    #         { "gender"    => "m", 
     36    #           "age"       => 35, 
     37    #           "name"      => "Home Simpson", 
     38    #           "dob"       => DateObject( 1998-01-01 ), 
     39    #           "joined_at" => TimeObject( 2000-04-28 23:01), 
     40    #           "is_cool"   => true  
     41    #         } 
     42    #     } 
     43    # 
     44    # === Mixed Content 
     45    # <story> 
     46    #   A Quick <em>brown</em> Fox 
     47    # </story> 
     48    # 
     49    # evaluates to 
     50    # { "story" => "A Quick <em>brown</em> Fox" } 
     51    #  
     52    # === Attributes other than type on a node containing text 
     53    # <story is-good='fasle'> 
     54    #   A Quick <em>brown</em> Fox 
     55    # </story> 
     56    # 
     57    # evaluates to 
     58    # { "story" => "A Quick <em>brown</em> Fox" } 
     59    # 
     60    # <bicep unit='inches' type='integer'>60</bicep> 
     61    # 
     62    # evaluates with a typecast to an integer.  But ignores the unit attribute 
     63    # { "bicep" => 60 } 
     64    def from_xml( xml ) 
     65      ToHashParser.from_xml(xml) 
     66    end 
    6967  end 
    7068   
    7169  # convert this hash into a Mash for string or symbol key access 
    7270  def to_mash 
    73           hash = Mash.new(self)  
    74           hash.default = self.default  
    75           hash 
    76         end 
    77  
    78   # convert this hash to a query string param 
    79   #   {:name => "Bob", :address => {:street => '111 Ruby Ave.', :city => 'Ruby Central', :phones => ['111-111-1111', '222-222-2222']}}.to_params 
     71    hash = Mash.new(self) 
     72    hash.default = default 
     73    hash 
     74  end 
     75   
     76  # Convert this hash to a query string: 
     77  #    
     78  #   { :name => "Bob", 
     79  #     :address => { 
     80  #       :street => '111 Ruby Ave.', 
     81  #       :city => 'Ruby Central', 
     82  #       :phones => ['111-111-1111', '222-222-2222'] 
     83  #     } 
     84  #   }.to_params 
    8085  #   #=> "name=Bob&address[city]=Ruby Central&address[phones]=111-111-1111222-222-2222&address[street]=111 Ruby Ave." 
     86  #  
    8187  def to_params 
    82     result = '' 
     88    params = '' 
    8389    stack = [] 
    84  
    85     each do |key, value| 
    86       Hash === value ? stack << [key, value] : result <<  "#{key}=#{value}&" 
    87     end 
    88  
     90     
     91    each do |k, v| 
     92      if v.is_a?(Hash) 
     93        stack << [k,v] 
     94      else 
     95        params << "#{k}=#{v}&" 
     96      end 
     97    end 
     98     
    8999    stack.each do |parent, hash| 
    90       hash.each do |key, value
    91         if Hash === value 
    92           stack << ["#{parent}[#{key}]", value
     100      hash.each do |k, v
     101        if v.is_a?(Hash) 
     102          stack << ["#{parent}[#{k}]", v
    93103        else 
    94           result << "#{parent}[#{key}]=#{value}&" 
     104          params << "#{parent}[#{k}]=#{v}&" 
    95105        end 
    96106      end 
    97107    end 
    98     result.chop 
    99   end 
    100    
    101   # lets through the keys in the argument 
    102   #   {:one => 1, :two => 2, :three => 3}.pass(:one) 
    103   #   #=> {:one=>1} 
     108     
     109    params.chop! # trailing & 
     110    params 
     111  end 
     112   
     113  # Returns a new Hash with only the selected keys: 
     114  #    
     115  #   { :one => 1, :two => 2, :three => 3 }.only(:one) 
     116  #   #=> { :one => 1 } 
    104117  def only(*allowed)  
    105     self.reject { |k,v| ! allowed.include?(k) }  
    106   end 
    107  
    108   # blocks the keys in the arguments 
    109   #   {:one => 1, :two => 2, :three => 3}.block(:one) 
    110   #   #=> {:two=>2, :three=>3} 
     118    reject { |k,v| !allowed.include?(k) } 
     119  end 
     120   
     121  # Returns a new hash without the selected keys: 
     122  #  
     123  #   { :one => 1, :two => 2, :three => 3 }.except(:one) 
     124  #   #=> { :two => 2, :three => 3 } 
     125  #  
    111126  def except(*rejected)  
    112     self.reject { |k,v| rejected.include?(k) }  
    113   end 
    114  
     127    reject { |k,v| rejected.include?(k) } 
     128  end 
     129   
    115130  # Converts the hash into xml attributes 
     131  #  
    116132  #   { :one => "ONE", "two"=>"TWO" }.to_xml_attributes 
    117133  #   #=> 'one="ONE" two="TWO"' 
     134  #  
    118135  def to_xml_attributes 
    119136    map do |k,v| 
    120137      %{#{k.to_s.camelize.sub(/^(.{1,1})/) { |m| m.downcase }}="#{v}"}  
    121     end.join(" "
     138    end.join(' '
    122139  end 
    123140   
    124141  alias_method :to_html_attributes, :to_xml_attributes 
    125   
     142   
    126143  # Adds the given class symbol or string to the hash in the 
    127144  # :class key.  This will add a html class if there are already any existing 
    128145  # or create the key and add this as the first class 
    129   # 
    130   # Example 
     146  #  
    131147  #   @hash[:class] #=> nil 
    132148  #   @hash.add_html_class!(:selected) #=> @hash[:class] == "selected" 
    133   # 
     149  #    
    134150  #   @hash.add_html_class!("class1 class2") #=> @hash[:class] == "selected class1 class2" 
     151  #  
    135152  def add_html_class!(html_class) 
    136153    if self[:class] 
     
    140157    end 
    141158  end 
    142  
    143   # Destructively convert all keys to symbols recursively. 
     159   
     160  # Destructively convert all keys which respond_to?(:to_sym) to symbols. 
     161  # Works recursively if given nested hashes. 
     162  #  
    144163  #  { 'one' => 1, 'two' => 2 }.symbolize_keys! 
    145164  #  #=> { :one => 1, :two => 2 } 
     165  #  
    146166  def symbolize_keys! 
    147167    each do |k,v|  
    148          sym = k.respond_to?(:to_sym) ? k.to_sym : k  
    149          self[sym] = Hash === v ? v.symbolize_keys! : v  
    150          delete(k) unless k == sym 
     168      sym = k.respond_to?(:to_sym) ? k.to_sym : k  
     169      self[sym] = Hash === v ? v.symbolize_keys! : v  
     170      delete(k) unless k == sym 
    151171    end 
    152172    self 
    153173  end 
    154  
    155   # Destructively convert each key to an uppercase string non-recursively. 
    156   #   {:name => "Bob", "age" => 12, "nick" => "Bobinator"}.environmentize_keys! 
    157   #   #=> {"NAME"=>"Bob", "NICK"=>"Bobinator", "AGE"=>12} 
     174   
     175  # Destructively and non-recursively convert each key to an uppercase string. 
     176  #  
     177  #   { :name => "Bob", "age" => 12, "nick" => "Bobinator" }.environmentize_keys! 
     178  #   #=> { "NAME" => "Bob", "NICK" => "Bobinator", "AGE" => 12 } 
     179  #  
    158180  def environmentize_keys! 
    159181    keys.each do |key| 
     
    162184    self 
    163185  end 
    164  
    165   def method_missing(m,*a) #:nodoc: 
    166     m.to_s =~ /=$/ ? self[$`]=a[0] : a==[] ? self[m] : raise(NoMethodError,"#{m}") 
    167   end 
    168    
    169   def respond_to?(method, include_private=false) 
     186   
     187  def method_missing(method, *args) #:nodoc: 
     188    if method.to_s =~ /=$/ 
     189      self[$`] = args[0] 
     190    elsif args.empty? 
     191      self[method] 
     192    else 
     193      raise NoMethodError, method.to_s 
     194    end 
     195  end 
     196   
     197  def respond_to?(method, include_private = false) 
    170198    return true if keys.include?(method) 
    171199    super(method, include_private) 
    172200  end 
    173    
    174 end   
     201end 
    175202 
    176203require 'rexml/parsers/streamparser' 
     
    205232      # group by the first key of each element of the array to find repeating groups 
    206233      groups = @children.group_by{ |c| c.name } 
    207  
    208       hash = {}   
     234       
     235      hash = {} 
    209236      groups.each do |key, values| 
    210237        if values.size == 1 
    211           hash.merge!( values.first )  
     238          hash.merge! values.first 
    212239        else 
    213           hash.merge!( key => values.map{ |element| element.to_hash[key] } ) 
     240          hash.merge! key => values.map { |element| element.to_hash[key] } 
    214241        end 
    215242      end 
    216  
     243       
    217244      # merge the arrays, including attributes 
    218       hash.merge!( attributes ) unless attributes.empty? 
    219       return { name => hash } 
    220     end 
    221   end 
    222  
    223   def to_s  
    224     self.to_html 
    225   end 
    226  
    227  
     245      hash.merge! attributes unless attributes.empty? 
     246       
     247      { name => hash } 
     248    end 
     249  end 
     250   
    228251  def typecast_value(value) 
    229     return value unless attributes["type"]  
     252    return value unless attributes["type"] 
    230253     
    231254    case attributes["type"] 
     
    237260    end 
    238261  end 
    239  
     262   
    240263  def translate_xml_entities(value) 
    241264    value.gsub(/&lt;/,   "<"). 
     
    245268          gsub(/&amp;/,  "&") 
    246269  end 
    247  
    248   def undasherize_keys(params) 
    249     params.keys.each do |key, vvalue| 
    250       params[key.tr("-", "_")] = params.delete(key) 
    251     end 
    252     params  
    253   end 
    254  
     270   
     271  def undasherize_keys(params) 
     272    params.keys.each do |key, vvalue| 
     273      params[key.tr("-", "_")] = params.delete(key) 
     274    end 
     275    params 
     276  end 
     277   
    255278  def inner_html 
    256279    @children.join 
    257280  end 
    258  
    259   def to_html  
     281   
     282  def to_html 
    260283    "<#{name}#{attributes.to_xml_attributes}>#{inner_html}</#{name}>" 
     284  end 
     285   
     286  def to_s  
     287    to_html 
    261288  end 
    262289end 
    263290 
    264291class ToHashParser # :nodoc: 
    265        def self.from_xml(xml) 
    266                stack = [] 
    267                parser = REXML::Parsers::BaseParser.new(xml) 
    268                  
    269                while true 
    270                        event = parser.pull 
    271                        case event[0] 
    272                        when :end_document 
    273                                break 
    274                        when :end_doctype, :start_doctype 
    275                                # do nothing 
    276                        when :start_element 
    277                          stack.push REXMLUtilityNode.new(event[1], event[2]) 
    278                        when :end_element 
    279                          if stack.size > 1 
    280                            temp = stack.pop 
    281                            stack.last.add_node(temp) 
    282                          end 
    283                        when :text, :cdata 
    284                          stack.last.add_node(event[1]) unless event[1].strip.length == 0 
    285                        end 
    286                end 
    287                stack.pop.to_hash 
    288         end      
     292  def self.from_xml(xml) 
     293    stack = [] 
     294    parser = REXML::Parsers::BaseParser.new(xml) 
     295     
     296    while true 
     297      event = parser.pull 
     298      case event[0] 
     299      when :end_document 
     300        break 
     301      when :end_doctype, :start_doctype 
     302        # do nothing 
     303      when :start_element 
     304        stack.push REXMLUtilityNode.new(event[1], event[2]) 
     305      when :end_element 
     306        if stack.size > 1 
     307          temp = stack.pop 
     308          stack.last.add_node(temp) 
     309        end 
     310      when :text, :cdata 
     311        stack.last.add_node(event[1]) unless event[1].strip.length == 0 
     312      end 
     313    end 
     314    stack.pop.to_hash 
     315  end 
    289316end