#!/usr/bin/python
"""Make contents manifest for a directory tree.

Make .xo-files files for each directory, which can be used to quickly
determine whether the contents of this directory been to be synchronized,
and a top-level contents manifest which includes all of these."""
# Copyright (C) 2007 One Laptop Per Child Association, Inc.
# Licensed under the terms of the GNU GPL v2 or later; see COPYING for details.
# Written by C. Scott Ananian <cscott@laptop.org>
from __future__ import with_statement

from bitfrost.contents.utils import mkcontents, mkusermap, mkgroupmap, DIROBJECT
import bitfrost.util.json as json
import sys

def main ():
    from optparse import OptionParser
    from bitfrost.contents import VERSION_INFO
    parser = OptionParser(usage='%prog [options] [directory]')
    parser.add_option('-f', '--file', dest='filename',default=None,
                      help='write full contents manifest to FILE',metavar='FILE')
    parser.add_option('-s','--split', action='store_true', dest='split',
                      default=False,
                      help='write directory objects to '+DIROBJECT+' in each directory')
    parser.add_option('-n','--numeric-owner', action='store_true',
                      dest='numeric', default=False,
                      help='use numeric UID/GID only')
    parser.add_option('--owner', dest='owner',
                      default=None, metavar='USER',
                      help="specifies that the contents manifest should use USER as "
                      "the owner of files when creating the contents manifest, "
                      "instead of the user associated with the source "
                      "file.  The argument USER can be either an existing "
                      "user symbolic name, or a decimal numeric user ID.")
    parser.add_option('--group', dest='group',
                      default=None, metavar='GROUP',
                      help="files added to the contents manifest will have a group ID "
                      "of GROUP, rather than the group from the source file.  "
                      "The argument GROUP can be either an existing group "
                      "symbolic name, or a decimal numeric group ID.")
    parser.add_option('-p', '--passwd-file', dest='passwd_file',
                      default='/etc/passwd', metavar='PASSWD',
                      help='use PASSWD to lookup usernames for uids')
    parser.add_option('-g', '--group-file', dest='group_file',
                      default='/etc/group', metavar='GROUP',
                      help='use GROUP to lookup groupnames for gids')
    # NFC is recommended in RFC3987
    parser.add_option('--allow-invalid-utf8', dest='valid_UTF8',
                      default=True, action='store_false',
                      help='allow filenames which are not valid UTF-8 '
                      'encodings of unicode Normalization Form C strings '
                      'in file, directory, user, and group names.  These '
                      'can break dbus services and file-scheme urls, and '
                      'confuse users.')
    parser.add_option('--version',action='store_true',dest='version',
                      default=False,
                      help="display version and license information.")
    (options, args) = parser.parse_args()
    if options.version:
        print VERSION_INFO
    if not args:
        parser.error('The directory argument is required.')
    path = args[0]

    # create user and group maps
    usermap = mkusermap(options.passwd_file)
    groupmap = mkgroupmap(options.group_file)
    # try to make force_user into an int.
    force_user = options.owner
    if force_user is not None:
        if force_user in usermap.values():
            force_user = [k for k,v in usermap.items() if v == force_user][0]
        else:
            try:
                force_user = int(force_user)
            except ValueError: pass # treat as username
    # try to make force_group to an int
    force_group = options.group
    if force_group is not None:
        if force_group in groupmap.values():
            force_group = [k for k,v in groupmap.items() if v == force_group][0]
        else:
            try:
                force_group = int(force_group)
            except ValueError: pass # treat as username
    if options.numeric:
        usermap, groupmap = None, None

    contents = mkcontents(path, write_dir=options.split,
                          usermap=usermap, groupmap=groupmap,
                          force_user=force_user,
                          force_group=force_group,
                          valid_UTF8=options.valid_UTF8)
    if options.filename:
        with open(options.filename, 'w') as f:
            json.write_to_file(f, contents)
    else:
        json.write_to_file(sys.stdout, contents)

if __name__ == '__main__': main ()
