#!/usr/bin/python

# zipem.py
#       --copyright--                   Copyright 2008 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       August 23, 2008         bar
#       April 4, 2010           bar     --np option
#       April 7, 2010           bar     file renaming ability  file_name=zip_file_name
#       October 1, 2010         bar     put the logic in a routine
#       October 4, 2011         bar     --sub_dirs
#       November 29, 2011       bar     pyflake cleanup
#       March 15, 2012          bar     date/time the zip file to now if there are no files to get the latest date from
#                                       able to take    ambiguous_file = dir_name
#                                       be clever about the permissions of a text "file"
#       March 16, 2012          bar     a_text
#       April 19, 2012          bar     put a time in for text
#       May 27, 2012            bar     doxygen namespace
#       June 28, 2012           bar     explicitely use time.time for obscure reasons (it can be monkey-patched)
#       January 11, 2013        bar     update_zip_file()
#       January 28, 2013        bar     hey - typo in update_zip_file()
#       March 11, 2013          bar     by default, make .pyc "text" executable 0755
#       June 20, 2013           bar     try to extricate us from the wrong-direction = by also doing < and > in the file rename method
#       August 10, 2014         bar     string  txt_to      file
#                                       file    from_txt    string
#       February 28, 2016       bar     do not whack args
#       --eodstamps--
##      \file
#       \namespace              tzpython.zipem
#
#
#       Zip up some files to the first file given in the command line
#
#

import  os
import  re
import  time
import  zipfile

import  tzlib
import  replace_file


class   a_text(object) :
    def __init__(me, text, permissions = 0600) :
        me.text         = text                             or ""
        me.permissions  = ((permissions is None) and 0600) or permissions
    #   a_text



def zipem(zfname, args, inc_dirs = True, sub_dirs = False, text_by_file_name = {}) :
    if  isinstance(args, basestring) :
        args    = [ args ]                                                  # only 1 file name to put in to the zip file
    args        = list(args)

    if  text_by_file_name is None :
        text_by_file_name   = {}

    tfn     = zfname + ".tmp"
    if  os.path.isfile(tfn) :
        os.remove(tfn)

    zf      = zipfile.ZipFile(tfn, "w", zipfile.ZIP_DEFLATED)
    d       = time.time()

    while args :
        afn = args.pop(0)

        fns = tzlib.ambiguous_file_list(afn, do_sub_dirs = sub_dirs)
        if  not fns :

            nms     = re.split(r"\s*=\s*", afn)                             # see if they want the file renamed:    local_name = unzip_name     Unfortunately. Sigh. Just change everybody who uses this facility and fix this ordering. !!!!
            if  len(nms) != 2 :
                nms = re.split(r"\s*->\s*", afn)                            # see if they want the file renamed:    local_name -> unzip_name
            if  len(nms) != 2 :
                nms = re.split(r"\s*<-\s*", afn)                            # see if they want the file renamed:    unzip_name <- local_name
                nms.reverse()
            if  len(nms) != 2 :
                nms = re.split(r"\s*>\s*", afn)                             # see if they want the file renamed:    local_name > unzip_name
            if  len(nms) != 2 :
                nms = re.split(r"\s*<\s*", afn)                             # see if they want the file renamed:    unzip_name < local_name
                nms.reverse()
            if  len(nms) != 2 :
                nms = re.split(r"\s+txt_to\s+", afn)                        # see if they want the file's text:    string       txt_to   unzip_name
                if  len(nms) == 2 :
                    text_by_file_name[nms[1]]   = eval('"""' + nms[0] + '"""')      # figure out a better way to get slashes and such in the cmd line string
                    continue
                pass
            if  len(nms) != 2 :
                nms = re.split(r"\s+from_txt\s+", afn)                      # see if they want the file's text:    unzip_name   from_txt  string
                if  len(nms) == 2 :
                    text_by_file_name[nms[0]]   = eval('"""' + nms[1] + '"""')
                    continue
                pass
            if  len(nms) != 2 :
                zf.close()
                os.remove(tfn)

                return(__file__ + " cannot find %s" % afn)

            fns = tzlib.ambiguous_file_list(nms[0], do_sub_dirs = sub_dirs)
            if  not len(fns) :
                zf.close()
                os.remove(tfn)

                return(__file__ + " cannot find to-be-renamed file %s!" % nms[0])

            for fn in fns :
                if  not os.path.isfile(fn) :
                    zf.close()
                    os.remove(tfn)

                    return(__file__ + " %s is not a file!" % fn)

                d   = max(d, os.path.getmtime(fn))
                zfn = nms[1]
                if  (len(fns) > 1) or zfn.endswith("\\") or zfn.endswith("/") :
                    zfn = zfn.strip("\\/")
                    ndn = os.path.expanduser(os.path.expandvars(nms[0]))
                    zfn = os.path.join(zfn, fn.replace(os.path.dirname(nms[0]), "").replace(os.path.dirname(ndn), "").strip("\\/"))
                # print "@@@@", fn, "-------", zfn, "-------", nms[1]

                zf.write(fn, zfn)
            pass

        else    :

            for fn in [ pfn for pfn in fns if os.path.isfile(pfn) ] :
                d   = max(d, os.path.getmtime(fn))
                fnz = fn
                if  not inc_dirs :
                    fnz = os.path.basename(fn)
                zf.write(fn, fnz)
            pass

        pass

    kys = text_by_file_name.keys()
    kys.sort()
    for fn in kys :
        txt                 = text_by_file_name[fn]

        perms               = None
        if  hasattr(txt, 'permissions') :
            perms           = txt.permissions
            txt             = txt.text

        try :
            txt             = txt.encode('utf8')
        except UnicodeError :
            pass

        info                = zipfile.ZipInfo(fn)
        info.compress_type  = zipfile.ZIP_DEFLATED

        if  perms is None   :
            ext             = os.path.splitext(fn)[1]
            if  ext.lower() in [ ".sh", ".csh", ".py", ".pyc", ".pl", ".pm", ".awk", ".bat", ".exe", ] :
                perms       = 0755
            else                    :
                perms       = a_text("").permissions
            pass
        info.external_attr  = (perms & 0xffff) << 16L

        tm                  = time.localtime(time.time())
        info.date_time      = ( tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec )

        zf.writestr(info, txt)
        d   = max(d, time.time())

    zf.close()

    os.utime(tfn, ( os.path.getatime(tfn), d ) )

    replace_file.replace_file(zfname, tfn, zfname + ".bak")

    return("")



