]> scripts.mit.edu Git - wizard.git/commitdiff
Move Application/ApplicationVersion to wizard.app
authorEdward Z. Yang <ezyang@mit.edu>
Fri, 16 Oct 2009 21:46:06 +0000 (17:46 -0400)
committerEdward Z. Yang <ezyang@mit.edu>
Fri, 16 Oct 2009 21:50:20 +0000 (17:50 -0400)
Signed-off-by: Edward Z. Yang <ezyang@mit.edu>
13 files changed:
doc/module/wizard.deploy.rst
tests/all.sh [new file with mode: 0755]
wizard/app/__init__.py
wizard/app/mediawiki.py
wizard/command/blacklist.py
wizard/command/configure.py
wizard/command/errors.py
wizard/command/install.py
wizard/command/research.py
wizard/deploy.py
wizard/old_log.py
wizard/tests/deploy_test.py
wizard/tests/old_log_test.py

index 391f8a085b527ace0e72e79d0d7ca0f0819a131a..2182263d7148a39edd7c8f1d563c26d14cccf7d1 100644 (file)
@@ -13,27 +13,16 @@ Classes
 .. autoclass:: WorkingCopy
     :members:
     :show-inheritance:
-.. autoclass:: Application
-    :members:
-.. autoclass:: ApplicationVersion
-    :members:
 
 Functions
 ---------
 .. autofunction:: get_install_lines
 .. autofunction:: parse_install_lines
-.. autofunction:: applications
 .. autofunction:: chdir_to_location
 
 Exceptions
 ----------
 .. autoexception:: Error
-.. autoexception:: NoSuchApplication
-    :members:
-.. autoexception:: DeploymentParseError
-    :members:
-.. autoexception:: NoRepositoryError
-    :members:
 .. autoexception:: AlreadyVersionedError
     :members:
 .. autoexception:: NotMigratedError
diff --git a/tests/all.sh b/tests/all.sh
new file mode 100755 (executable)
index 0000000..5f5dfe6
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/sh
+bold="`tput -T${TERM:-dumb} bold`"
+red="`tput -T${TERM:-dumb} setf 4`"
+sgr0="`tput -T${TERM:-dumb} sgr0`"
+for i in test-*.sh; do
+    ./$i "$1"
+    if [ $? -eq 0 ]
+    then
+        echo "${bold}OK${sgr0}"
+    else
+        echo "${bold}${red}FAIL${sgr0}"
+    fi
+    echo
+done
index 8a4789f3eb78d94a400bdb77470db8bfac743d48..88e9fec7f45cf8327b0a240880e2c3571a0f4496 100644 (file)
@@ -1,8 +1,279 @@
 import os.path
 import re
+import distutils.version
 
 import wizard
 
