#!/usr/bin/ruby.ruby4.0 
#
# genprovider
#
#  Generate Ruby provider templates for use with cmpi-bindings
#
# Copyright (c) 2010, 2011 Klaus Kämpf <kkaempf@suse.de>
#
# Licensed under the Ruby license
#
# == Usage
#
# genprovider [-d] [-h] [-q] [-t] [-n <namespace>] [-I <includedir>] [-o <output>] <moffile> [<moffile> ...]
#
# -d:
#   turn on debugging
# -h:
#   show (this) help
# -q:
#   be quiet
# -I <includedir>
#   additional include directories to search
# -o <outputdir>
#   directory to write generated files, defaults to 'generated'
# -t:
#   generate testcase only
# <moffile>
#   .mof files to read
#

require 'rubygems'

require 'mof'
require 'pathname'
require 'fileutils'

$:.push(File.join(File.dirname(__FILE__), '..', 'lib'))

require 'genprovider'

#--------------------------------------------------------------------

#
# Extend CIM::Class with parent attribute, point to CIM::Class of parent
#

module CIM
  class Class
    attr_accessor :parent
  end
end

#
# Convert CamelCase to underline_case
#

class String
  def decamelize
    # CamelCase -> under_score
    self.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
	 gsub(/([a-z\d])([A-Z])/,'\1_\2').
	 tr("-", "_").
	 downcase
  end
end

#
# find_and_parse_superclass_of
#
# Find the superclass name of a class, parse its mof file
# returns Hash of { classname -> CIM::Class }
#

def find_and_parse_superclass_of c, options
  superclasses = {}
  # parent unknown
  #  try parent.mof
  begin
    parser = MOF::Parser.new options
    result = parser.parse ["qualifiers.mof", "#{c.superclass}.mof"]
    if result
      result.each_value do |r|
	r.classes.each do |parent|
	  if parent.name == c.superclass
	    c.parent = parent
	    superclasses[parent.name] = parent
	    superclasses.merge!(find_and_parse_superclass_of(parent,options)) if parent.superclass
	  end
	end
      end
    else
      $stderr.puts "Warn: Parent #{c.superclass} of #{c.name} not known"
    end
  rescue Exception => e
    parser.error_handler e
  end
  superclasses
end

#--------------------------------------------------------------------

# path to current cim schema files
CIM_CURRENT = "/usr/share/mof/cim-current"

####
## argv handling

generate_test = ARGV.delete("-t")

#
# parse mofparse arguments
#

moffiles, options = MOF::Parser.argv_handler "genprovider", ARGV
if moffiles.empty?
  $stderr.puts "No .mof files given"
  exit 1
end
if !options[:namespace].nil? && options[:namespace].empty?
  options[:namespace] = nil
end

if options[:namespace].nil?
  $stderr.puts "** skipping registration"
  $stderr.puts "   Provide a namespace (-n <namespace>) to generate registration file"
end

#
# parse genprovider arguments
#

options[:style] ||= :cim
options[:includes] ||= []
options[:includes].unshift(Pathname.new ".")
options[:includes].unshift(Pathname.new CIM_CURRENT)
options[:testcase] = generate_test

#
# Extend include pathes with all directories below CIM_CURRENT
#

Dir.new(CIM_CURRENT).each do |d|
  next if d[0,1] == "."
  fullname = File.join(CIM_CURRENT, d)
  next unless File.stat(fullname).directory?
  options[:includes].unshift(Pathname.new fullname)
end

#
# Ensure that qualifiers.mof is included
#

moffiles.unshift "qualifiers.mof" unless moffiles.include? "qualifiers.mof"
qualifiers_optional = "qualifiers_optional.mof"
unless moffiles.include? qualifiers_optional
  moffiles.unshift qualifiers_optional if File.readable?(File.join(CIM_CURRENT, qualifiers_optional))
end

####
## parse

#
# Parse all given mof files
#

parser = MOF::Parser.new options
begin
  result = parser.parse moffiles
rescue Exception => e
  parser.error_handler e
  exit 1
end

exit 0 unless result

#
# collect classes to find parent classes
#

# classes: Hash of { classname -> CIM::Class }
classes = {}
# to_generate: CIM::Class of mof files given as ARGV
to_generate = []
result.each do |name, res|
  puts "Result #{name}"
  res.classes.each do |c|
    to_generate << c
    puts "  Generate #{c.name}"
    # complete classes hash (happens if a .mof file defines multiple classes)
    classes[c.name] = c unless classes.has_key? c.name
  end
end

#
# Iterate through parsed classes to find their superclasses
#

superclasses = {}
classes.each_value do |c|
  puts "#{c.name} << #{c.superclass}"
  next unless c.superclass # skip if no superclass
  next if classes.has_key? c.superclass # skip if superclass known
  next if superclasses.has_key? c.superclass # skip if superclass known
  superclasses.merge! find_and_parse_superclass_of(c,options)
end

# extend the { classname -> CIM::Class } hash with all superclasses

classes.merge! superclasses

####
## generate

outdir = options[:output] || "generated"
Dir.mkdir outdir rescue Errno::EEXIST

to_generate.each do |c|
  if options[:testcase]
    Genprovider::Output.new File.join(outdir,"test_#{c.name.decamelize}.rb"), options[:force] do |out|
      Genprovider::Testcase.new c, options[:namespace]||"root/cimv2", out
    end
    next
  end

  providerprop = c.qualifiers["provider", :string]
  providername = (providerprop ? providerprop.value.sub("cmpi:","") : c.name)
  Genprovider::Output.new File.join(outdir,"#{providername.decamelize}.rb"), options[:force] do |out|
    Genprovider::Provider.new c, providername, out
  end

  Genprovider::Output.new File.join(outdir,"#{providername}.rdoc"), options[:force] do |out|
    Genprovider::RDoc.new c, out
  end

  if options[:namespace]
    Genprovider::Output.new File.join(outdir,"#{c.name}.registration"), options[:force] do |out|
      Genprovider::Registration.new c, options[:namespace], providername, out
    end
  end
  
end
