########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Server/Server/Commands/Init.py,v 1.79 2005/04/07 00:18:47 jkloth Exp $
"""
4Suite repository init command (4ss_manager init)

Copyright 2005 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

__doc__ = """Initialize the repository.  This involves reformatting \
the storage back end (e.g. Postgres database or FlatFile root \
directory) and then adding the core data structures. You will LOSE \
ALL DATA when you execute the init, unless you use the --update \
option.  You can only init on the machine on which the command is \
executed.  You must have write access to where the back end stores its \
data (e.g., your userid must be able to write to the Postgres database \
if the back end is Postgres).  You may be prompted for additional \
information to complete the init."""

from __future__ import generators

import sha, getpass, os, glob, time, sys, posixpath

from Ft import GetConfigVar
from Ft.Lib import Uri, Wrap, Terminal
from Ft.Server import FtServerBaseException, FTSERVER_NAMESPACE
from Ft.Server.Common import AclConstants, ResourceTypes
from Ft.Server.Common import Schema, ClAuthenticate, XmlLib
from Ft.Server.Common.Install import InstallUtil, Serialize
from Ft.Server.Server import Drivers
from Ft.Server.Server import SCore, FtServerServerException, Error
from Ft.Server.Server.Drivers import PathImp
from Ft.Server.Server.Lib import ConfigFile, LogUtil
from Ft.Server.Server.SCore import RepositoryImp
from Ft.Xml.XLink import XLINK_NAMESPACE


def CommandLineInit(options, args):
    """
    Init function called from the command line tool.
    Parse options and args, do validation, and then
    call the common Initialization routines.
    """

    #Gather the command line information
    do_update = options.get('update', 0)
    repoOnly = options.get('repoOnly', 0)
    confirm = options.get('confirm', 0)
    db_port = options.get('dashboard-port', 8080)

    components = args.get('components')

    #Adjust the components as needed
    if not len(components) and not repoOnly:
        #Default to full
        full = True
    else:
        full = False

    if repoOnly:
        #if they only want the repo, then the components can only be Core
        #If they specify anything else then it is an error.
        if components:
            sys.stderr.write("You cannot specify repo-only and a list of components.\n")
            return
        components = ['Core']
    else:
        #If they didn't specify repo-only then update the component list
        if full:
            components = g_allComponentNames[:]
        components.insert(0, 'Core')  #Make sure it is first.
        # data must be second
        if 'data' in components:
            components.remove('data')
            components.insert(1, 'data')
        else:
            sys.stderr.write("The Data component must be first in the list; other components depend on it.\n")
            return

    #Read the config file
    properties = ConfigFile.Read(options.get('config-file'))
    coreId = options.get('core-id', os.environ.get('FTSS_CORE_ID', 'Core'))
    try:
        properties = properties[coreId]
    except KeyError:
        raise FtServerServerException(Error.CONFIG_INVALID_CORE_ID, name=coreId)
    properties['CoreId'] = coreId


    #Validate that the user can do what they think they can
    driver = Drivers.Begin(LogUtil.NullLogger(), properties)

    exists = driver.exists()
    userName = passwd = None
    if exists == 1:
        #A repository was found.  Make sure we have access
        if do_update:
            sys.stderr.write("Updating existing repository\n")
        else:
            #Make sure the user knows
            sys.stderr.write("\nA repository currently exists!\n")
        sys.stderr.write("Please authenticate with a user name and password\n")
        sys.stderr.write("with execute permission for the init command.\n")
        userName, passwd = ClAuthenticate.UserAuthenticate(prompt="Manager",
                                                           promptOnly=True)
        driver.login(userName, passwd)
        model = driver.getSystemModel()
        stmts = model.complete(None,
                               Schema.COMMAND_FULL_NAME,
                               '4ss_manager.init')
        try:
            p = PathImp.CreateInitialPath('/', driver)
            if len(stmts) == 1:
                p = p.normalize(stmts[0].subject + ";no-traverse")
            else:
                initCmdDefaultPath = '/ftss/commands/4ss_manager.init'
                sys.stderr.write("Metadata for the Init command was not found in the repo.\n")
                sys.stderr.write("Will attempt to check execute permission using\n")
                sys.stderr.write("%s, if it exists.\n" % initCmdDefaultPath)
                p = p.normalize('%s;no-traverse' % initCmdDefaultPath)
            driver.verifyAcl(p, AclConstants.EXECUTE_ACCESS, 0)
        except FtServerServerException, e:
            if e.errorCode == Error.PERMISSION_DENIED:
                raise e
            pass
        driver.rollback()
        del driver
    elif do_update:
        #You cannot update a repo that does not exist.
        sys.stderr.write("You cannot execute an update when a repository does not exist\n")
        return


    #Determine if we need to destroy the repository.  We destroy the repo is
    #a) it partially exists (exists == 0)
    #b) the user didn't specify update and exists == 1 (it is there)
    destroyRepo = (exists == 0) or (exists == 1 and not do_update)

    #Hand processing off to the common init routines
    DoInit(properties,
           userName,
           passwd,
           components,
           confirm=confirm,
           destroyRepo=destroyRepo,
           do_update=do_update)

    sys.stdout.flush()
    sys.stderr.write('\n')
    return


def DoInit(properties, userName, password, components, confirm=False,
           destroyRepo=False, users=None, quiet=False, do_update=False):
    """
    Do the actual initialization.
    Iterate over the components and do each one.
    """

    products = BuildProductList(components, destroyRepo, users, do_update=do_update)
    for (product, base) in products:
        repo = product.getRepo(userName, password, properties)
        commit = False
        try:
            product.install(repo, base, quiet, confirm, do_update=do_update)
            commit = True
        finally:
            if commit:
                product.commit(repo)
            else:
                product.rollback(repo)
    return


def BuildProductList(components, destroyRepo, users, do_update=False):
    for component in components:
        if component.lower() == 'core':
            yield (CoreProduct(destroyRepo, users), '/')
        elif component.lower() == 'data':
            pl, base = BuildDataProduct()
            yield (OtherProduct(pl, users), base)
        elif component.lower() == 'servers':
            pl, base = BuildServerProduct()
            yield (OtherProduct(pl, users), base)
        elif component.lower() == 'commands':
            pl, base = BuildCommandProduct()
            yield (OtherProduct(pl, users), base)
        elif component.lower() == 'docs':
            for pl, base in BuildDocProduct(do_update=do_update):
                yield (OtherProduct(pl, users), base)
        elif component.lower() == 'dashboard':
            path = os.path.join(GetConfigVar('DATADIR'), 'Dashboard',
                                'setup.xml')
            f = open(path)
            pl = InstallUtil.Deserialize(f, Uri.OsPathToUri(path))
            f.close()
            yield (OtherProduct(pl, users), '/ftss')
        elif component.lower() == 'demos':
            path = os.path.join(GetConfigVar('DATADIR'), 'Demos', 'setup.xml')
            f = open(path)
            pl = InstallUtil.Deserialize(f, Uri.OsPathToUri(path))
            f.close()
            yield (OtherProduct(pl, users), '/ftss')
        else:
            sys.stderr.write('Skipping unknown component %s\n' % component)


def BuildDataProduct():
    l = []

    #Add some containers
    l.append(InstallUtil.Container('docdefs',[], None, None, None))
    l.append(InstallUtil.Container('data',[], None, None, None))
    l.append(InstallUtil.Container('data/icons',[], None, None, None))
    l.append(InstallUtil.Container('data/presmaterial',[], None, None, None))

    #stBase = posixpath.join(urllib.pathname2url(GetConfigVar('DATADIR')), 'Data', 'Stylesheets')
    stBase = posixpath.join(Uri.OsPathToUri(GetConfigVar('DATADIR'), attemptAbsolute=False), 'Data',
                            'Stylesheets')

    #Add the docdefs
    dd = InstallUtil.XPathDocumentDefinition('docdefs/docbook1',[], None, None, None)
    dd.setPath(posixpath.join(stBase, 'docbook1.docdef'))
    l.append(dd)
    dd = InstallUtil.XPathDocumentDefinition('docdefs/wsdl',[], None, None, None)
    dd.setPath(posixpath.join(stBase, 'wsdl.docdef'))
    l.append(dd)
    dd = InstallUtil.XPathDocumentDefinition('docdefs/dublin_core',[], None, None, None)
    dd.setPath(posixpath.join(stBase, 'dublin_core.docdef'))
    l.append(dd)

    dd = InstallUtil.XPathDocumentDefinition('docdefs/generated-docs',[], None, None, None)
    dd.setPath(posixpath.join(stBase, 'generated-docs.docdef'))
    l.append(dd)

    #Get stuff from the stylesheet package
    stBase = os.path.join(GetConfigVar('DATADIR'), 'Data', 'Stylesheets')
    icBase = os.path.join(GetConfigVar('DATADIR'), 'Data', 'Icons')
    schBase = os.path.join(GetConfigVar('DATADIR'), 'Schemata')

    for css in [
                'sdocbook_html.css',
                'commandline.css',
                'extensions.css',
                'modules.css',
                ]:
        r = InstallUtil.RawFile('data/' + css, [], None, 'text/css')
        r.setPath(posixpath.join(Uri.OsPathToUri(stBase, attemptAbsolute=False), css))
        l.append(r)


    for (ext, imt_suff) in [('gif', 'gif'), ('png', 'png'), ('jpg', 'jpeg')]:
        files = glob.glob(os.path.join(stBase, '*.'+ext))
        for f in files:
            name = os.path.basename(f)
            r = InstallUtil.RawFile('data/' + name,
                                    [],
                                    None,
                                    'image/'+imt_suff)
            r.setPath(Uri.OsPathToUri(f, attemptAbsolute=False))
            l.append(r)
        files = glob.glob(os.path.join(icBase, '*.'+ext))
        for f in files:
            name = os.path.basename(f)
            r = InstallUtil.RawFile('data/icons/' + name,
                                    [],
                                    None,
                                    'image/'+imt_suff)
            r.setPath(Uri.OsPathToUri(f, attemptAbsolute=False))
            l.append(r)

    dtds = glob.glob(os.path.join(schBase, '*.dtd')) + glob.glob(os.path.join(schBase, '*.ent'))
    #print schBase, dtds
    for f in dtds:
        name = os.path.basename(f)
        r = InstallUtil.RawFile('data/' + name, [], None, 'text/xml')
        r.setPath(Uri.OsPathToUri(f, attemptAbsolute=False))
        l.append(r)

    xmls = glob.glob(os.path.join(stBase, '*.xml')) + [os.path.join(stBase, 'null')]
    for x in xmls:
        name = os.path.basename(x)
        d = InstallUtil.XmlDocument('data/'+name, [], None, None, None)
        d.setPath(Uri.OsPathToUri(x, attemptAbsolute=False))
        l.append(d)

    stys = [
            # basic transformations
            'null.xslt',
            'identity.xslt',
            'pretty.xslt',
            'decorated-xml.xslt',
            # DocBook XML conversion
            'docbook_html1.xslt',
            'sdocbook_html.xslt',
            'docbook_text1.xslt',
            # text formatting
            'justify.xslt',
            # repository resource formatting
            'container.xslt',
            'cl-container.xslt',
            # API doc XML formatting
            'commandline_html.xslt',
            'extensions_html.xslt',
            'modules_html.xslt',
            # misc
            'xbel2rdf.xslt',
            ]
    for s in stys:
        path = os.path.join(stBase, s)
        d = InstallUtil.XsltDocument('data/'+s,[], None, None, None)
        d.setPath(Uri.OsPathToUri(path, attemptAbsolute=False))
        l.append(d)

    pl = InstallUtil.Product(l, name='Data')
    return pl, '/ftss'


def BuildServerProduct():
    l = []

    l.append(InstallUtil.Container('servers',[], None, None, None))

    serverbase = os.path.join(GetConfigVar('DATADIR'), 'Data', 'Servers')

    s = InstallUtil.Server('servers/FtRpc-server.xml',[], None, None, None)
    s.setPath(posixpath.join(Uri.OsPathToUri(serverbase, attemptAbsolute=False), 'FtRpc-server.xml'))
    l.append(s)
    s = InstallUtil.Server('servers/FtFtp-server.xml',[], None, None, None)
    s.setPath(posixpath.join(Uri.OsPathToUri(serverbase, attemptAbsolute=False), 'FtFtp-server.xml'))
    l.append(s)
    s = InstallUtil.Server('servers/FtSoap-server.xml',[], None, None, None)
    s.setPath(posixpath.join(Uri.OsPathToUri(serverbase, attemptAbsolute=False), 'FtSoap-server.xml'))
    l.append(s)

    pl = InstallUtil.Product(l, name='Servers')
    return pl, '/ftss'


def BuildDocProduct(do_update=False):
    l = []

    l.append(InstallUtil.Container('docs', [], None, None, None))
    mapping = Serialize.g_defaultFileMap.copy()
    mapping['.doc'] = ('/ftss/docdefs/docbook1','text/xml', InstallUtil.XmlDocument)
    mapping['.xml'] = ('/ftss/docdefs/generated-docs','text/xml', InstallUtil.XmlDocument)

    tty = Terminal.Terminal(sys.stderr)
    columns = tty.columns()
    docs_dir = os.path.join(GetConfigVar('DOCDIR'), 'xml')

    if os.path.exists(docs_dir):
        l.extend(Serialize.MirrorDir('/ftss/docs', docs_dir, mapping, 0))
        if not do_update and len(l) > 24:
            msg = "\nNote: %d docs will be installed." % len(l) + \
                  " This may be a time-consuming process. To avoid" + \
                  " this, abort now and either remove the " + docs_dir + \
                  " directory or run the init command without" + \
                  " specifying Docs installation (see --help)."
            tty.write(Wrap(msg, columns) + '\n')
    else:
        msg = "\nNote: There are no docs to " + \
              ('install','update')[do_update and 1 or 0] + \
              ". If docs are needed, first reinstall 4Suite with" + \
              " 'setup.py install --with-docs'. Also note that" + \
              " generating the docs and installing them in the" + \
              " repository are time-consuming processes."
        tty.write(Wrap(msg, columns) + '\n')

    # chunking is safe if there are no dependencies across chunks.
    # docs are independent XML; they only need their docdef,
    # /ftss/docdefs/generated-docs
    size = len(l)
    chunksize = 110
    if chunksize < size:
        totalchunks = size / chunksize + 1
        part = 0
        for i in xrange(totalchunks):
            part += 1
            chunk = l[:chunksize]
            pl = InstallUtil.Product(chunk, name='Docs (part %d of %d)' % (part, totalchunks))
            yield pl, '/ftss'
            l = l[i+chunksize:]
    else:
        yield InstallUtil.Product(l, name='Docs'), 'ftss'


def BuildCommandProduct():
    l = []
    l.append(InstallUtil.Container('commands', [], None, None, None))
    from Ft.Server.Server.Commands import ManagerCommandLineApp
    app = ManagerCommandLineApp()

    l.extend(BuildCommandDocs(
        app, '', [(AclConstants.EXECUTE_ACCESS, AclConstants.SUPER_USER_GROUP_NAME, 1)]
        ))

    from Ft.Server.Client.Commands import GeneralCommandLineApp
    app = GeneralCommandLineApp()

    l.extend(BuildCommandDocs(
        app, '', [(AclConstants.EXECUTE_ACCESS, AclConstants.WORLD_GROUP_NAME, 1)]
        ))

    pl = InstallUtil.Product(l, name='Commands')
    return pl, '/ftss'


class CoreProduct(InstallUtil.Product):

    #There are two different flows through this code.
    #One, the user is doing a fresh install.  Note, this could be
    #because they want to remove an existing repo

    #Secondly, the user is doing an update.

    #The main difference is how we deal with users.  If, the user is not doing an update,
    #Then we need to add the users, and the superuser group to the installation.
    #If the user is doing an update then we don't touch these.

    coreResources = [InstallUtil.Container('ftss',[], AclConstants.SUPER_USER_GROUP_NAME, None, None),
                     InstallUtil.Container('ftss/users',[], AclConstants.SUPER_USER_GROUP_NAME, None, None),
                     InstallUtil.Container('ftss/groups',[], AclConstants.SUPER_USER_GROUP_NAME, None, None),
                     ]

    users = []

    def __init__(self, destroyRepo, users):
        self.destroyRepo = destroyRepo
        if users:
            self.users = users
        InstallUtil.Product.__init__(self,
                                     resourceList = self.coreResources[:],
                                     name='Repository',
                                     useIndicator=False
                                     )

    def install(self, driver, basePath, quiet, confirm, do_update=False):
        """
        Perform a few extra installation features.
        """
        #First, see if we need to get rid of an existing repo.
        if self.destroyRepo:
            if confirm:
                sys.stderr.write("Initialization will erase ALL DATA in the 4Suite repository.\n")
                sys.stderr.write('Are you sure you want to continue (yes/no)? ')
                sys.stderr.flush()
                answer = raw_input()
                if answer.lower() not in ['yes', 'y']:
                    sys.stderr.write("Aborting Initialization\n")
                    return

            #See ya!
            if not quiet:
                sys.stderr.write("Removing Current Repository\n")
            driver.destroy()


        #now, if the user is not doing an update, then add the users to the resource list
        #We know the user is doing an update because we either explictly destroyed the
        #repo, or it never existed.
        if self.destroyRepo or driver.exists() == -1:
            #We are doing an update.  See if we need to ask for the user list
            if not self.users:
            #Gather a list of users
                usernames = {}
                while 1:
                    sys.stderr.write('Superuser name to add (or just "enter" when done): ')
                    sys.stderr.flush()
                    user = raw_input()
                    if not user:
                        if len(self.users): break
                        sys.stderr.write("You must add at least one Superuser!\n")
                        continue
                    if user in usernames:
                        sys.stderr.write("Superuser %s was already added!\n" % user)
                        continue
                    same = False
                    while not same:
                        password = getpass.getpass()
                        same = (password == getpass.getpass('Reenter password:'))
                    CoreProduct.users.append((user, password))
                    usernames[user] = True

            #Now, add all of the users to the resource list
            for u in self.users:
                self.resourceList.append(InstallUtil.User(
                    u[0], [], None, None, None, 'ftss/users', u[1]))

            #Lastly add the super-user group
            members = [user[0] for user in self.users]
            self.resourceList.append(InstallUtil.Group(
                AclConstants.SUPER_USER_GROUP_NAME, [], None, None, None,
                'ftss/groups', members))

            #Now, since we know it is not an update (inside this if statement)
            #create the new repo
            if not quiet:
                sys.stderr.write("Initializing the Repo\n")

            driver.initialize()  #Create the root

            #Update the log file
            init_time = time.asctime(time.localtime(time.time()))
            #open(self.errorLogFileName, 'a').write("Initialized Repo: %s\n" % init_time)
        else:
            #If the user just wants to update, then log into the driver to start a TX
            driver.login(self.userName, self.password)

        #Now, at this point in the install, we have a initialized driver so go into the normal installation
        #and add all of the system containers
        #Create a new Repo instance so the rest of install will work

        path = PathImp.CreateInitialPath('/', driver)
        repo = RepositoryImp.RepositoryImp(path, driver, None)

        return InstallUtil.Product.install(self, repo, '/', quiet, do_update=do_update)

    def getRepo(self, userName, password, properties):
        """
        For the core product, our repo is the driver
        """
        self.coreResources = self.coreResources[:]

        #Store the user name and password incase we need them
        self.userName = userName
        self.password = password

        #One extra bit of trickery.  At this time we know the properties so add the errorlog to our resourceList

        #from Ft.Server.Server.GlobalConfig import g_logFileName
        #self.errorLogFileName = g_logFileName
        #self.coreResources.append(InstallUtil.UriReferenceFile(
        #    'ftss/error.log', [], AclConstants.SUPER_USER_GROUP_NAME,
        #    'text/plain', Uri.OsPathToUri(self.errorLogFileName, attemptAbsolute=False)))
        return Drivers.Begin(LogUtil.NullLogger(), properties)

    def commit(self, driver):
        driver.commit()

    def rollback(self, driver):
        pass

    def setIndicator(self):
        pass


class OtherProduct:

    def __init__(self, baseProduct, users):
        self.users = users
        self.baseProduct = baseProduct
        return

    def getRepo(self, userName, password, properties):
        if userName is None:
            if self.users is None:
                users = CoreProduct.users
                userName = CoreProduct.users[0][0]
                password = sha.new(CoreProduct.users[0][1]).hexdigest()
            else:
                userName, password = self.users[0]
        return SCore.GetRepository(userName, password, LogUtil.NullLogger(),
                                   properties)

    def install(self, repo, basePath, quiet, confirm, do_update=False):
        if confirm:
            sys.stderr.write('%s %s (yes/no)? ' % (
                             ('Install', 'Update')[do_update and 1 or 0],
                              self.baseProduct.name
                             ))
            sys.stderr.flush()
            answer = raw_input()
            if answer.lower() not in ['yes', 'y']:
                print "Skipping %s" % self.baseProduct.name
                return 0
        return self.baseProduct.install(repo, basePath, quiet, do_update=do_update)

    def commit(self, repo):
        repo.txCommit()

    def rollback(self, repo):
        repo.txRollback()


class ExtendedCommand(InstallUtil.Command):

    def __init__(self, path, acl, owner, imt, docDef, command, cmdPath):
        self.command = command
        self.cmdPath = cmdPath
        InstallUtil.Command.__init__(self, path, acl, owner, imt, docDef)
        return

    def _getContent(self):
        doc = """<ftss:Command xmlns:ftss="%s" xmlns:dc="%s" xmlns:xlink="%s" name='%s' full-name='%s'>""" % (
            FTSERVER_NAMESPACE,
            Schema.DC,
            XLINK_NAMESPACE,
            self.command.name,
            posixpath.basename(self.path)
            )

        if self.command.description:
            doc += '<dc:Description>%s</dc:Description>\n' % self.command.description
        if self.command.example:
            doc += '<ftss:Example>%s</ftss:Example>\n' % self.command.example
        if self.command.verbose_description:
            doc += '<ftss:VerboseDescription>%s</ftss:VerboseDescription>\n' % self.command.verbose_description

        doc += '<ftss:Options>'

        for option in self.command.options:
            doc += self.buildOptionDoc(option, self.command)

        doc += '</ftss:Options>\n'

        doc += '<ftss:Arguments>\n'
        for a in self.command.arguments:
            doc += "<ftss:Argument name='%s' requirements = '%d'>" % (a.name, a.requirements)
            if a.description:
                doc += '<dc:Description>%s</dc:Description>\n' % a.description
            doc += "</ftss:Argument>"
        doc += '</ftss:Arguments>\n'


        doc += '<ftss:SubCommands>\n'
        for name, cmd in self.command.subCommands.items():
            doc += '<ftss:CommandReference xlink:type="simple" xlink:href="%s.%s" xlink:actuate="onLoad" xlink:show="embed"/>' % (self.cmdPath, name)
        doc += '</ftss:SubCommands>\n'

        doc += """</ftss:Command>"""

        return XmlLib.MakeString(doc)


    def buildOptionDoc(self, option, command):
        from Ft.Lib.CommandLine import Options

        if isinstance(option, Options.ExclusiveOptions):
            doc = '<ftss:ExclusiveOption>\n'
            for o in option.choices:
                doc += self.buildOptionDoc(o, command)
            doc += '</ftss:ExclusiveOption>\n'

        elif isinstance(option, Options.TypedOption):
            doc = '<ftss:TypedOption '

            if option.shortName:
                doc += 'short-name="%s" ' % option.shortName

            doc += 'long-name="%s">\n' % option.longName
            if option.description:
                doc += '<dc:Description>%s</dc:Description>\n' % option.description

            for value, desc in option.allowed:
                doc += '<ftss:Allowed name="%s">' % value
                doc += '<dc:Description>%s</dc:Description>\n' % desc
                doc += '</ftss:Allowed>'

            if option.subOptions:
                doc += '<ftss:SubOptions>\n'
                for so in command.subOptions:
                    doc += self.buildOptionDoc(so, command)
                doc += '</ftss:SubOptions>\n'
            doc += '</ftss:TypedOption>\n'
        else:
            #Normal option
            doc = '<ftss:Option '
            if option.shortName:
                doc += 'short-name="%s" ' % option.shortName

            if option.takesArg:
                doc += 'arg-name="%s" ' % option.argName

            doc += 'long-name="%s">\n' % option.longName
            if option.description:
                doc += '<dc:Description>%s</dc:Description>\n' % option.description

            if option.subOptions:
                doc += '<ftss:SubOptions>\n'
                for so in command.subOptions:
                    doc += self.buildOptionDoc(so, command)
                doc += '</ftss:SubOptions>\n'
            doc += '</ftss:Option>\n'

        return unicode(doc)


def BuildCommandDocs(command, baseName, acl):

    if not command._fileName:
        raise "Command %s did not register a file name" % command.name

    if baseName:
        path = baseName + '.' + command.name
    else:
        path = command.name

    cmds = [ExtendedCommand('commands/'+path, acl, None, None,
                            '/ftss/docdefs/dublin_core', command, path)]
    uri = Uri.OsPathToUri(command._fileName, attemptAbsolute=False) + '#ExtendedCommand-XML'
    cmds[0].setPath(uri)

    for name, cmd in command.subCommands.items():
        cmds.extend(BuildCommandDocs(cmd, path, acl))

    return cmds

g_allComponents = {'Demos': 'The default 4Suite Repository Demonstration applications'
                            ' and the server to access them',
                   'Docs': 'All 4Suite documentation',
                   'Servers': 'The default FtRpc and Ftp Server',
                   'Commands': '4ss and 4ss_manager commands and documentation;'
                               ' these are needed to be able to use the 4ss and '
                               ' 4ss_manager command-line tools',
                   'Dashboard': 'The web-based control panel for 4Suite',
                   'Data': 'Commonly used 4Suite data and icons (required; must be '
                           ' first component installed)'
                   }

g_allComponentNames = [c.lower() for c in g_allComponents]

#These actually need to be sorted a bit.  Data needs to be first
g_allComponentNames.remove('data')
g_allComponentNames.insert(0, 'data')

def VerifyComponent(comp):
    if str(comp).lower() in g_allComponentNames:
        return str(comp).lower()
    raise ValueError("Component argument must be one of: %s." % ', '.join(g_allComponentNames))

g_componentHelp = """Specify which components to add/update in the repository. Allowed values are:\n\n"""

for name, value in g_allComponents.items():
    g_componentHelp += "'%s' - %s;\n" % (name.capitalize(), value)
g_componentHelp = g_componentHelp[:-2] + '.'


def Register():
    from Ft.Lib.CommandLine import Options, Command, Arguments

    OPTIONS = Options.Options([
        Options.Option('c',
                       'confirm',
                       'Confirm before performing each sub-task.'),
        Options.Option('r',
                       'update',
                       "Don't destroy the current repository, just update the data."),
        Options.Option('o',
                       'repo-only',
                       'Only create the bare repository (destroying any current one), and don\'t install any components. Only useful for development.'),
        ])

    ARGUMENTS = [
        Arguments.ZeroOrMoreArgument('components',
                                     g_componentHelp,
                                     VerifyComponent),
        ]

    cmd = Command.Command('init',
                          'Initialize the 4Suite repository',
                          'Data Servers Dashboard',
                          __doc__,
                          function=CommandLineInit,
                          options=OPTIONS,
                          arguments=ARGUMENTS,
                          fileName = __file__
                          )
    return cmd