+_application_list = [
+    "mediawiki", "wordpress", "joomla", "e107", "gallery2",
+    "phpBB", "advancedbook", "phpical", "trac", "turbogears", "django",
+    # these are technically deprecated
+    "advancedpoll", "gallery",
+]
+_applications = None
+
+def applications():
+    """Hash table for looking up string application name to instance"""
+    global _applications
+    if not _applications:
+        _applications = dict([(n,Application.make(n)) for n in _application_list ])
+    return _applications
+
+
+class Application(object):
+    """
+    Represents an application, i.e. mediawiki or phpbb.
+
+    .. note::
+        Many of these methods assume a specific working
+        directory; prefer using the corresponding methods
+        in :class:`deploy.Deployment` and its subclasses.
+    """
+    #: String name of the application
+    name = None
+    #: Dictionary of version strings to :class:`ApplicationVersion`.
+    #: See also :meth:`makeVersion`.
+    versions = None
+    #: List of files that need to be modified when parametrizing.
+    #: This is a class-wide constant, and should not normally be modified.
+    parametrized_files = []
+    #: Keys that are used in older versions of the application, but
+    #: not for the most recent version.
+    deprecated_keys = []
+    def __init__(self, name):
+        self.name = name
+        self.versions = {}
+        # cache variables
+        self._extractors = {}
+        self._substitutions = {}
+    def repository(self, srv_path):
+        """
+        Returns the Git repository that would contain this application.
+        ``srv_path`` corresponds to ``options.srv_path`` from the global baton.
+        """
+        repo = os.path.join(srv_path, self.name + ".git")
+        if not os.path.isdir(repo):
+            repo = os.path.join(srv_path, self.name, ".git")
+            if not os.path.isdir(repo):
+                raise NoRepositoryError(self.name)
+        return repo
+    def makeVersion(self, version):
+        """
+        Creates or retrieves the :class:`ApplicationVersion` singleton for the
+        specified version.
+        """
+        if version not in self.versions:
+            self.versions[version] = ApplicationVersion(distutils.version.LooseVersion(version), self)
+        return self.versions[version]
+    def extract(self, deployment):
+        """Extracts wizard variables from a deployment."""
+        result = {}
+        for k,extractor in self.extractors.items():
+            result[k] = extractor(deployment)
+        return result
+    def parametrize(self, deployment):
+        """
+        Takes a generic source checkout and parametrizes
+        it according to the values of deployment.  This function
+        operates on the current working directory.
+        """
+        variables = deployment.extract()
+        for file in self.parametrized_files:
+            try:
+                contents = open(file, "r").read()
+            except IOError:
+                continue
+            for key, value in variables.items():
+                if value is None: continue
+                contents = contents.replace(key, value)
+            f = open(file, "w")
+            f.write(contents)
+    def resolveConflicts(self, deployment):
+        """
+        Resolves conflicted files in the current working
+        directory.  Returns whether or not all conflicted
+        files were resolved or not.  Fully resolved files are
+        added to the index, but no commit is made.  By default
+        this is a no-op and returns ``False``.
+        """
+        return False
+    def prepareMerge(self, deployment):
+        """
+        Performs various edits to files in the current working directory in
+        order to make a merge go more smoothly.  This is usually
+        used to fix botched line-endings.  If you add new files,
+        you have to 'git add' them; this is not necessary for edits.
+        By default this is a no-op.
+        """
+        pass
+    def prepareConfig(self, deployment):
+        """
+        Takes a deployment and replaces any explicit instances
+        of a configuration variable with generic ``WIZARD_*`` constants.
+        The default implementation uses :attr:`substitutions`;
+        you can override this method to provide arbitrary extra
+        behavior.
+        """
+        for key, subst in self.substitutions.items():
+            subs = subst(deployment)
+            if not subs and key not in self.deprecated_keys:
+                logging.warning("No substitutions for %s" % key)
+    def install(self, version, options):
+        """
+        Run for 'wizard configure' (and, by proxy, 'wizard install')
+        to configure an application.  This assumes that the current
+        working directory is a deployment.  (This function does not take
+        a :class:`deploy.Deployment` as a parameter, as those operations are
+        not meaningful yet.)
+        """
+        raise NotImplemented
+    def upgrade(self, deployment, version, options):
+        """
+        Run for 'wizard upgrade' to upgrade database schemas and other
+        non-versioned data in an application.  This assumes that
+        the current working directory is the deployment.
+        """
+        raise NotImplemented
+    def backup(self, deployment, options):
+        """
+        Run for 'wizard backup' and upgrades to backup database schemas
+        and other non-versioned data in an application.  This assumes
+        that the current working directory is the deployment.
+        """
+        raise NotImplemented
+    def restore(self, deployment, backup, options):
+        """
+        Run for 'wizard restore' and failed upgrades to restore database
+        and other non-versioned data to a backed up version.  This assumes
+        that the current working directory is the deployment.
+        """
+        raise NotImplemented
+    def detectVersion(self, deployment):
+        """
+        Checks source files to determine the version manually.  This assumes
+        that the current working directory is the deployment.
+        """
+        return None
+    def checkWeb(self, deployment, output=None):
+        """
+        Checks if the autoinstall is viewable from the web.  To get
+        the HTML source that was retrieved, pass a variable containing
+        an empty list to ``output``; it will be mutated to have its
+        first element be the output.
+        """
+        raise NotImplemented
+    def checkConfig(self, deployment):
+        """
+        Checks whether or not an autoinstall has been configured/installed
+        for use.  Assumes that the current working directory is the deployment.
+        """
+        raise NotImplemented
+    @property
+    def extractors(self):
+        """
+        Dictionary of variable names to extractor functions.  These functions
+        take a :class:`deploy.Deployment` as an argument and return the value of
+        the variable, or ``None`` if it could not be found.
+        See also :func:`wizard.app.filename_regex_extractor`.
+        """
+        return {}
+    @property
+    def substitutions(self):
+        """
+        Dictionary of variable names to substitution functions.  These functions
+        take a :class:`deploy.Deployment` as an argument and modify the deployment such
+        that an explicit instance of the variable is released with the generic
+        WIZARD_* constant.  See also :func:`wizard.app.filename_regex_substitution`.
+        """
+        return {}
+    @staticmethod
+    def make(name):
+        """Makes an application, but uses the correct subtype if available."""
+        try:
+            __import__("wizard.app." + name)
+            return getattr(wizard.app, name).Application(name)
+        except ImportError:
+            return Application(name)
+
+class ApplicationVersion(object):
+    """Represents an abstract notion of a version for an application, where
+    ``version`` is a :class:`distutils.version.LooseVersion` and
+    ``application`` is a :class:`Application`."""
+    #: The :class:`distutils.version.LooseVersion` of this instance.
+    version = None
+    #: The :class:`Application` of this instance.
+    application = None
+    def __init__(self, version, application):
+        self.version = version
+        self.application = application
+    @property
+    def tag(self):
+        """
+        Returns the name of the git describe tag for the commit the user is
+        presently on, something like mediawiki-1.2.3-scripts-4-g123abcd
+        """
+        return "%s-%s" % (self.application, self.version)
+    @property
+    def scripts_tag(self):
+        """
+        Returns the name of the Git tag for this version.
+        """
+        end = str(self.version).partition('-scripts')[2].partition('-')[0]
+        return "%s-scripts%s" % (self.pristine_tag, end)
+    @property
+    def pristine_tag(self):
+        """
+        Returns the name of the Git tag for the pristine version corresponding
+        to this version.
+        """
+        return "%s-%s" % (self.application.name, str(self.version).partition('-scripts')[0])
+    def __cmp__(self, y):
+        return cmp(self.version, y.version)
+    @staticmethod
+    def parse(value):
+        """
+        Parses a line from the :term:`versions store` and return
+        :class:`ApplicationVersion`.
+
+        Use this only for cases when speed is of primary importance;
+        the data in version is unreliable and when possible, you should
+        prefer directly instantiating a :class:`deploy.Deployment` and having it query
+        the autoinstall itself for information.
+
+        The `value` to parse will vary.  For old style installs, it
+        will look like::
+
+           /afs/athena.mit.edu/contrib/scripts/deploy/APP-x.y.z
+
+        For new style installs, it will look like::
+
+           APP-x.y.z-scripts
+        """
+        name = value.split("/")[-1]
+        try:
+            if name.find("-") != -1:
+                app, _, version = name.partition("-")
+            else:
+                # kind of poor, maybe should error.  Generally this
+                # will actually result in a not found error
+                app = name
+                version = "trunk"
+        except ValueError:
+            raise DeploymentParseError(value)
+        return ApplicationVersion.make(app, version)
+    @staticmethod
+    def make(app, version):
+        """
+        Makes/retrieves a singleton :class:`ApplicationVersion` from
+        a``app`` and ``version`` string.
+        """
+        try:
+            # defer to the application for version creation to enforce
+            # singletons
+            return applications()[app].makeVersion(version)
+        except KeyError:
+            raise NoSuchApplication(app)
+
 def expand_re(val):
     if isinstance(val, str):
         return re.escape(val)
