#!/usr/bin/ruby

###
### kwartz -- a template system which realized the concept of
###    'Separation of Presentation Logic from Presentation Data'(SoPL/PD).
###
### Copyright (C) 2004-2005 kuwata-lab
### All rights reserved.
### author::  kwa(at)kuwata-lab.com
### id::      $Id: kwartz-ruby 52 2005-03-06 23:15:41Z kwatch $
###
### This project is subsidized by Exploratory Software Project of IPA
### (Information-Technology Promotion Agency Japan).
### See http://www.ipa.go.jp/about/english/index.html for IPA.
###
### This program is free software; you can redistribute it and/or
### modify it under the terms of the GNU General Public License
### as published by the Free Software Foundation; either version 2
### of the License, or (at your option) any later version.
###
### This program is distributed in the hope that it will be useful,
### but WITHOUT ANY WARRANTY; without even the implied warranty of
### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
### GNU General Public License for more details.
###
### You should have received a copy of the GNU General Public License
### along with this program; if not, write to the Free Software
### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
###

require 'kwartz'
require 'kwartz/translator/eruby'
require 'kwartz/translator/erb'
require 'kwartz/translator/php'
require 'kwartz/translator/jstl'
require 'kwartz/util/optparse'

module Kwartz

   class CommandOptionError < KwartzError
      def initialize(msg)
         super(msg)
      end
   end

   class MainCommand

      VERSION     = ('$Rev: 52 $' =~ /\d+(?:\.\d+)*/ && $&)
      LAST_UPDATE = ('$Date: 2005-03-07 08:15:41 +0900 (Mon, 07 Mar 2005) $' =~ /\d+[-\/]\d+[-\/]\d+/ && $&)
      RELEASE     = ('$Release: 2.0.0-beta3$' =~ /Release: (.*)\$/ && $1)

      def initialize(argv=ARGV)
         @argv = argv
         @command = File::basename($0)
      end

      @@actions = {
         'scan'          => :scan,
         'convert'       => :convert,
         'parse'         => :parse_program,
         'parse-program' => :parse_program,
         'parse_program' => :parse_program,
         'parse-plogic'  => :parse_plogic,
         'parse_plogic ' => :parse_plogic,
         'expand'        => :expand,
         'translate'     => :translate,
         'compile'       => :compile,
         'analyze'       => :analyze,
      }

      def main()
         ## parse command-line options
         begin
            options, properties = Kwartz::Util::optparse(@argv, "hves", "alpi", nil, true)
         rescue Kwartz::Util::OptparseError => ex
            case ex.type
            when :invalid_option
               msg = "-#{ex.optchar.chr}: invalid option."
            when :invalid_property
               msg = "--#{ex.propstr.to_s}: invalid property."
            when :argument_required
               case ex.optchar
               when ?a
                  msg = "-#{ex.optchar.chr}: action required."
               when ?l
                  msg = "-#{ex.optchar.chr}: lang requried."
               when ?p
                  msg = "-#{ex.optchar.chr}: presentation logic file requried."
               when ?i
                  msg = "-#{ex.optchar.chr}: contents data file requried."
               else
                  Kwartz::assert(false, "ex.optchar=#{ex.optchar}")
               end
            end
            raise CommandOptionError.new(msg)
         end

         ## help or version
         if options[?h] || options[?v]
            #$stderr.puts "kwartz: #{RELEASE} (rev.#{VERSION}), kwartz.rb: #{Kwartz::RELEASE} (rev.#{Kwartz::VERSION})" if options[?v]
            if options[?v]
               if RELEASE == Kwartz::RELEASE
                  $stderr.puts "kwartz-ruby: #{RELEASE}"
               else
                  $stderr.puts "kwartz-ruby: #{RELEASE}, kwartz.rb: #{Kwartz::RELEASE}"
               end
            end
            $stderr.puts usage() if options[?h]
            return 0
         end

         ## action
         action = @@actions[ options[?a] || 'compile' ]
         unless action
            raise CommandOptionError.new("-a #{options[?a]}: invalid action.")
         end

         ## escape(sanitize)
         if properties.key?(:escape)
            # nothing
         elsif options.key?(?s)
            properties[:escape] = options[?s]
         elsif options.key?(?e)
            properties[:escape] = options[?e]
         end

         ## properties which requires a list as value
         [ :noend, :incdirs ].each do |key|
            next unless properties.key?(key)
            properties[key] = properties[key].split(/,/)
         end

         ## target language
         lang = (options[?l] || Kwartz::Config::LANG)

         ## add JSP directives
         output = ''
         charset = properties[:charset] || Kwartz::Config::CHARSET
         case lang
         when 'jstl', 'jstl11'
            output << Kwartz::Config::HEADER_JSTL11 if !properties.key?(:header)
            output << Kwartz::Config::HEADER_JSP_CHARSET.gsub(/__CHARSET__/, charset) if charset
         when 'jstl10'
            output << Kwartz::Config::HEADER_JSTL10 if !properties.key?(:header)
            output << Kwartz::Config::HEADER_JSP_CHARSET.gsub(/__CHARSET__/, charset) if charset
         end

         ## add header
         properties[:header] = '' if properties[:header] == true
         output << properties[:header] if properties[:header]

         ## parse presentation logic file
         compiler = Kwartz::Compiler.new(properties)
         plogic_filenames = options[?p] ? options[?p].split(/,/) : []
         element_decl_list = []
         plogic_filenames.each do |filename|
            unless test(?f, filename)
               raise CommandOptionError.new("'#{filename}': not found.")
            end
            File.open(filename) do |file|
               plogic_str = file.read()
               list = compiler.parse_plogic(plogic_str, filename)
               element_decl_list.concat(list) if list
            end
         end
         
         ## parse contens data file
         contents_filenames = options[?i] ? options[?i].split(/,/) : []
         element_list = []
         contents_filenames.each do |filename|
            unless test(?f, filename)
               raise CommandOptionError.new("'#{filename}': not found.")
            end
            File.open(filename) do |file|
               pdata_str = file.read()
               block_stmt, elem_list = compiler.convert(pdata_str, filename)
               element_list.concat(elem_list) if elem_list
            end
         end

         ## perform action and get output
         if @argv.empty?
            input = ARGF.read()
            filename = ARGF.filename
            output << perform(action, compiler, input, filename, element_list, element_decl_list, lang)
         else
            @argv.each do |filename|
               unless test(?f, filename)
                  raise CommandOptionError.new("'#{filename}': not found.")
               end
               File.open(filename) do |file|
                  input = file.read()
                  output << perform(action, compiler, input, filename, element_list, element_decl_list, lang)
               end
            end
         end

         ## return output
         output << properties[:header] if properties[:header]
         return output
      end

      private

      def usage()
         s = <<END
