<commit_before>#TODO: this does some conversion of form; should determine what shoudl be done here versus subsequent parser phase
#TODO: doe snot check for extra attributes
module DTK; class ComponentDSL; class V2
  class ObjectModelForm < ComponentDSL::ObjectModelForm
    def self.convert(input_hash)
      new.convert(input_hash)
    end
    def convert(input_hash)
      Component.new(input_hash.req(:module)).convert(input_hash.req(:components))
    end

    def self.convert_attribute_mapping(input_am,base_cmp,dep_cmp,opts={})
      Choice.new.convert_attribute_mapping(input_am,base_cmp,dep_cmp,opts)
    end

    def convert_to_hash_form(hash_or_array,&block)
      if hash_or_array.kind_of?(Hash)
        hash_or_array.each_pair{|k,v|block.call(k,v)}
      else #hash_or_array.kind_of?(Array)
        hash_or_array.each do |el|
          if el.kind_of?(Hash)
            block.call(el.keys.first,el.values.first)
          else #el.kind_of?(String)
            block.call(el,Hash.new)
          end
        end
      end
    end

   private

    ModCmpDelim = "__"
    CmpPPDelim = '::'
    def convert_to_internal_cmp_form(cmp)
      cmp.gsub(Regexp.new(CmpPPDelim),ModCmpDelim)
    end
    def convert_to_pp_cmp_form(cmp)
      cmp.gsub(Regexp.new(ModCmpDelim),CmpPPDelim)
    end
    def component_part(cmp_external_form)
      if cmp_external_form =~ Regexp.new("^.+#{CmpPPDelim}(.+$)")
        $1
      else
        cmp_external_form
      end
    end
    #returns a subset or hash for all keys listed; if an extyra keys then null signifying error condition is returned 
    # '*' means required
    #e.g., keys ["*module","version"]
    def hash_contains?(hash,keys)
      req_keys = keys.inject(Hash.new){|h,r|h.merge(r.gsub(/^\*/,"") => (r =~ /^\*/) ? 1 : 0)}
      ret = Hash.new
      hash.each do |k,v|
        return nil unless req_keys[k]
        req_keys[k] = 0
        ret.merge!(k => v)
      end
      #return nil if there is a required key not found
      unless req_keys.values.find{|x|x == 1} 
        ret
      end
    end

    class Component < self
      def initialize(module_name)
        @module_name = module_name
      end
      def convert(input_hash)
        input_hash.inject(OutputHash.new){|h,(k,v)|h.merge(key(k) => body(v,k))}
      end
     private
      def key(input_key)
         qualified_component(input_key)
      end
      def qualified_component(cmp)
        if @module_name == cmp
          cmp
        else
          "#{@module_name}#{ModCmpDelim}#{cmp}"
        end
      end

      def body(input_hash,cmp)
        ret = OutputHash.new
        cmp_type = ret["display_name"] = ret["component_type"] = qualified_component(cmp)
        ret["basic_type"] = "service"
        ret.set_if_not_nil("description",input_hash["description"])
        external_ref = external_ref(input_hash.req(:external_ref),cmp)
        ret["external_ref"] = external_ref
        ret.set_if_not_nil("only_one_per_node",only_one_per_node(external_ref))
        add_attributes!(ret,cmp_type,input_hash)
        opts = Hash.new
        add_dependent_components!(ret,input_hash,cmp_type,opts)
        ret.set_if_not_nil("include_module",include_modules?(input_hash["include_modules"]))
        if opts[:constants]
          add_attributes!(ret,cmp_type,ret_input_hash_with_constants(opts[:constants]),:constant_attribute => true)
        end
        ret
      end

      def ret_input_hash_with_constants(constant_assigns)
        attrs_hash = constant_assigns.inject(InputHash.new) do |h,ca|
          el = {ca.attribute_name() => {
              "type"=>ca.datatype()||"string",
              "default" => ca.attribute_value(),
              "hidden" => true
            }}
          h.merge(el)
        end
        InputHash.new("attributes" => attrs_hash)
      end

      def external_ref(input_hash,cmp)
        unless input_hash.kind_of?(Hash) and input_hash.size == 1
          raise ParsingError.new("Component (?1) external_ref is ill-formed (?2)",cmp,input_hash)
        end
        type = input_hash.keys.first
        name_key = 
          case type 
            when "puppet_class" then "class_name" 
            when "puppet_definition" then "definition_name" 
          else raise ParsingError.new("Component (?1) external_ref has illegal type (?2)",cmp,type)
          end
        name = input_hash.values.first
        OutputHash.new("type" => type,name_key => name)
      end

      def only_one_per_node(external_ref)
        external_ref["type"] != "puppet_definition"
      end

      def include_modules?(incl_module_array)
        return nil if incl_module_array.nil?
        unless incl_module_array.kind_of?(Array)
          raise ParsingError.new("The content in the 'include_modules' key (?1) is ill-formed",incl_module_array)
        end
        ret = OutputHash.new
        incl_module_array.each do |incl_module|
          el = 
            if incl_module.kind_of?(String)
              {"display_name" => incl_module}
            elsif incl_module.kind_of?(Hash)
              hash = hash_contains?(incl_module,["*module","version"])
              version_constraint = include_module_version_constraint(hash["version"])
              {"display_name" => hash["module"],"version_constraint" => version_constraint}
            end
          unless el
            raise ParsingError.new("The include_module element (?1) is ill-formed",incl_module)
          end
          ref = el["display_name"]
          ret.merge!(ref => el)
        end
        ret
      end

      IncludeModVersionOps = [">="]
      IncludeModVersionNumRegexp = /^[0-9]+\.[0-9]+\.[0-9]+/
      def include_module_version_constraint(version)
        no_error = 
          if version.kind_of?(String)
            if version =~ IncludeModVersionNumRegexp
              true
            end
          elsif version.kind_of?(Array) 
            if version.size == 2 and IncludeModVersionOps.include?(version[0]) and version[1] =~ IncludeModVersionNumRegexp
              true
            end
          end
        unless no_error
          raise ParsingError.new("The include_modules version key (?1)  is ill-formed",version)
        end
        version
      end

      def add_attributes!(ret,cmp_type,input_hash,opts={})
        if in_attrs = input_hash["attributes"]
          attrs = OutputHash.new
          in_attrs.each_pair do |name,info|
            external_ref = 
              if opts[:constant_attribute]
                Attribute::Constant.ret_external_ref()
              else
                type = "puppet_attribute" #TODO: hard-wired
                external_ref_name = (info["external_ref"]||{})[type]||name
                {"type" => type,
                 "path" => "node[#{cmp_type}][#{external_ref_name}]"
              }
              end
            attr_props = OutputHash.new("display_name" => name,"external_ref" => external_ref)
            add_attr_data_type_attrs!(attr_props,info)
            attr_props["value_asserted"] = info["default"] #setting even when info["default"] so this can handle case where remove a default
            attr_props.set_if_not_nil("description",info["description"])
            attr_props.set_if_not_nil("required",info["required"])
            attr_props.set_if_not_nil("hidden",info["hidden"])
            attrs.merge!(name => attr_props)
          end
          if ret["attribute"]
            ret["attribute"].merge!(attrs)<commit_msg>fixed bug where dynamic attribute in dsl was not updating object model<commit_after>#TODO: this does some conversion of form; should determine what shoudl be done here versus subsequent parser phase