@@ -86,6 +357,43 @@ class Error(wizard.Error):
     """Generic error class for this module."""
     pass
 
+class NoRepositoryError(Error):
+    """
+    :class:`Application` does not appear to have a Git repository
+    in the normal location.
+    """
+    #: The name of the application that does not have a Git repository.
+    app = None
+    def __init__(self, app):
+        self.app = app
+    def __str__(self):
+        return """Could not find Git repository for '%s'.  If you would like to use a local version, try specifying --srv-path or WIZARD_SRV_PATH.""" % self.app
+
+class DeploymentParseError(Error):
+    """
+    Could not parse ``value`` from :term:`versions store`.
+    """
+    #: The value that failed to parse.
+    value = None
+    #: The location of the autoinstall that threw this variable.
+    #: This should be set by error handling code when it is available.
+    location = None
+    def __init__(self, value):
+        self.value = value
+
+class NoSuchApplication(Error):
+    """
+    You attempted to reference a :class:`Application` named
+    ``app``, which is not recognized by Wizard.
+    """
+    #: The name of the application that does not exist.
+    app = None
+    #: The location of the autoinstall that threw this variable.
+    #: This should be set by error handling code when it is availble.
+    location = None
+    def __init__(self, app):
+        self.app = app
+
 class UpgradeFailure(Error):
     """Upgrade script failed."""
     #: String details of failure (possibly stdout or stderr output)
