#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# "Incomplete" module to create `ar` (archive) format files.
#
# Author: follower@rancidbacon.com
#
# Date: 13 September 2006
#
# License: GPL 2.0
#
# Note:
#
#  * This has been written based on the format described in the AR(5) man page.
#
#  * This module does not fully implement the file format as described--there
#    is no support for file names longer than 16 characters--nor is there
#    any error checking that this does not occur.
#
#  * There is no functionality for reading `ar` format files--only writing for
#    them.
#
#  * The motivation for writing this was for creating a pure-Python way
#    of constructing ".deb" files--which is an archive file with 3 specific
#    files in it--thus a limited functionality requirement.
#
import os

MAGIC_STRING = "!<arch>\n"

def paddedString(text, length):
    """
    Return a string with (possibly no) trailing spaces so the total
    length of the string is `length` characters.
    """
    return (text + length * " ")[:length]

NAME_BUFFER_LENGTH = 16
MOD_TIME_BUFFER_LENGTH = 12
ID_BUFFER_LENGTH = 6
FILE_MODE_BUFFER_LENGTH = 8
FILE_SIZE_BUFFER_LENGTH = 10

NO_PADDING_CHAR = ""
PADDING_CHAR = "\n"

FILE_HEADER_TRAILER = "`\n"

class FileInfo(object):
    """
    Represents a single file in the archive.
    The name is a little of a misnomer as it also (currently) holds the
    file content.
    """

    def __init__(self,
                 name,
                 modificationTime,
                 userId, groupId,
                 fileMode,
                 fileSize,
                 data):
        """
        """
        self.name = name + "/"# TODO: Ensure ar-compatible
        self.modificationTime = int(modificationTime)
        self.userId = userId
        self.groupId = groupId
        self.fileMode = fileMode
        self.fileSize = fileSize
        self.data = data

    def _getHeader(self):
        """
        Returns the header for the file as documented in AR(5).
        """
        data = []
        data.append(paddedString(self.name, NAME_BUFFER_LENGTH))
        data.append(paddedString(str(self.modificationTime), MOD_TIME_BUFFER_LENGTH))
        data.append(paddedString(str(self.userId), ID_BUFFER_LENGTH))
        data.append(paddedString(str(self.groupId), ID_BUFFER_LENGTH))
        data.append(paddedString("%o" % self.fileMode, FILE_MODE_BUFFER_LENGTH))
        data.append(paddedString(str(self.fileSize), FILE_SIZE_BUFFER_LENGTH))
        data.append(FILE_HEADER_TRAILER)
        return "".join(data)

    header = property(_getHeader, doc="")

    def packed(self):
        """
        Returns the "packed" form suitable for appended to an `ar` file.

        The packing concatenates the header, data and--if
        necessary--trailing padding. The padding is required to obey
        the "Objects in the archive are always an even number of bytes
        long;" requirement of the file format.
        """
        return self.header + \
               self.data + \
               [NO_PADDING_CHAR, PADDING_CHAR][self.fileSize % 2] #Pad odd size
        
    @staticmethod
    def fromFilePath(filepath):
        """
        """
        statResult = os.stat(filepath)
        return FileInfo(name=os.path.basename(filepath),
                        modificationTime=statResult.st_mtime,
                        userId=statResult.st_uid,
                        groupId=statResult.st_gid,
                        fileMode=statResult.st_mode,
                        fileSize=statResult.st_size,
                        data=open(filepath, "rb").read()) # Hardly efficient!

    #fromFilePath = staticmethod(_fromFilePath, doc="Create a FileInfo instance from a file.")

class ArFile(object):
    """
    Represents a complete archive.
    Files can be added by appending the paths to the `files` list.
    """

    def __init__(self):
        """
        """
        self.files = [] # TODO: Append FileInfo objects instead?

    def packed(self):
        """
        Returns the "packed" form of the archive--suitable for writing
        to disc.
        """
        content = []
        content.append(MAGIC_STRING)
        for theFilePath in self.files:
            content.append(FileInfo.fromFilePath(theFilePath).packed())
        return "".join(content)

if __name__ == "__main__":
    import sys

    if len(sys.argv) < 2:
        print "Usage: %s  <filepath> [...]" % sys.argv[0]
        raise SystemExit

    #print FileInfo.fromFilePath(filepath).header
    #print FileInfo.fromFilePath(filepath).packed()

    archiveFile = ArFile()

    for theFilepath in sys.argv[1:]:
        archiveFile.files.append(theFilepath)

    sys.stdout.write(archiveFile.packed())
    
    
    