Usage: #{@command} [..options..] file1 [file2...]
  -h, --help : help
  -v         : version
  -a action  : compile/parse/translate/convert/analyze (default 'compile')
  -l lang    : eruby/erb/php/jstl10/jstl11 (default '#{Kwartz::Config::LANG}')
  -s, -e     : sanitize(escape) (equvarent to --escape=true)
  -p file1,file2,...  : presentation logic filenames
  -i file1,file2,...  : import element from outer files
  --escape[=true] : sanitize(escape)
  --rename[=true] : rename local variables (ex. rename 'var' to '_var')
  --charset=name  : add '<%@ page contentType="text/html; charset=name" %>'
  --odd=value     : odd  value in FOREACH or LIST (default "#{Kwartz::Config::ODD}")
  --even=value    : even value in FOREACH or LIST (default "#{Kwartz::Config::EVEN}")
  --dattr=name    : use attribute name as directive (default "#{Kwartz::Config::DATTR}")
  --header=text   : header text
  --footer=text   : footer text
  --extract=name  : extract an marked element
  --include-dirs=dir1,dir2,...  : directories for 'include:' directive
END
         return s
      end

      def perform(action, compiler, input, filename, element_list, decl_list, lang)
         case action
         when :scan
            scanner = Scanner.new(input)
            output = scanner.scan_all()
         when :convert
            pdata_str = input
            block_stmt, element_list = compiler.convert(pdata_str, filename)
            output = block_stmt._inspect()
         when :parse_plogic
            plogic_str = input
            decl_list = compiler.parse_plogic(plogic_str, filename)
            output = decl_list.collect { |decl| decl._inspect() }.join
         when :parse_program
            program_str = input
            block_stmt = compiler.parse_program(program_str, filename)
            output = block_stmt._inspect()
         when :expand
            pdata_str = input
            block_stmt, element_list = compiler.convert(pdata_str, filename)
            element_table = compiler.merge(element_list, decl_list)
            compiler.expand(block_stmt, element_table)
            output = block_stmt._inspect()
         when :translate
            program_str = input
            block_stmt = compiler.parse_program(program_str, filename)
            code = compiler.translate(block_stmt, lang)
            output = code
         when :compile, :analyze
            pdata_str = input
            block_stmt, elem_list = compiler.convert(pdata_str, filename)
            element_list.concat(elem_list) if elem_list
            element_table = compiler.merge(element_list, decl_list)
            compiler.handle_doc_decl(block_stmt, decl_list)
            if elem_name = compiler.properties[:extract]
               block_stmt = compiler.parse_program("@element(#{elem_name});")
            end
            compiler.expand(block_stmt, element_table)
            if action == :compile
               code = compiler.translate(block_stmt, lang)
               output = code
            elsif action == :analyze
               analyzer = Kwartz::Analyzer.create('scope', compiler.properties)
               analyzer.analyze(block_stmt)
               output = analyzer.result()
            else
               Kwartz::assert(false)
            end
         else
            raise CommandOptionError.new("#{action}: not supported yet.")
         end
         return output
      end

   end

end



#if __FILE__ == $0
unless defined?(KWARTZ_NOEXEC)
   begin
      main_command = Kwartz::MainCommand.new(ARGV)
      output = main_command.main()
      print output
   rescue Kwartz::KwartzError => ex
      $stderr.puts ex.message
      exit 1
   end
end