index 035de39fb5d5242674c62f3438ed880c967856a3..d7dc035036e7d5e7b8f102cb2067140881fbd207 100644 (file)
@@ -121,7 +121,7 @@ $wgCacheEpoch = max( $wgCacheEpoch, gmdate( 'YmdHis', @filemtime( __FILE__ ) ) )
     ]
 }
 
-class Application(deploy.Application):
+class Application(app.Application):
     parametrized_files = ['LocalSettings.php', 'php.ini']
     deprecated_keys = set(['WIZARD_IP']) | php.deprecated_keys
     @property
index a3e159bd17c4269d4a6346fdaf4f9c08bc70edb1..d2c1d35e46339d07b846f61d25b88d6a5c1a75f9 100644 (file)
@@ -4,7 +4,7 @@ import optparse
 import sys
 import distutils.version
 
-from wizard import command, deploy, git, shell, util
+from wizard import command, git, shell, util
 
 def main(argv, baton):
     options, args = parse_args(argv, baton)
index 1ac7ea7734c80eb7d86695c08dd88c820b63af85..8bf97bc34f7074103598acbea872076d6d21da34 100644 (file)
@@ -3,7 +3,7 @@ import optparse
 import sys
 import distutils.version
 
-from wizard import command, deploy, git, shell, util
+from wizard import app, command, git, shell, util
 
 def main(argv, baton):
 
@@ -35,15 +35,15 @@ This is a plumbing command, normal users should use
         tag = git.describe()
         application, _, version = tag.partition('-')
 
-    app = deploy.applications()[application]
-    handler = app.install_handler
+    application = app.applications()[application]
+    handler = application.install_handler
 
     parser = command.WizardOptionParser(usage)
     handler.push(parser)
     options, args = parser.parse_all(argv)
     handler.handle(options)
 
-    app.install(distutils.version.LooseVersion(version), options)
+    application.install(distutils.version.LooseVersion(version), options)
 
     sh = shell.Shell()
     message = "Autoinstall configuration of %s locker.\n\n%s" % (util.get_dir_owner(), util.get_git_footer())
index e2032be1ef28e6190f4d082762216155334aae9b..08fd76ee538678d3fb0e9c3192f7aa51a6170c33 100644 (file)
@@ -1,16 +1,16 @@
 import logging
 
-from wizard import deploy, command
+from wizard import app, deploy, command
 
 def main(argv, baton):
     options, show = parse_args(argv, baton)
     for e in deploy.parse_install_lines(show, options.versions_path, True):
-        if not isinstance(e, deploy.Error):
+        if not isinstance(e, deploy.Error) and not isinstance(e, app.Error):
             if isinstance(e, Exception):
                 raise e
             continue
         if options.verbose:
-            if isinstance(e, deploy.NoSuchApplication):
+            if isinstance(e, app.NoSuchApplication):
                 print "Application %s does not exist, at %s" % (e.app, e.location)
             elif isinstance(e, deploy.DeploymentParseError):
                 print "Parse error for line '%s', at %s" % (e.value, e.location)
index c0fa58de810f73e68f569f8bc6d6df58724735a7..224f8f25cddfc1ccd6c7f75eb7a0cf899351d69a 100644 (file)
@@ -5,22 +5,22 @@ import errno
 import sys
 
 import wizard
-from wizard import command, deploy, shell, util
+from wizard import app, command, shell, util
 
 def main(argv, baton):
     options, args = parse_args(argv, baton)
     # XXX: do something smart if -scripts is not at the end
-    app = args[0]
+    appstr = args[0]
     dir = args[1]
     if os.path.exists(dir):
         raise DirectoryExistsError
-    appname, _, version = app.partition('-')
-    application = deploy.applications()[appname]
+    appname, _, version = appstr.partition('-')
+    application = app.applications()[appname]
     sh = shell.Shell()
     sh.call("git", "clone", "-q", "--shared", application.repository(options.srv_path), dir)
     with util.ChangeDirectory(dir):
         if version:
-            sh.call("git", "reset", "-q", "--hard", app)
+            sh.call("git", "reset", "-q", "--hard", appstr)
         # this command's stdin should be hooked up to ours
         try:
             configure_args = args[2:] + command.make_base_args(options)
index 46164e7132d3622d89de437a05750f3fd2305483..6ddd07cbfca1865bc4260bf82d6420baaae6b41a 100644 (file)
@@ -68,6 +68,7 @@ def main(argv, baton):
                 # XXX: These should error, but for now don't
                 pass
             except (deploy.Error, shell.CallError):
