#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright (c) 2007 Stas Zykiewicz <stas.zytkiewicz@gmail.com>
#
#           pyassetmlcreator.py
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 3 of the GNU General Public License
# as published by the Free Software Foundation.  A copy of this license should
# be included in the file GPL-3.
#
# 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 Library 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.

####### Howto use this script ##############################################
# First you must have a directory containing content of some sort.
# Then you run this script with the path to the directory you should
# use some of the options to fill in some of the assetml nodes.
# (See the usage function for possible options)
# 
# Now you have a file called 'assetml.raw' inside this directory.
# Once you've edit that file by giving the descriptions in English you
# must rename the file assetml.raw to <dir name>.assetml
# you should run this script again this time with the path to the directory
# but no options..
#
# Now a directory called 'po' is created with inside a gettext like file
# called 'Translation.raw'. This file holds the translation strings from
# the assetml file.
# This po file is a normal gettext po file.
# Now when you have translated the Translation.raw file and saved it as
# <locale>.po you should again run this script to add the translation to
# the assetml file.
# Make sure your pofiles are located in the subdirectory 'po'
# Example of pofile naming: A Dutch file would be called nl.po and a French
# one, fr.po etc.
#
# Things to consider:
# NEVER RUN THIS SCRIPT AS ROOT
# This script needs some additional files but should NOT be run from
# a installed childsplay source tree.
# The best way would be to run it from inside the CVS tree.
# 
# Alternatively you could send your stuff to me and I will create the 
# neccesary file for you.
# 
# You can contact me at stas.zytkiewicz@gmail.com
#
#############################################################################

_VERSION = "0.2.2"
_DEBUG = 0
_TALK = 1


PO_HEADER = \
"""
# This is a 'po' file to be used by pyassetml-creator.
# I've added the po header from pygettext.
# Please also fill in the fields in the header if you do any
# translation work.

# After translating save/rename this file as
# (for example) 'nl.po' or 'de.po' only do 'pt_BR.po' instead of
# 'pt.po' if it REALLY nessecary for the translation.

# SOME DESCRIPTIVE TITLE.
# Copyright (C) 2003 StasZ
# FIRST AUTHOR <assetml@linux.isbeter.nl>, 2002.
#
msgid ""
msgstr ""
"Project-Id-Version: 0.1\\n"
"POT-Creation-Date: \\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"
"Language-Team: <assetml@linux.isbeter.nl>\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=CHARSET\\n"
"Content-Transfer-Encoding: ENCODING\\n"
"Generated-By: pyassetmlcreator.py from %s \\n"

"""

def usage():
    return \
    """
   pyassetml-creator creates a assetml file from a directory contents.
   See http://www.ofset.org/assetml  for info.
   
   XXX Options -u -C -D -R not implemented yet
      
   Usage: pyassetml-creator.py [OPTIONS] FILE
    
   Options:
    -m STRING Set mimetype attribute.
    -l STRING Set locale attribute.
    -d STRING Set dataset attribute.
    -r STRING Set rootdir attribute. Default is ../
    -c STRING Set the credits element data. This becomes the same for
              all the credits elements.
    -a STRING Set the category element data
    -o FILE   Pipe the assetml to FILE, default is ./assetml.raw.
              when "-" is given, stdout is used. This option is only
              used the first time the script is run, after that the original
              file name is used in all file operations.
    -u        Update a existing assetml package. Use this after you added extra
              content to a package.
    -q        Be quiet, only errors will be send to stderr.
    -h        Prints a usage message.
    -C FILE   Use configuration file FILE.
    -D        Make a debian package.
    -R        Make a rpm package.
   
     FILE     The directory to create a assetml package from.
              This is mandatory.
   
   For example the command:
   pyassetml-creator.py -m "image/png" -d "flags" -D -R /home/stas/images

   Results that from the contents of the directory ./images a assetml file is
   created with the mimetype and dataset attributes set. the assetml file is put
   to stdout.
   There will be also two packages created: a debian and a rpm.
   
"""

import sys
class PyassetmlError(Exception):
    pass
 
try:
    import pyassetml
except ImportError,info:
    print >> sys.stderr, "pyassetmlcreator depends on pyassetml.py\n\
            Make sure it's in your Pythonpath or in the same directory\n\
            as pyassetmlcreator.py\n", info
    raise PyassetmlError

import os,getopt,glob,string,pprint,gettext,locale

class Options:
    """ Used to store default options.
     These options are can be set by commendline options.
     """
    output = "assetml.raw"     # -o
    mimetype = ""        # -m
    locale = "en"    # -l .
    dataset = ""     # -d .
    rootdir = "../" # -r.
    credits = "" # -c 
    category = ""  # -a 
    update = None
    rc = ""  # -C
    deb  = "" # -D.
    rpm = ""  # -R
   

