=begin Copyright 2010-2013 Tasos Laskos Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License 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. =end module Arachni module Platform # Represents a collection of applicable platforms. # # @author Tasos "Zapotek" Laskos class List include Enumerable # @param [Array] valid_platforms # Valid platforms for this list. def initialize( valid_platforms ) @valid_platforms = normalize!( valid_platforms ) @platforms = Set.new end # @return [Array] Supported platforms. def valid @valid ||= Set.new( hierarchical? ? flat_list : @valid_platforms ) end # Selects appropriate data depending on the applicable platforms # from `data_per_platform`. # # @param [Hash{ => Object}] data_per_platform # Hash with platform names as keys and arbitrary data as values. # # @return [Hash] `data_per_platform` with non-applicable entries removed. # @raise [Error::Invalid] On {#invalid?} platforms. def pick( data_per_platform ) orig_data_per_platform = data_per_platform.dup data_per_platform = data_per_platform.dup data_per_platform.select! { |k, v| include? k } # Bail out if the valid platforms are just a flat array, without hierarchy. return data_per_platform if !hierarchical? # Keep track of parents which will be removed due to the existence of # their children. specified_parents = [] # Remove parents if we have children. data_per_platform.keys.each do |platform| specified_parents |= parents = find_parents( platform ) data_per_platform.reject! { |k, _| parents.include? k } end # Include all of the parents' children if parents are specified but no # children for them. children = {} children_for = valid & @platforms.to_a children_for.each do |platform| next if specified_parents.include? platform c = find_children( platform ) children.merge! orig_data_per_platform.select { |k, _| c.include? k } end data_per_platform.merge! children # Include the nearest parent data there is a child platform but there # are no data for it. ignore = data_per_platform.keys | specified_parents orig_data_per_platform.each do |platform, data| next if ignore.include?( platform ) || !include_any?( find_children( platform ) ) data_per_platform[platform] = data end data_per_platform end # @param [Array Symbol, String] platforms # Platform(s) to check. # @return [Boolean] # `true` if platforms are valid (i.e. in {#valid}), `false` otherwise. # @see #invalid? def valid?( platforms ) normalize( platforms ) true rescue false end # @param [Array Symbol, String] platforms # Platform(s) to check. # @return [Boolean] # `true` if platforms are invalid (i.e. not in {#valid}), `false` otherwise. # @see #valid? def invalid?( platforms ) !valid?( platforms ) end # @return [Boolean] # `true` if there are no applicable platforms, `false` otherwise. def empty? @platforms.empty? end # @return [Boolean] # `true` if there are applicable platforms, `false` otherwise. def any? !empty? end # @param [Symbol, String] platform Platform to add to the list. # @return [Platform] `self` # @raise [Error::Invalid] On {#invalid?} platforms. def <<( platform ) @platforms << normalize( platform ) self end # @param [Platform, Enumerable] enum # Enumerable object containing platforms. # @return [Platform] Updated copy of `self`. # @raise [Error::Invalid] On {#invalid?} platforms. def merge( enum ) dup.merge!( enum ) end # @param [Enumerable] enum # Enumerable object containing platforms. # @return [Platform] Updated `self`. # @raise [Error::Invalid] On {#invalid?} platforms. def merge!( enum ) @platforms.merge normalize( enum ) self end alias update merge! # @param [Platform, Enumerable] enum # {Platform} or enumerable object containing platforms. # @return [Platform] # New {Platform} built by merging `self` and the elements of the # given enumerable object. # @raise [Error::Invalid] On {#invalid?} platforms. def |( enum ) dup.merge( enum ) end alias + | # @param [Block] block Block to be passed each platform. # @return [Enumerator, Platform] # `Enumerator` if no `block` is given, `self` otherwise. def each( &block ) return enum_for( __method__ ) if !block_given? @platforms.each( &block ) self end # @param [Symbol, String] platform Platform to check. # @return [Boolean] # `true` if `platform` applies to the given resource, `false` otherwise. # @raise [Error::Invalid] On {#invalid?} `platforms`. def include?( platform ) @platforms.include? normalize( platform ) end # @param [Array] platforms Platform to check. # @return [Boolean] # `true` if any platform in `platforms` applies to the given resource, # `false` otherwise. # @raise [Error::Invalid] On {#invalid?} `platforms`. def include_any?( platforms ) (@platforms & normalize( platforms )).any? end # Clears platforms. def clear @platforms.clear end # @return [Platform] Copy of `self`. def dup self.class.new( @valid_platforms ).tap { |p| p.platforms = @platforms } end # @return [Boolean] # `true` if the list has a hierarchy, `false` otherwise. def hierarchical? @valid_platforms.is_a? Hash end def self.normalize( platforms ) case platforms when self.class, Symbol platforms when String platforms.to_sym when Hash platforms.symbolize_keys when Enumerable, Array platforms.to_a.flatten.map( &:to_sym ).uniq.sort end end protected def platforms=( enum ) @platforms = Set.new( enum ) end private # @return [Array] Flat list of supported {OS operating systems}. def flat_list( hash = @valid_platforms ) flat = [] hash.each do |k, v| flat << k flat |= flat_list( v ) if v.any? end flat.reject { |i| !i.is_a? Symbol } end def find_children( platform, hash = @valid_platforms ) return [] if hash.empty? children = [] hash.each do |k, v| if k == platform children |= flat_list( v ) elsif v.is_a? Hash children |= find_children( platform, v ) end end children end def find_parents( platform, hash = @valid_platforms ) return [] if hash.empty? parents = [] hash.each do |k, v| if v.include?( platform ) parents << k elsif v.is_a? Hash parents |= find_parents( platform, v ) end end parents end def normalize( platforms ) return platforms if platforms.is_a? self.class if platforms.is_a?( Symbol ) || platforms.is_a?( String ) platform = normalize!( platforms ) if !valid.include?( platform ) fail Error::Invalid, "Invalid platform: #{platform}" end return platform end platforms = normalize!( platforms ) invalid = (valid + platforms) - valid if invalid.any? fail Error::Invalid, "Invalid platforms: #{invalid.to_a.join( ', ' )}" end platforms end def normalize!( platforms ) self.class.normalize( platforms ) end end end end