+                # XXX: Maybe should also do app.Error
                 logging.error("%s in %s" % (traceback.format_exc(), d.location))
             except KeyboardInterrupt:
                 raise
index ed69dc37c428bd1d453dd557a93b6b088388449b..005a60218e2593ca5437bb3c9edfd116be5a08a3 100644 (file)
@@ -1,21 +1,19 @@
 """
 Object model for querying information and manipulating deployments
-of autoinstalls.  Every :class:`Deployment` has an :class:`ApplicationVersion`
-which in turn has an :class:`Application`.
+of autoinstalls.  Every :class:`Deployment` has an :class:`app.ApplicationVersion`
+which in turn has an :class:`app.Application`.
 """
 
 import os.path
 import fileinput
 import dateutil.parser
-import distutils.version
 import tempfile
 import logging
 import shutil
 import decorator
-import functools
 
 import wizard
-from wizard import git, old_log, scripts, shell, util
+from wizard import app, git, old_log, scripts, shell, util
 
 ## -- Global Functions --
 
@@ -40,7 +38,7 @@ def parse_install_lines(show, versions_store, yield_errors = False, user = None)
     log output.
     """
     if not show:
-        show = applications()
+        show = app.applications()
     elif isinstance(show, str):
         # otherwise, frozenset will treat string as an iterable
         show = frozenset([show])
@@ -51,7 +49,7 @@ def parse_install_lines(show, versions_store, yield_errors = False, user = None)
         try:
             d = Deployment.parse(line)
             name = d.application.name
-        except NoSuchApplication as e:
+        except app.NoSuchApplication as e:
             if yield_errors:
                 yield e
             continue
@@ -229,7 +227,7 @@ class Deployment(object):
         return os.path.join(self.scripts_dir, 'version')
     @property
     def application(self):
-        """The :class:`Application` of this deployment."""
+        """The :class:`app.Application` of this deployment."""
         return self.app_version.application
     @property
     def old_log(self):
@@ -249,13 +247,13 @@ class Deployment(object):
         return self.app_version.version
     @property
     def app_version(self):
-        """The :class:`ApplicationVersion` of this deployment."""
+        """The :class:`app.ApplicationVersion` of this deployment."""
         if not self._app_version:
             if os.path.isdir(os.path.join(self.location, ".git")):
                 try:
                     with util.ChangeDirectory(self.location):
                         appname, _, version = git.describe().partition('-')
-                    self._app_version = ApplicationVersion.make(appname, version)
+                    self._app_version = app.ApplicationVersion.make(appname, version)
                 except shell.CallError:
                     pass
         if not self._app_version:
@@ -278,7 +276,7 @@ class Deployment(object):
         except ValueError:
             return ProductionCopy(line) # lazy loaded version
         try:
-            return ProductionCopy(location, version=ApplicationVersion.parse(deploydir))
+            return ProductionCopy(location, version=app.ApplicationVersion.parse(deploydir))
         except Error as e:
             e.location = location
             raise e
@@ -365,303 +363,12 @@ class WorkingCopy(Deployment):
         """
         return self.application.prepareMerge(self)
 