#TODO: doe snot check for extra attributes
module DTK; class ComponentDSL; class V2
  class ObjectModelForm < ComponentDSL::ObjectModelForm
    def self.convert(input_hash)
      new.convert(input_hash)
    end
    def convert(input_hash)
      Component.new(input_hash.req(:module)).convert(input_hash.req(:components))
    end

    def self.convert_attribute_mapping(input_am,base_cmp,dep_cmp,opts={})
      Choice.new.convert_attribute_mapping(input_am,base_cmp,dep_cmp,opts)
    end

    def convert_to_hash_form(hash_or_array,&block)
      if hash_or_array.kind_of?(Hash)
        hash_or_array.each_pair{|k,v|block.call(k,v)}
      else #hash_or_array.kind_of?(Array)
        hash_or_array.each do |el|
          if el.kind_of?(Hash)
            block.call(el.keys.first,el.values.first)
          else #el.kind_of?(String)
            block.call(el,Hash.new)
          end
        end
      end
    end

   private

    ModCmpDelim = "__"
    CmpPPDelim = '::'
    def convert_to_internal_cmp_form(cmp)
      cmp.gsub(Regexp.new(CmpPPDelim),ModCmpDelim)
    end
    def convert_to_pp_cmp_form(cmp)
      cmp.gsub(Regexp.new(ModCmpDelim),CmpPPDelim)
    end
    def component_part(cmp_external_form)
      if cmp_external_form =~ Regexp.new("^.+#{CmpPPDelim}(.+$)")
        $1
      else
        cmp_external_form
      end
    end
    #returns a subset or hash for all keys listed; if an extyra keys then null signifying error condition is returned 
    # '*' means required
    #e.g., keys ["*module","version"]
    def hash_contains?(hash,keys)
      req_keys = keys.inject(Hash.new){|h,r|h.merge(r.gsub(/^\*/,"") => (r =~ /^\*/) ? 1 : 0)}
      ret = Hash.new
      hash.each do |k,v|
        return nil unless req_keys[k]
        req_keys[k] = 0
        ret.merge!(k => v)
      end
      #return nil if there is a required key not found
      unless req_keys.values.find{|x|x == 1} 
        ret
      end
    end

    class Component < self
      def initialize(module_name)
        @module_name = module_name
      end
      def convert(input_hash)
        input_hash.inject(OutputHash.new){|h,(k,v)|h.merge(key(k) => body(v,k))}
      end
     private
      def key(input_key)
         qualified_component(input_key)
      end
      def qualified_component(cmp)
        if @module_name == cmp
          cmp
        else
          "#{@module_name}#{ModCmpDelim}#{cmp}"
        end
      end

      def body(input_hash,cmp)
        ret = OutputHash.new
        cmp_type = ret["display_name"] = ret["component_type"] = qualified_component(cmp)
        ret["basic_type"] = "service"
        ret.set_if_not_nil("description",input_hash["description"])
        external_ref = external_ref(input_hash.req(:external_ref),cmp)
        ret["external_ref"] = external_ref
        ret.set_if_not_nil("only_one_per_node",only_one_per_node(external_ref))
        add_attributes!(ret,cmp_type,input_hash)
        opts = Hash.new
        add_dependent_components!(ret,input_hash,cmp_type,opts)
        ret.set_if_not_nil("include_module",include_modules?(input_hash["include_modules"]))
        if opts[:constants]
          add_attributes!(ret,cmp_type,ret_input_hash_with_constants(opts[:constants]),:constant_attribute => true)
        end
        ret
      end

      def ret_input_hash_with_constants(constant_assigns)
        attrs_hash = constant_assigns.inject(InputHash.new) do |h,ca|
          el = {ca.attribute_name() => {
              "type"=>ca.datatype()||"string",
              "default" => ca.attribute_value(),
              "hidden" => true
            }}
          h.merge(el)
        end
        InputHash.new("attributes" => attrs_hash)
      end

      def external_ref(input_hash,cmp)
        unless input_hash.kind_of?(Hash) and input_hash.size == 1
          raise ParsingError.new("Component (?1) external_ref is ill-formed (?2)",cmp,input_hash)
        end
        type = input_hash.keys.first
        name_key = 
          case type 
            when "puppet_class" then "class_name" 
            when "puppet_definition" then "definition_name" 
          else raise ParsingError.new("Component (?1) external_ref has illegal type (?2)",cmp,type)
          end
        name = input_hash.values.first
        OutputHash.new("type" => type,name_key => name)
      end

      def only_one_per_node(external_ref)
        external_ref["type"] != "puppet_definition"
      end

      def include_modules?(incl_module_array)
        return nil if incl_module_array.nil?
        unless incl_module_array.kind_of?(Array)
          raise ParsingError.new("The content in the 'include_modules' key (?1) is ill-formed",incl_module_array)
        end
        ret = OutputHash.new
        incl_module_array.each do |incl_module|
          el = 
            if incl_module.kind_of?(String)
              {"display_name" => incl_module}
            elsif incl_module.kind_of?(Hash)
              hash = hash_contains?(incl_module,["*module","version"])
              version_constraint = include_module_version_constraint(hash["version"])
              {"display_name" => hash["module"],"version_constraint" => version_constraint}
            end
          unless el
            raise ParsingError.new("The include_module element (?1) is ill-formed",incl_module)
          end
          ref = el["display_name"]
          ret.merge!(ref => el)
        end
        ret
      end

      IncludeModVersionOps = [">="]
      IncludeModVersionNumRegexp = /^[0-9]+\.[0-9]+\.[0-9]+/
      def include_module_version_constraint(version)
        no_error = 
          if version.kind_of?(String)
            if version =~ IncludeModVersionNumRegexp
              true
            end
          elsif version.kind_of?(Array) 
            if version.size == 2 and IncludeModVersionOps.include?(version[0]) and version[1] =~ IncludeModVersionNumRegexp
              true
            end
          end
        unless no_error
          raise ParsingError.new("The include_modules version key (?1)  is ill-formed",version)
        end
        version
      end

      def add_attributes!(ret,cmp_type,input_hash,opts={})
        if in_attrs = input_hash["attributes"]
          attrs = OutputHash.new
          in_attrs.each_pair do |name,info|
            external_ref = 
              if opts[:constant_attribute]
                Attribute::Constant.ret_external_ref()
              else
                type = "puppet_attribute" #TODO: hard-wired
                external_ref_name = (info["external_ref"]||{})[type]||name
                {"type" => type,
                 "path" => "node[#{cmp_type}][#{external_ref_name}]"
              }
              end
            attr_props = OutputHash.new("display_name" => name,"external_ref" => external_ref)
            add_attr_data_type_attrs!(attr_props,info)
            attr_props["value_asserted"] = info["default"] #setting even when info["default"] so this can handle case where remove a default
            %w{description required dynamic hidden}.each{|field|attr_props.set_if_not_nil(field,info[field])}
            attrs.merge!(name => attr_props)
          end
          if ret["attribute"]
            ret["attribute"].merge!(attrs)
{'fraction_non_alphanumeric': 0.10935601458080195, 'fraction_numerical': 0.0031051707843931416, 'mean_word_length': 2.4262257169287698, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5526044', 'n_tokens_mistral': 4579, 'n_tokens_neox': 4538, 'n_words': 855}