def update_zip_file(dfn, file_name_in_zip, name_of_new_file, min_size_factor = None, max_size_factor = None) :
    """
        Update the given file in a ZIP file with the given file from disk.
        If 'name_of_new_file' is empty, just delete all instances of 'file_name_in_zip' in the ZIP file (returning False if there are none).
        Do not update if the new file is smaller than the current file times the 'min_size_factor'.
        Do not update if the new file is lareger than the current file times the 'max_size_factor'.
        Return True if the file is updated OK, False otherwise.
    """

    zf          = zipfile.ZipFile(dfn, "r", zipfile.ZIP_DEFLATED)
    try         :
        info    = zf.getinfo(file_name_in_zip)
    except KeyError :
        zf.close()
        return(False)

    if  name_of_new_file :
        sz  = os.path.getsize(name_of_new_file)
        if  (min_size_factor != None) and (sz < info.file_size * min_size_factor) :
            zf.close()

            return(False)

        if  (max_size_factor != None) and (sz > info.file_size * max_size_factor) :
            zf.close()

            return(False)

        pass


    tfn     = dfn + ".tmp"
    ozf     = zipfile.ZipFile(tfn, "w", zipfile.ZIP_DEFLATED)
    for f  in zf.infolist() :
        if  f.filename == file_name_in_zip :
            if  name_of_new_file    :
                ozf.write(name_of_new_file, file_name_in_zip)
                name_of_new_file    = ""                            # replace the first file, but whack all the rest
            pass
        else    :
            fd  = zf.read(f.filename)
            ozf.writestr(f, fd)
        pass
    ozf.close()
    zf.close()
    replace_file.replace_file(dfn, tfn, dfn + ".bak")

    return(True)



help_str    = """
%s new_zip_file (options) (ambiguous_file_name|file_name=name_for_file_in_zip_file)...

Options:

    --np                        Don't include directory paths with the file names in the ZIP file.
    --sub_dirs                  Include matching files in subdirectories.
    --text  file_name text      Put the given text in under the given file name.

ZIP up some files.
The files to ZIP may be renamed when put in the ZIP file. E.g.:
    "file_name = what_to_name_the_file_in_the_ZIP_file"
    If the option is in double-quotes, the equal sign may be surrounded by whitespace.
The ZIP file will be date/time stamped to the newest file's date/time.
"""

#
#
if __name__ == '__main__' :

    import  sys

    import  TZCommandLineAtFile


    program_name    = sys.argv.pop(0)

    TZCommandLineAtFile.expand_at_sign_command_line_files(sys.argv)

    inc_dirs        = True                  # this should be flipped, but I don't know where this script may be called from
    sub_dirs        = False
    text            = {}

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--help", "-h", "-?", "/?", "/h", "/H" ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        print help_str % ( os.path.basename(program_name) )

        sys.exit(254)


    while True :
        oi  = tzlib.array_find(sys.argv, [ "--np", "-np", "--nd", "-nd", ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        inc_dirs    = False                 # don't put directory paths in the ZIP file

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--text", "--string", "--str", "-t", ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        fn          = sys.argv.pop(oi)
        text[fn]    = sys.argv.pop(oi)

    while True :
        oi  = tzlib.array_find(sys.argv, [ "--sub_dirs", "--subdirs", "--sub-dirs", "--sub_dir", "--subdir", "--sub-dir", "--sd", "-s", ] )
        if  oi < 0 :    break
        del sys.argv[oi]
        sub_dirs    = True                  # search sub-directories


    zfname  = sys.argv.pop(0)
    err     = zipem(zfname, sys.argv, inc_dirs = inc_dirs, sub_dirs = sub_dirs, text_by_file_name = text)
    if  err :
        print >>sys.stderr, err
        sys.exit(101)

    pass

#
#
#
# eof