-class Application(object):
-    """
-    Represents an application, i.e. mediawiki or phpbb.
-
-    .. note::
-        Many of these methods assume a specific working
-        directory; prefer using the corresponding methods
-        in :class:`Deployment` and its subclasses.
-    """
-    #: String name of the application
-    name = None
-    #: Dictionary of version strings to :class:`ApplicationVersion`.
-    #: See also :meth:`makeVersion`.
-    versions = None
-    #: List of files that need to be modified when parametrizing.
-    #: This is a class-wide constant, and should not normally be modified.
-    parametrized_files = []
-    #: Keys that are used in older versions of the application, but
-    #: not for the most recent version.
-    deprecated_keys = []
-    def __init__(self, name):
-        self.name = name
-        self.versions = {}
-        # cache variables
-        self._extractors = {}
-        self._substitutions = {}
-    def repository(self, srv_path):
-        """
-        Returns the Git repository that would contain this application.
-        ``srv_path`` corresponds to ``options.srv_path`` from the global baton.
-        """
-        repo = os.path.join(srv_path, self.name + ".git")
-        if not os.path.isdir(repo):
-            repo = os.path.join(srv_path, self.name, ".git")
-            if not os.path.isdir(repo):
-                raise NoRepositoryError(self.name)
-        return repo
-    def makeVersion(self, version):
-        """
-        Creates or retrieves the :class:`ApplicationVersion` singleton for the
-        specified version.
-        """
-        if version not in self.versions:
-            self.versions[version] = ApplicationVersion(distutils.version.LooseVersion(version), self)
-        return self.versions[version]
-    def extract(self, deployment):
-        """Extracts wizard variables from a deployment."""
-        result = {}
-        for k,extractor in self.extractors.items():
-            result[k] = extractor(deployment)
-        return result
-    def parametrize(self, deployment):
-        """
-        Takes a generic source checkout and parametrizes
-        it according to the values of deployment.  This function
-        operates on the current working directory.
-        """
-        variables = deployment.extract()
-        for file in self.parametrized_files:
-            try:
-                contents = open(file, "r").read()
-            except IOError:
-                continue
-            for key, value in variables.items():
-                if value is None: continue
-                contents = contents.replace(key, value)
-            f = open(file, "w")
-            f.write(contents)
-    def resolveConflicts(self, deployment):
-        """
-        Resolves conflicted files in the current working
-        directory.  Returns whether or not all conflicted
-        files were resolved or not.  Fully resolved files are
-        added to the index, but no commit is made.  By default
-        this is a no-op and returns ``False``.
-        """
-        return False
-    def prepareMerge(self, deployment):
-        """
-        Performs various edits to files in the current working directory in
-        order to make a merge go more smoothly.  This is usually
-        used to fix botched line-endings.  If you add new files,
-        you have to 'git add' them; this is not necessary for edits.
-        By default this is a no-op.
-        """
-        pass
-    def prepareConfig(self, deployment):
-        """
-        Takes a deployment and replaces any explicit instances
-        of a configuration variable with generic ``WIZARD_*`` constants.
-        The default implementation uses :attr:`substitutions`;
-        you can override this method to provide arbitrary extra
-        behavior.
-        """
-        for key, subst in self.substitutions.items():
-            subs = subst(deployment)
-            if not subs and key not in self.deprecated_keys:
-                logging.warning("No substitutions for %s" % key)
-    def install(self, version, options):
-        """
-        Run for 'wizard configure' (and, by proxy, 'wizard install')
-        to configure an application.  This assumes that the current
-        working directory is a deployment.  (This function does not take
-        a :class:`Deployment` as a parameter, as those operations are
-        not meaningful yet.)
-        """
-        raise NotImplemented
-    def upgrade(self, deployment, version, options):
-        """
-        Run for 'wizard upgrade' to upgrade database schemas and other
-        non-versioned data in an application.  This assumes that
-        the current working directory is the deployment.
-        """
-        raise NotImplemented
-    def backup(self, deployment, options):
-        """
-        Run for 'wizard backup' and upgrades to backup database schemas
-        and other non-versioned data in an application.  This assumes
-        that the current working directory is the deployment.
-        """
-        raise NotImplemented
-    def restore(self, deployment, backup, options):
-        """
-        Run for 'wizard restore' and failed upgrades to restore database
-        and other non-versioned data to a backed up version.  This assumes
-        that the current working directory is the deployment.
-        """
-        raise NotImplemented
-    def detectVersion(self, deployment):
-        """
-        Checks source files to determine the version manually.  This assumes
-        that the current working directory is the deployment.
-        """
-        return None
-    def checkWeb(self, deployment, output=None):
-        """
-        Checks if the autoinstall is viewable from the web.  To get
-        the HTML source that was retrieved, pass a variable containing
-        an empty list to ``output``; it will be mutated to have its
-        first element be the output.
-        """
-        raise NotImplemented
-    def checkConfig(self, deployment):
-        """
-        Checks whether or not an autoinstall has been configured/installed
-        for use.  Assumes that the current working directory is the deployment.
-        """
-        raise NotImplemented
-    @property
-    def extractors(self):
-        """
-        Dictionary of variable names to extractor functions.  These functions
-        take a :class:`Deployment` as an argument and return the value of
-        the variable, or ``None`` if it could not be found.
-        See also :func:`wizard.app.filename_regex_extractor`.
-        """
-        return {}
-    @property
-    def substitutions(self):
-        """
-        Dictionary of variable names to substitution functions.  These functions
-        take a :class:`Deployment` as an argument and modify the deployment such
-        that an explicit instance of the variable is released with the generic
-        WIZARD_* constant.  See also :func:`wizard.app.filename_regex_substitution`.
-        """
-        return {}
-    @staticmethod
-    def make(name):
-        """Makes an application, but uses the correct subtype if available."""
-        try:
-            __import__("wizard.app." + name)
-            return getattr(wizard.app, name).Application(name)
-        except ImportError:
-            return Application(name)
-
-class ApplicationVersion(object):
-    """Represents an abstract notion of a version for an application, where
-    ``version`` is a :class:`distutils.version.LooseVersion` and
-    ``application`` is a :class:`Application`."""
-    #: The :class:`distutils.version.LooseVersion` of this instance.
-    version = None
-    #: The :class:`Application` of this instance.
-    application = None
-    def __init__(self, version, application):
-        self.version = version
-        self.application = application
-    @property
-    def tag(self):
-        """
-        Returns the name of the git describe tag for the commit the user is
-        presently on, something like mediawiki-1.2.3-scripts-4-g123abcd
-        """
-        return "%s-%s" % (self.application, self.version)
-    @property
-    def scripts_tag(self):
-        """
-        Returns the name of the Git tag for this version.
-        """
-        end = str(self.version).partition('-scripts')[2].partition('-')[0]
-        return "%s-scripts%s" % (self.pristine_tag, end)
-    @property
-    def pristine_tag(self):
-        """
-        Returns the name of the Git tag for the pristine version corresponding
-        to this version.
-        """
-        return "%s-%s" % (self.application.name, str(self.version).partition('-scripts')[0])
-    def __cmp__(self, y):
-        return cmp(self.version, y.version)
-    @staticmethod
-    def parse(value):
-        """
-        Parses a line from the :term:`versions store` and return
-        :class:`ApplicationVersion`.
-
-        Use this only for cases when speed is of primary importance;
-        the data in version is unreliable and when possible, you should
-        prefer directly instantiating a Deployment and having it query
-        the autoinstall itself for information.
-
-        The `value` to parse will vary.  For old style installs, it
-        will look like::
-
-           /afs/athena.mit.edu/contrib/scripts/deploy/APP-x.y.z
-
-        For new style installs, it will look like::
-
-           APP-x.y.z-scripts
-        """
-        name = value.split("/")[-1]
-        try:
-            if name.find("-") != -1:
-                app, _, version = name.partition("-")
-            else:
-                # kind of poor, maybe should error.  Generally this
-                # will actually result in a not found error
-                app = name
-                version = "trunk"
-        except ValueError:
-            raise DeploymentParseError(value)
-        return ApplicationVersion.make(app, version)
-    @staticmethod
-    def make(app, version):
-        """
-        Makes/retrieves a singleton :class:`ApplicationVersion` from
-        a``app`` and ``version`` string.
-        """
-        try:
-            # defer to the application for version creation to enforce
-            # singletons
-            return applications()[app].makeVersion(version)
-        except KeyError:
-            raise NoSuchApplication(app)
-
 ## -- Exceptions --
 
 class Error(wizard.Error):
     """Base error class for this module"""
     pass
 
