# Copyright 2011-2012 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
module AWS
module Core
module XML
# A class that simplifies building XML {Parser} rules. This is also
# a compatability layer between the old and new formats of the api
# config.
class Grammar
def initialize rules = {}, options = {}
@rules = rules
@context = @rules
@element_name = 'xml'
@inflect_rename = options.key?(:inflect_rename) ?
options[:inflect_rename] : true
end
# Parses the XML with the rules provided by the current grammar.
# This method is meant to provide backwards compatability with
# the old XmlGrammar class that handled rules and parsing.
# @param [String] xml
# @return [Data] Returns a hash-like parsed response.
def parse xml
Data.new(Parser.parse(xml, rules))
end
# @return [Hash] Returns a hash of rules defined by this grammar.
attr_reader :rules
# Returns a new grammar (leaving the current one un-modified) with
# the given customizations applied. Customizations can be given in
# a hash-form or in a block form.
#
# @example Block-form customizations
#
# grammar.customize do
# element "EnumElement" do
# symbol_value
# list
# end
# end
#
# @example Hash-form customizations
#
# grammar.customize "EnumElement" => [:symbol_value, :list]
#
# @return [Grammar] Returns a grammar with the given customizations
# applied.
#
def customize customizations = nil, &block
opts = { :inflect_rename => @inflect_rename }
self.class.customize(customizations, @rules, opts, &block)
end
# Applies customizations to the current grammar, not returning
# a new grammar.
def customize! customizations = nil, &block
apply_customizations(customizations) if customizations
instance_eval(&block) if block_given?
self
end
def self.customize customizations = nil, rules = {}, opts = {}, &block
grammar = self.new(deep_copy(rules), opts)
grammar.send(:apply_customizations, customizations) if customizations
grammar.instance_eval(&block) if block_given?
grammar
end
def self.parse xml
self.new.parse(xml)
end
protected
# Performs a deep copy of the rules hash so that it can be
# customized without chaning the parent grammar.
def self.deep_copy rules
rules.inject({}) do |copy,(key,value)|
copy[key] = value.is_a?(Hash) ? deep_copy(value) : value
copy
end
end
def apply_customizations customizations
customizations.each do |item|
(type, identifier, args) = parse_customization_item(item)
case type
when :method
validate_config_method(identifier)
validate_args(identifier, args)
send(identifier, *args)
when :element
element(identifier) do
apply_customizations(args)
end
end
end
end
def parse_customization_item item
case item
when Symbol
[:method, item, []]
when Hash
(method, arg) = item.to_a.first
if method.kind_of?(Symbol)
[:method, method, [arg].flatten]
else
[:element, method, arg]
end
end
end
def validate_config_method(method)
allow_methods = %w(
rename attribute_name boolean integer long float list force string
ignore collect_values symbol_value timestamp map_entry map blob
position
)
unless allow_methods.include?(method.to_s)
raise "#{method} cannot be used in configuration"
end
end
def validate_args(identifier, args)
arity = method(identifier).arity
if args.length > 0
raise "#{identifier} does not accept an argument" if
arity == 0
else
raise "#{identifier} requires an argument" unless
arity == 0 || arity == -1
end
end
def inflect value
Inflection.ruby_name(value.to_s).to_sym
end
##
## customization methods
##
def element element_name, &block
parent_context = @context
parent_element_name = @element_name
@context = context_for_child(element_name)
@element_name = element_name
begin
if block_given?
block.arity == 1 ? yield(parent_element_name) : yield
end
ensure
@context = parent_context
@element_name = parent_element_name
end
end
def ignore
@context[:ignore] = true
end
def rename new_name
if @inflect_rename
@context[:rename] = inflect(new_name)
else
@context[:rename] = new_name
end
end
def force
@context[:force] = true
end
def collect_values
@context[:list] = true
end
def index index_name, options = {}
@context[:index] = options.merge(:name => index_name)
end
def default_value name, value
@context[:defaults] ||= {}
@context[:defaults][name] = value
end
def list child_element_name = nil, &block
if child_element_name
ignore
element(child_element_name) do |parent_element_name|
rename(parent_element_name)
collect_values
yield if block_given?
end
else
collect_values
end
end
def map_entry key_element_name, value_element_name
@context[:map] = [key_element_name, value_element_name]
end
def map map_element_name, key_element_name, value_element_name
ignore
element(map_element_name) do |parent_element_name|
rename(parent_element_name)
map_entry(key_element_name, value_element_name)
end
end
def wrapper method_name, options = {}, &block
options[:for].each do |child|
context_for_child(child)[:wrap] = method_name
end
end
def construct_value &block
raise 'remove the need for this'
end
def boolean_value
@context[:type] = :boolean
end
alias_method :boolean, :boolean_value
def blob_value
@context[:type] = :blob
end
alias_method :blob, :blob_value
def datetime_value
@context[:type] = :datetime
end
alias_method :datetime, :datetime_value
def time_value
@context[:type] = :time
end
alias_method :timestamp, :time_value
alias_method :time, :time_value
def string_value
@context[:type] = :string
end
alias_method :string, :string_value
def integer_value
@context[:type] = :integer
end
alias_method :integer, :integer_value
alias_method :long, :integer_value
def float_value
@context[:type] = :float
end
alias_method :float, :float_value
def symbol_value
@context[:type] = :symbol
end
alias_method :symbol, :symbol_value
def eql? other
other.is_a?(Grammar) and self.rules == other.rules
end
alias_method :==, :eql?
def position *args; end
def http_trait *args; end
alias_method :http_header, :http_trait
alias_method :http_uri_label, :http_trait
alias_method :http_payload, :http_trait
alias_method :http_status, :http_trait
protected
def context_for_child child_element_name
@context[:children] ||= {}
@context[:children][child_element_name] ||= {}
@context[:children][child_element_name]
end
end
end
end
end