class Creator:
    """ Class to generate assetml files from a directory contents.
      This class constructor takes a directory path and tries to
      create a assetml file out of it.It also tries to generate a
      description file if it doesn't exists.
      The raw assetml stays part of the instance, and you call methods
      form this instance to do things with the assetml source.
      See the methods docstrings for more information"""
    def __init__(self, dirpath,options):
        """ dirpath = directory path with assetml contents.
          options = class instance Options
            """
        # TODO XXX when update is true -> load assetml and update assetml and po files??
        
        if _TALK: print "\n============================================================\n",\
                " Start Assetml creator version:", _VERSION,\
                    "\n"," Trying to 'assetmlify' content directory:","\n"," ",dirpath
        if options.rc:# try to parse rc file
            rc = self._parse_rc(options.rc)#returns a None when it fails
            if not rc:
                print >> sys.stderr, "Failed to parse configfile:\n",options.rc
            else:
                options = rc
        self.options = options
        self.dirpath = dirpath
        self.xml_elements = {'root':"""<?xml version="1.0" encoding="UTF-8"?>\n""",\
                            'doc':"""<AssetML dataset="%(dataset)s" rootdir="%(rootdir)s" locale="%(locale)s">\n""",\
                            'asset':"""  <Asset file="%(file)s" mimetype="%(mimetype)s">\n    <Description>%(descr)s</Description>\n    <Credits>%(credits)s</Credits>\n    <Categories>%(category)s</Categories>\n  </Asset>\n""",\
                            'endml':"""</AssetML>\n"""}
                
        # used for dict. based string formatting
        self.xml_data = {'dataset':"",'rootdir':"",'locale':"",'file':"",'mimetype':"",\
                        'descr':"",'credits':"",'category':""}
        
        
    def start(self):
        # Update attribute dict with possible commandline args.
        for k in self.xml_data.keys():
            if hasattr(self.options, k):
                self.xml_data[k] = getattr(self.options, k)
        if _DEBUG: print "Updated options dict",self.xml_data   
        self.xml_source = []
                
        # Look for (a) po file(s), and store them in a list
        self.pofiles = glob.glob(os.path.join(self.dirpath, 'po', '*.po'))
        if _DEBUG: print "Result of pofile search",self.pofiles
        if _TALK: 
            print "\n Looking for po files..."
            pprint.pprint(self.pofiles)
        # look for a edited assetml file, put the descriptions in.
        # This file should be renamed to foo.assetml
        self.assetml_path = glob.glob(os.path.join(self.dirpath, '*.assetml'))
        if _TALK: print "\n Looking for a assetml file...\n", self.assetml_path
        if self.assetml_path:
            self.assetml_path = self.assetml_path[0]
            if _TALK: print " Found, using assetml file:\n",self.assetml_path
            if _DEBUG: print "\n Result of assetml file search\n",self.assetml_path
        else:# we create a new assetml file
            if _TALK: print " Not found, creating new assetml file -> assetml.raw"
            # Have we any content ?
            if _TALK: print "\n Looking for content..."
            dir_content = glob.glob(os.path.join(self.dirpath, '*'))
            raw_content,content = None,None
            if dir_content:
                # Filter the found dir contents
                raw_content = filter(os.path.isfile, dir_content)
                if raw_content:
                    content = map(os.path.basename, raw_content)
                    if content:
                        self.content = self._filter_content(content)
                        if _TALK:
                            print " Found content :\n"
                            print self.content
                        self.generate_assetml()# this saves it to disk or to stdout
            else:
                print >> sys.stderr, "\nCan\'t find any files to generate a assetml file from\n",\
                                "The directory i was looking in was:",self.dirpath
                sys.exit(1)
 
        # Have we po files?
        if _DEBUG >= 2: print "po files?",self.pofiles
        if self.pofiles: # Go to a 'insert description method' XXX
            self._insert_lang()
        elif self.assetml_path: # make a po file out of the assetml descriptions.
            stat = self.generate_pofile()
            if _DEBUG >= 2: print "Status of po file creation",stat
            if stat: #None is succes, anything else signals faillure
                print >> sys.stderr, "Trouble writing translation file:\n",stat
               
        if _DEBUG >= 2: print "Filtered content",self.content
        
        # We end up with a list with filenames without a path -> self.content
        # and a list of po files -> self.pofiles OR a ./po/translation.raw on disk
                            
    def _filter_content(self,content):
        """ Remove certain names from the content list, return
         a new list"""
        craplist = ['pyassetml-creator.py','README','assetml.raw']
        for item in craplist:
            try:
                content.remove(item)
            except ValueError:
                pass
        return content
    
    def _parse_rc(self,rcfile):
        """ Parse a configfile, uses _parse_file to put it in a dict,
          then put it in a options class instance and return it"""
        dict = self._parse_file(rcfile)
        if not dict:
            return None
        opt = Options()
        for k in dict:
            if hasattr(opt, k):
                setattr(opt, k, dict[k])
        return opt        
        
    def _parse_file(self,filepath):
        """ Split the contents of a creator file in dictionary
         keys and values. keys are the filenames (left of =),
         values are the descriptions (right of =)
         Surrounding whitespace is stripped."""
        try:
            f = open(filepath,'r')
            stuff = f.readlines()
            f.close()
        except OSError,info:
            print >> sys.stderr, info
            return {}
        dict = {}
        newstuff = []
        pieces = map(string.split, stuff, '='*len(stuff))
        for piece in pieces:
            if len(piece) == 2:
                piece[1] = piece[1][:-1]# loose \n
                if piece[1].strip() != '' :
                    piece[1] = piece[1].strip()
                else:
                    piece[1] = piece[1].strip()
                piece[0] = piece[0].strip()
                newstuff.append(piece)
        if _DEBUG >= 2: print "newstuff in parse_file",newstuff
        if not newstuff:
            return None
        for key, value in newstuff:
            dict[key] = '%s' % value
        if _DEBUG >= 2: print "parse_file dict ",dict
        return dict
    
    def _parse_po_file(self,filepath):
        """ Split the contents of a po file in dictionary
         keys and values. keys are the msgid, values are the msgstr
         """
        try:
            f = open(filepath,'r')
            stuff = f.readlines()
            f.close()
        except OSError,info:
            print >> sys.stderr, info
            return {}
        if _DEBUG: print "po file contents",stuff
        dict = {}
        i = 0
        for line in stuff:
            i += 1
            if line.find('msgid') != -1:
                msgid = line.split('"',2)
                msgstr = stuff[i].split('"',2)
                #if msgid[1] and msgstr[1]:# there's something between the ""
                if _DEBUG: print "msgid",msgid,"msgstr",msgstr
                if msgid[1]:
                    dict[msgid[1]] = msgstr[1]
        if _DEBUG: print "po dict",dict
        return dict
         
        
    def _insert_lang(self):
        """ Insert i18n descriptions nodes in a assetml file.
          return None on succes, or text on faillure
         """        
        if _DEBUG: print "insert_lang, assetml path",self.assetml_path
        if not self.assetml_path:
            return "Can't find assetml file"
        if _TALK: print "Tying to add the translations to the assetml file:\n" ,self.assetml_path 
        try:
            f = open(self.assetml_path,"r")
            assetml_source = f.readlines()
            f.close()
        except OSError,info:
            return info
        # parse assetml and get description names
        try:
            parser = pyassetml.AssetmlParser(self.assetml_path)
        except RuntimeError,info:
            print >> sys.stderr, "Error inserting i18n descriptions\n", info
            return info
        items = (('description',pyassetml.DEFAULTLOCALE))
        names = parser.find_names([items])
        if not names:
            return "No descriptions found in assetml file"
        podicts = []
        # Convert po files in dictonairies
        for file in self.pofiles:
            loc = os.path.basename(file)[:-3]
            # dict = {'Number one':Nummer een','Number two':'Nummer twee'}
            dict = self._parse_po_file(file)
            dict["xml:lang"] = loc
            podicts.append(dict)
        # Here we start inserting description stuff
        if _DEBUG: print "podicts",podicts
        first = "<Description>"
        last = "</Description>"
        new_source = []
        new_line ="""     <Description xml:lang="%s">%s</Description>\n"""
        for line in assetml_source:
            if line.find(first) != -1:
                new_source.append(line)
                left,right = line.split(first,1)
                name,spam = right.split(last,1)
                name = name.strip()
                if name:
                    for dict in podicts:
                        found = parser.find_names((('description',name),('description',dict['xml:lang'])))
                        if found:
                            if  _DEBUG >= 1: print "already have:",found,"lang:",dict["xml:lang"]
                            continue
                        try:
                            new = new_line % (dict['xml:lang'], dict[name])
                        except KeyError,info:
                            if _DEBUG: print "inserting i18n error",info,name
                            new = ''
                        else:
                            new_source.append(new)
            else:
                new_source.append(line)
        assetml_source = filter(None,new_source)
        if self.options.output == "-":
            print >> sys.stdout, assetml_source
        else:
            f = os.path.join(self.dirpath, os.path.basename(self.assetml_path))
            self._save_file(f, assetml_source)
        
    def generate_assetml(self):
        """ self.xml_data is a dictionary where the keys are the names of the
         attributes and data from the asset xml. The keys of the dictionary
         are: dataset, rootdir, locale, mimetype, descr, credits, category.
         The key "file" is updated dynamicly during generation.
         It defaults to a empty dictionary and returns None
         """
        if _DEBUG: print "generate xml, xml_data",self.xml_data
        self.xml_source.append(self.xml_elements['root'])
        self.xml_source.append(self.xml_elements['doc'] % self.xml_data)      
        # Here's all the replacement taken place in the asset element
        # by means of dictionary based formatting (see the constructor)
        for file in self.content:
            self.xml_data['file'] = file
            self.xml_source.append(self.xml_elements['asset'] % self.xml_data)
        self.xml_source.append(self.xml_elements['endml'])
        
        if self.options.output == "-":
            print >> sys.stdout, self.xml_source
        else:
            f = os.path.join(self.dirpath,self.options.output)
            self._save_file(f, self.xml_source)
    
    def generate_pofile(self, assetmlpath=None):
        """ Generate a po file from the assetml descriptions.
          Or any other assetml file.
          Return None on succes, error messages on faillure.
        """
        if not self.assetml_path and not assetmlpath:
            return "No assetml file found to create a translation file\n"
        path = 1 and self.assetml_path or assetmlpath
        if _DEBUG: print "assetml path for pot file creation",path
        popath = os.path.join(self.dirpath, 'po')
        if _TALK: print "\n Generating a pot file from the assetml descriptions..."
        if not os.path.exists(popath):
            try:
                os.mkdir(popath)
            except OSError,info:
                print >> sys.stderr,info
                return "Can\'t make directory %s\n" % popath
        # parse assetml and get description names
        try:
            parser = pyassetml.AssetmlParser(path)
        except RuntimeError,info:
            print >> sys.stderr, "Error creating pot file\n", info
            return info
        
        items = (('description',pyassetml.DEFAULTLOCALE))
        l = parser.find_names([items])
        if _DEBUG: print "Found names",l
        if l:
            mlist = map(''.join,map(None, ('msgid "',)*len(l),l,('"',)*len(l)))
            polist = []
            for item in mlist:
                polist.append(item+'\n')
                polist.append('msgstr ""\n\n')
            polist.insert(0,PO_HEADER % self.dirpath)
            if _DEBUG: print "polist ",polist
            ## XXX get the name of the assetml file + .pot
            ## XXX line numbering like regular pot files
            f = os.path.join(popath,'Translation.raw')
            self._save_file(f, polist)
            if _TALK:
                print " The po file is called: ",'Translation.raw' 
                print " Done, po file placed in:\n",popath
            return None
        else:
            if _TALK: print "No descriptions found to make a pot file from" 
            return "No descriptions found to make a pot file"
         
        
    def _save_file(self, filepath, stringlist):
        try:
            f= open(filepath,'w')
            f.writelines(stringlist)
            f.close()
        except (IOError,TypeError),info:
            print >> sys.stderr, "Trouble writing file to disk:\n",info
            return info
        return None