-class NoSuchApplication(Error):
-    """
-    You attempted to reference a :class:`Application` named
-    ``app``, which is not recognized by Wizard.
-    """
-    #: The name of the application that does not exist.
-    app = None
-    #: The location of the autoinstall that threw this variable.
-    #: This should be set by error handling code when it is availble.
-    location = None
-    def __init__(self, app):
-        self.app = app
-
-class DeploymentParseError(Error):
-    """
-    Could not parse ``value`` from :term:`versions store`.
-    """
-    #: The value that failed to parse.
-    value = None
-    #: The location of the autoinstall that threw this variable.
-    #: This should be set by error handling code when it is available.
-    location = None
-    def __init__(self, value):
-        self.value = value
-
-class NoRepositoryError(Error):
-    """
-    :class:`Application` does not appear to have a Git repository
-    in the normal location.
-    """
-    #: The name of the application that does not have a Git repository.
-    app = None
-    def __init__(self, app):
-        self.app = app
-    def __str__(self):
-        return """Could not find Git repository for '%s'.  If you would like to use a local version, try specifying --srv-path or WIZARD_SRV_PATH.""" % self.app
-
 class NotMigratedError(Error):
     """
     The deployment contains a .scripts-version file, but no .git
@@ -833,18 +540,3 @@ on the application.  You can specify this manually using
 the WIZARD_WEB_HOST and WIZARD_WEB_PATH environment
 variables."""
 
