# # Copyright (C) 2010-2016 dtk contributors # # This file is part of the dtk project. # # 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. # require 'singleton' require 'json' require 'yaml' module DtkCommon module DSL class Loader include Singleton def self.file_parser(file_type,version=nil) instance.file_parser(file_type,version) end def file_parser(file_type,version=nil) ret = (@loaded_types[file_type]||{})[version] return ret if ret unless FileTypes.include?(file_type) raise Error.new("Illegal file type (#{file_type})") end #load base if no versions loaded already base_path = "#{BaseDirForFileTypes}/#{file_type}" #if (@loaded_types[file_type]||{}).empty? # require File.expand_path(base_path,File.dirname(__FILE__)) #end version ||= default_version(file_type) path = "#{base_path}/v#{version.to_s}/#{file_type}" require File.expand_path(path, File.dirname(__FILE__)) base_class = FileParser.const_get(Aux.snake_to_camel_case(file_type.to_s)) ret_class = base_class.const_get("V#{version.to_s}") input_hash_class = ret_class.const_get "InputHash" ret = ret_class.new(input_hash_class) (@loaded_types[file_type] ||= Hash.new)[version] = ret ret end BaseDirForFileTypes = "file_parser/file_types" private def initialize() @loaded_types = Hash.new end def default_version(file_type) FileTypeVesisonDefaults[file_type] || 1 end FileTypes = [ :component_module_refs, :assembly ] FileTypeVesisonDefaults = { :component_module_refs => 1, :assembly => 2 } end class FileParser def initialize(input_hash_class) @input_hash_class = input_hash_class end def self.implements_method?(method_name) [:parse_content,:generate_hash].include?(method_name) end def self.parse_content(file_type,file_content,opts={}) ret = OutputArray.new # if there is no content (nil) return empty array as if content was empty return ret unless file_content file_parser = Loader.file_parser(file_type,opts[:version]) # raw_hash_content = convert_json_content_to_hash(file_content,opts) #TODO: for Rich # we need to implement dsl v3 parser, this is just temp fix for autoimport # just changed parser to yaml instead of json because new modules/services # are in yaml format now raw_hash_content = convert_yaml_content_to_hash(file_content,opts) return raw_hash_content if raw_hash_content.is_a?(ErrorUsage::DSLParsing::JSONParsing) file_parser.parse_hash_content_aux(raw_hash_content) end def parse_hash_content_aux(raw_hash) parse_hash_content(input_form(raw_hash)) end def self.generate_hash(file_type,output_array,opts={}) file_parser = Loader.file_parser(file_type,opts[:version]) file_parser.generate_hash(output_array) end class OutputArray < Array def <<(hash_el) bad_keys = hash_el.keys - self.class.keys_for_row() unless bad_keys.empty? raise Error.new("Illegal keys being inserted in OutputArray (#{bad_keys.join(',')})") end super end def +(output_obj) if output_obj.kind_of?(OutputArray) super elsif output_obj.kind_of?(OutputHash) super(OutputArray.new(OutputHash)) elsif output_obj.nil? self else raise Error.new("Unexpected object type (#{output_obj.class})") end end #can be overwritten def self.has_required_keys?(hash_el) (keys_for_row() - hash_el.keys?).nil? end end class OutputHash < ::DTK::Common::SimpleHashObject def merge_non_empty!(hash) hash.each{|k,v| merge!(k => v) unless v.nil? or v.empty?} self end def +(output_obj) if output_obj.kind_of?(OutputArray) OutputArray.new(self) + output_obj elsif output_obj.kind_of?(OutputHash) merge(output_obj) elsif output_obj.nil? self else raise Error.new("Unexpected object type (#{output_obj.class})") end end end class InputHash < Hash #to provide autovification and use of symbol indexes def initialize(hash=nil) super() return unless hash replace_el = hash.inject(Hash.new) do |h,(k,v)| processed_v = (v.kind_of?(Hash) ? self.class.new(v) : v) h.merge(k => processed_v) end replace(replace_el) end def [](index) val = super(internal_key_form(index)) || {} (val.kind_of?(Hash) ? self.class.new(val) : val) end def only_has_keys?(*only_has_keys) (keys() - only_has_keys.map{|k|internal_key_form(k)}).empty? end private def internal_key_form(key) key.to_s end end private def input_form(raw_hash) @input_hash_class.new(raw_hash) end def self.convert_yaml_content_to_hash(content, opts={}) ret = Hash.new return ret if content.empty? begin YAML.load(content) rescue Exception => e return ErrorUsage::DSLParsing::JSONParsing.new("YAML parsing error #{e.to_s} in file", opts[:file_path]) if opts[:do_not_raise] raise ErrorUsage::DSLParsing::JSONParsing.new("YAML parsing error #{e.to_s} in file", opts[:file_path]) end end def self.convert_json_content_to_hash(json_file_content, opts={}) ret = Hash.new if json_file_content.empty? return ret end begin ::JSON.parse(json_file_content) rescue ::JSON::ParserError => e # raise ErrorUsage::JSONParse.new(e.to_s) return ErrorUsage::DSLParsing::JSONParsing.new("JSON parsing error #{e.to_s} in file", opts[:file_path]) if opts[:do_not_raise] raise ErrorUsage::DSLParsing::JSONParsing.new("JSON parsing error #{e.to_s} in file", opts[:file_path]) end end end class ErrorUsage < Error class DSLParsing < self def initialize(base_error_msg,file_path_or_opts=nil) file_path,opts = file_path_and_opts(file_path_or_opts) super(local_method_err_msg(base_error_msg,file_path),opts) end private def local_method_err_msg(base_error_msg,file_path=nil) file_ref = (file_path && " (in file: #{file_path})") "#{base_error_msg}#{file_ref}" end def file_path_and_opts(file_path_or_opts) file_path = nil opts = Hash.new if file_path_or_opts.kind_of?(Hash) opts = file_path_or_opts else file_path = file_path_or_opts end [file_path,opts] end class JSONParsing < self end end #when error is dtk content class DTKParse < self end end end end