def main(args):
    
    if len(sys.argv) < 2:
        print usage()
        sys.exit(1)
    try:
        o, a = getopt.getopt(args, 'm:l:d:r:c:a:C:hDR')
    except getopt.GetoptError,info:
        print info,'\n', usage()
        sys.exit(1)
    if _DEBUG >= 3: print "Sys.argv options",o,"\nSys.argv arguments",a  
    options = Options()
    opts = {}
    for k, v in o:
        opts[k] = v
    if opts.has_key('-h'):
        print usage()
        sys.exit(0)

    if opts.has_key('-C'):
        # Use config file, skip everything else
        options.rc = opts['-C']
    else:
        if opts.has_key('-m'):
            # mimetype = opts['-m']
            # set mimetype
            options.mimetype = opts['-m']
        if opts.has_key('-l'):
            # set locale
            options.locale = opts['-l']
        if opts.has_key('-d'):
            # set dataset
            options.dataset = opts['-d']
        if opts.has_key('-r'):
            # set rootdir
            options.rootdir = opts['-r']
        if opts.has_key('-c'):
            # set credits
            options.credits = opts['-c']
        if opts.has_key('-a'):
            # set category
            options.category = opts['-a']
        if opts.has_key('-u'):
            # update package
            options.update = 1
        if opts.has_key('-q'):
            # be quiet
            _TALK = 0
        if opts.has_key('-D'):
            # make debian package
            options.deb = "TRUE"
        if opts.has_key('-R'):
            # make rpm
            options.rpm = "TRUE"
        if a and os.path.exists(a[0]):
            dirpath = a[0]
        else:
            print "You must provide a path to the content directory"
            sys.exit(1)
    
    if _DEBUG:
        print "Options",options.__dict__
        print "Arguments",a
        
    c = Creator(dirpath,options)
    c.start()
    
    
if __name__ == "__main__":
    _TALK = 1
    print __doc__
    main(sys.argv[1:])