-_application_list = [
-    "mediawiki", "wordpress", "joomla", "e107", "gallery2",
-    "phpBB", "advancedbook", "phpical", "trac", "turbogears", "django",
-    # these are technically deprecated
-    "advancedpoll", "gallery",
-]
-_applications = None
-
-def applications():
-    """Hash table for looking up string application name to instance"""
-    global _applications
-    if not _applications:
-        _applications = dict([(n,Application.make(n)) for n in _application_list ])
-    return _applications
-
index 1a853bf36c2f7fd3687d1d2d0f299ed902df7289..6a81e8df5fd015565f912073c17e691c37aff514 100644 (file)
@@ -3,6 +3,7 @@ import sys
 import dateutil.parser
 
 import wizard
+from wizard import app
 import wizard.deploy # to break circular loop
 
 # This code operates off of the assumption of .scripts-version, which
@@ -53,8 +54,8 @@ class DeployLog(list):
                 rev.source = DeploySource.parse(line)
             elif i == 3:
                 try:
-                    rev.version = wizard.deploy.ApplicationVersion.parse(line)
-                except wizard.deploy.Error as e:
+                    rev.version = app.ApplicationVersion.parse(line)
+                except (wizard.deploy.Error, app.Error) as e:
                     e.location = deployment.location
                     raise e, None, sys.exc_info()[2]
             else:
@@ -69,9 +70,9 @@ class DeployRevision(object):
     this revision, what application version this is, etc."""
     def __init__(self, datetime=None, user=None, source=None, version=None):
         """ `datetime`  Time this revision was deployed
-            `user`      Person who deployed this revision, in user@host format.
-            `source`    Instance of DeploySource
-            `version`   Instance of ApplicationVersion
+            `user`      Person who deployed this revision, in ``user@host`` format.
+            `source`    Instance of :class:`DeploySource`
+            `version`   Instance of :class:`app.ApplicationVersion`
         Note: This object is typically built incrementally."""
         self.datetime = datetime
         self.user = user
index 95e7bb80c54736cbc7f1f4531757897aa8d27dc8..9de6bcb617dae0d724b272fe5456f93637b5f2d0 100644 (file)
@@ -2,7 +2,7 @@ import distutils.version
 import datetime
 import dateutil.tz
 
-from wizard import deploy
+from wizard import app, deploy
 
 def test_deployment_parse():
     result = deploy.Deployment.parse("/afs/athena.mit.edu/user/e/z/ezyang/web_scripts/test-wiki:/afs/athena.mit.edu/contrib/scripts/deploy/mediawiki-1.11.0\n")
@@ -14,7 +14,7 @@ def test_deployment_parse_nosuchapplication():
     try:
         deploy.Deployment.parse("a:/foo/obviouslybogus-1.11.0\n")
         assert False
-    except deploy.NoSuchApplication:
+    except app.NoSuchApplication:
         pass
 
 def test_deployment_from_dir():
index a6d697bb361998e35d3817d06bce509e7dc7c43a..16e215c8d2e163da8f99f5ba9a53ce56bab2e4c1 100644 (file)
@@ -2,7 +2,7 @@ import os.path
 from dateutil.tz import tzoffset
 from datetime import datetime
 
-from wizard import deploy, old_log
+from wizard import app, deploy, old_log
 
 def getTestFile(file):
     return os.path.join(os.path.dirname(os.path.abspath(__file__)), file)
@@ -16,11 +16,11 @@ def test_deploy_log_load():
     assert isinstance(dlog[0].source, old_log.TarballInstall)
     assert dlog[0].source.location == "/afs/athena.mit.edu/contrib/scripts/deploy/mediawiki.tar.gz"
     assert dlog[0].source.isDev == False
-    assert dlog[0].version == deploy.applications()["mediawiki"].makeVersion('1.5.6')
+    assert dlog[0].version == app.applications()["mediawiki"].makeVersion('1.5.6')
 
     assert dlog[1].datetime == datetime(2007, 10, 17, 3, 38, 2, tzinfo=tzoffset(None, -4 * 60 * 60))
     assert dlog[1].user == "quentin@QUICHE-LORRAINE.MIT.EDU"
     assert isinstance(dlog[1].source, old_log.OldUpdate)
     assert dlog[1].source.isDev == True
-    assert dlog[1].version == deploy.applications()["mediawiki"].makeVersion('1.5.6')
+    assert dlog[1].version == app.applications()["mediawiki"].makeVersion('1.5.6')