changeset 80:99f0d19d0285

Integrated MoinMoin:PackageInstaller and zip support. imported from: moin--main--1.5--patch-82
author Alexander Schremmer <alex@alexanderweb.de.tla>
date Thu, 06 Oct 2005 16:00:49 +0000
parents e51cb51522d1
children dcbfffac3f9c
files MoinMoin/_tests/test_packages.py MoinMoin/action/AttachFile.py MoinMoin/packages.py
diffstat 3 files changed, 628 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/_tests/test_packages.py	Thu Oct 06 16:00:49 2005 +0000
@@ -0,0 +1,66 @@
+# -*- coding: iso-8859-1 -*-
+"""
+MoinMoin - MoinMoin.packages tests
+
+@copyright: 2005 MoinMoin:AlexanderSchremmer
+@license: GNU GPL, see COPYING for details.
+"""
+
+import unittest
+from MoinMoin.Page import Page
+from MoinMoin._tests import TestConfig
+from MoinMoin._tests import TestSkiped as TestSkipped
+from MoinMoin.packages import Package, ScriptEngine, MOIN_PACKAGE_FILE, packLine, unpackLine
+
+class DebugPackage(Package, ScriptEngine):
+    """ Used for debugging, does not need a real .zip file. """
+    def __init__(self, request, filename):
+        Package.__init__(self, request)
+        ScriptEngine.__init__(self)
+        self.filename = filename
+
+    def extract_file(self, filename):
+        if filename == MOIN_PACKAGE_FILE:
+            return u"""moinmoinpackage|1
+print|foo
+ReplaceUnderlay|testdatei|TestSeite2
+DeletePage|TestSeite2|Test ...
+IgnoreExceptions|True
+DeletePage|TestSeiteDoesNotExist|Test ...
+IgnoreExceptions|False
+AddRevision|foofile|FooPage
+DeletePage|FooPage|Test ...
+setthemename|foo
+#foobar
+installplugin|foo|local|parser|testy
+""".encode("utf-8")
+        else:
+            return "Hello world, I am the file " + filename.encode("utf-8")
+
+    def filelist(self):
+        return [MOIN_PACKAGE_FILE, "foo"]
+
+    def isPackage(self):
+        return True
+    
+class PackagesTests(unittest.TestCase):
+    """ Tests various things in the packages package. Note that this package does
+        not care to clean up and needs to run in a test wiki because of that. """
+
+    def setUp(self):
+        if not getattr(self.request.cfg, 'is_test_wiki', False):
+            raise TestSkipped('This test needs to be run using the test wiki.')
+   
+    def testBasicPackageThings(self):
+        myPackage = DebugPackage(self.request, 'test')
+        myPackage.installPackage()
+        self.assertEqual(myPackage.msg, "foo\n")
+        testseite2 = Page(self.request, 'TestSeite2')
+        self.assertEqual(testseite2.getPageText(), "Hello world, I am the file testdatei")
+        self.assert_(testseite2.isUnderlayPage())
+        self.assert_(not Page(self.request, 'FooPage').exists())
+
+class QuotingTests(unittest.TestCase):
+    def testQuoting(self):
+        for line in ([':foo', 'is\\', 'ja|', u't|ü', u'baAzß'], [], ['', '']):
+            self.assertEqual(line, unpackLine(packLine(line)))
--- a/MoinMoin/action/AttachFile.py	Thu Oct 06 15:56:14 2005 +0000
+++ b/MoinMoin/action/AttachFile.py	Thu Oct 06 16:00:49 2005 +0000
@@ -16,15 +16,17 @@
        to view the content of the file
 
     To insert an attachment into the page, use the "attachment:" pseudo
-    schema.  
+    schema.
 
     @copyright: 2001 by Ken Sugino (sugino@mediaone.net)
     @copyright: 2001-2004 by Jürgen Hermann <jh@web.de>
+    @copyright: 2005 R. Bauer
+    @copyright: 2005 MoinMoin:AlexanderSchremmer
     @license: GNU GPL, see COPYING for details.
 """
 
-import os, mimetypes, time, urllib
-from MoinMoin import config, user, util, wikiutil
+import os, mimetypes, time, urllib, zipfile
+from MoinMoin import config, user, util, wikiutil, packages
 from MoinMoin.Page import Page
 from MoinMoin.util import MoinMoinNoFooter, filesys, web
 
@@ -203,6 +205,8 @@
         label_get = _("get")
         label_edit = _("edit")
         label_view = _("view")
+        label_unzip = _("unzip")
+        label_install = _("install")
 
         for file in files:
             fsize = float(os.stat(os.path.join(attach_dir,file).encode(config.charset))[6]) # in byte
@@ -218,6 +222,8 @@
                         'urlfile': urlfile, 'label_del': label_del,
                         'base': base, 'label_edit': label_edit,
                         'label_view': label_view,
+                        'label_unzip': label_unzip,
+                        'label_install': label_install,
                         'get_url': get_url, 'label_get': label_get,
                         'file': wikiutil.escape(file), 'fsize': fsize,
                         'pagename': pagename}
@@ -232,6 +238,15 @@
             else:
                 viewlink = '<a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&amp;do=view&amp;target=%(urlfile)s">%(label_view)s</a>' % parmdict
 
+            if (packages.ZipPackage(request, os.path.join(attach_dir, file).encode(config.charset)).isPackage() and
+                request.user.name in request.cfg.superuser):
+                viewlink += ' | <a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&amp;do=install&amp;target=%(urlfile)s">%(label_install)s</a>' % parmdict
+            elif (zipfile.is_zipfile(os.path.join(attach_dir,file).encode(config.charset)) and
+                request.user.may.read(pagename) and request.user.may.delete(pagename)
+                and request.user.may.write(pagename)):
+                viewlink += ' | <a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&amp;do=unzip&amp;target=%(urlfile)s">%(label_unzip)s</a>' % parmdict
+
+
             parmdict['viewlink'] = viewlink
             parmdict['del_link'] = del_link
             str = str + ('<li>[%(del_link)s'
@@ -419,6 +434,16 @@
             get_file(pagename, request)
         else:
             msg = _('You are not allowed to get attachments from this page.')
+    elif request.form['do'][0] == 'unzip':
+         if request.user.may.delete(pagename) and request.user.may.read(pagename) and request.user.may.write(pagename):
+            unzip_file(pagename, request)
+         else:
+            msg = _('You are not allowed to unzip attachments of this page.')
+    elif request.form['do'][0] == 'install':
+         if request.user.name in request.cfg.superuser:
+            install_package(pagename, request)
+         else:
+            msg = _('You are not allowed to install files.')
     elif request.form['do'][0] == 'view':
         if request.user.may.read(pagename):
             view_file(pagename, request)
@@ -587,6 +612,91 @@
 
     raise MoinMoinNoFooter
 
+def install_package(pagename, request):
+    _ = request.getText
+
+    target, targetpath = _access_file(pagename, request)
+    if not target:
+        return
+
+    package = packages.ZipPackage(request, targetpath)
+
+    if package.isPackage():
+        if package.installPackage():
+            msg=_("Attachment '%(filename)s' installed.") % {'filename': wikiutil.escape(target)}
+        else:
+            msg=_("Installation of '%(filename)s' failed.") % {'filename': wikiutil.escape(target)}
+        if package.msg != "":
+            msg += "<br><pre>" + wikiutil.escape(package.msg) + "</pre>"
+    else:
+        msg = _('The file %s is not a MoinMoin package file.' % wikiutil.escape(target))
+
+    upload_form(pagename, request, msg=msg)
+
+def unzip_file(pagename, request):
+    _ = request.getText
+    valid_pathname = lambda name: (name.find('/') == -1) and (name.find('\\') == -1)
+
+    filename, fpath = _access_file(pagename, request)
+    if not filename: return # error msg already sent in _access_file
+
+    attachment_path = getAttachDir(request, pagename)
+    single_file_size = 2.0 * 1000**2
+    attachments_file_space = 200.0 * 1000**2
+
+    files = _get_files(request, pagename)
+
+    msg = ""
+    if files:
+        fsize = 0.0
+        for file in files:
+            fsize += float(os.stat(getFilename(request, pagename, file))[6]) # in byte
+
+        available_attachments_file_space = attachments_file_space - fsize
+
+        if zipfile.is_zipfile(fpath):
+            zf = zipfile.ZipFile(fpath)
+            sum_size_over_all_valid_files = 0.0
+            for name in zf.namelist():
+                if valid_pathname(name):
+                    sum_size_over_all_valid_files += zf.getinfo(name).file_size
+
+            if sum_size_over_all_valid_files < available_attachments_file_space:
+                valid_name = False
+                for name in zf.namelist():
+                    if valid_pathname(name):
+                        zi = zf.getinfo(name)
+                        if zi.file_size < single_file_size:
+                            new_file = getFilename(request, pagename, name)
+                            if not os.path.exists(new_file):
+                                outfile = open(new_file, 'wb')
+                                outfile.write(zf.read(name))
+                                outfile.close()
+                                # it's not allowed to zip a zip file so it is dropped
+                                if zipfile.is_zipfile(new_file):
+                                    os.unlink(new_file)
+                                else:
+                                    valid_name = True
+                                    os.chmod(new_file, 0666 & config.umask)
+                                    _addLogEntry(request, 'ATTNEW', pagename, new_file)
+
+                if valid_name:
+                    msg=_("Attachment '%(filename)s' unzipped.") % {'filename': filename}
+                else:
+                    msg=_("Attachment '%(filename)s' not unzipped because the "
+                          "files are too big, .zip files only, exist already or "
+                          "reside in folders.") % {'filename': filename}
+            else:
+                msg=_("Attachment '%(filename)s' could not be unzipped because"
+                      " the resulting files would be too large (%(space)d kB"
+                      " missing).") % {'filename': filename,
+                    'space': (sum_size_over_all_valid_files -
+                              available_attachments_file_space) / 1000}
+        else:
+            msg = _('The file %(target) is not a .zip file.' % target)
+
+    upload_form(pagename, request, msg=wikiutil.escape(msg))
+
 def send_viewfile(pagename, request):
     _ = request.getText
 
@@ -614,6 +724,21 @@
             request.write("</pre>")
             return
 
+    package = packages.ZipPackage(request, fpath)
+    if package.isPackage():
+        request.write("<pre><b>%s</b>\n%s</pre>" % (_("Package script:"),wikiutil.escape(package.getScript())))
+        return
+
+    import zipfile
+    if zipfile.is_zipfile(fpath):
+        zf = zipfile.ZipFile(fpath, mode='r')
+        request.write("<pre>%-46s %19s %12s\n" % (_("File Name"), _("Modified")+" "*5, _("Size")))
+        for zinfo in zf.filelist:
+            date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time
+            request.write(wikiutil.escape("%-46s %s %12d\n" % (zinfo.filename, date, zinfo.file_size)))
+        request.write("</pre>")
+        return
+
     request.write('<p>' + _("Unknown file type, cannot display this attachment inline.") + '</p>')
     request.write('<a href="%s">%s</a>' % (
         getAttachUrl(pagename, filename, request, escaped=1), wikiutil.escape(filename)))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/packages.py	Thu Oct 06 16:00:49 2005 +0000
@@ -0,0 +1,434 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - Package Installer
+
+    @copyright: 2005 by MoinMoin:AlexanderSchremmer
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import os
+import sys
+import zipfile
+
+from MoinMoin import config, wikiutil, caching
+from MoinMoin.Page import Page
+from MoinMoin.PageEditor import PageEditor
+
+MOIN_PACKAGE_FILE = 'MOIN_PACKAGE'
+MAX_VERSION = 1
+
+# Exceptions
+class PackageException(Exception):
+    """ Raised if the package is broken. """
+    pass
+
+class ScriptException(Exception):
+    """ Raised when there is a problem in the script. """
+
+    def __unicode__(self):
+        """ Return unicode error message """
+        if isinstance(self.args[0], str):
+            return unicode(self.args[0], config.charset)
+        else:
+            return unicode(self.args[0])
+
+class RuntimeScriptException(ScriptException):
+    """ Raised when the script problem occurs at runtime. """
+
+class ScriptExit(Exception):
+    """ Raised by the script commands when the script should quit. """
+
+# Parsing and (un)quoting for script files
+def packLine(list):
+    return '|'.join([x.replace('\\', '\\\\').replace('|', r'\|') for x in list])
+
+def unpackLine(string):
+    result = []
+    token = None
+    escaped = False
+    for x in string:
+        if token is None:
+            token = ""
+        if escaped and x in ('\\', '|'):
+            token += x
+            escaped = False
+            continue
+        escaped = (x == '\\')
+        if escaped:
+            continue
+        if x == '|':
+            result.append(token)
+            token = ""
+        else:
+            token += x
+    if token is not None:
+        result.append(token)
+    return result
+
+class ScriptEngine:
+    """
+    The script engine supplies the needed commands to execute the installation
+    script.
+    """
+
+    def _toBoolean(string):
+        """
+        Converts the parameter to a boolean value by recognising different
+        truth literals.
+        """
+        return (string.lower() in ('yes', 'true', '1'))
+    _toBoolean = staticmethod(_toBoolean)
+
+    def _extractToFile(self, source, target):
+        """ Extracts source and writes the contents into target. """
+        # TODO, add file dates
+        f = open(target, "wb")
+        f.write(self.extract_file(source))
+        f.close()
+
+    def __init__(self):
+        self.themename = None
+        self.ignoreExceptions = False
+        self.goto = 0
+
+    def do_print(self, *param):
+        """ Prints the parameters into output of the script. """
+        self.msg += '; '.join(param) + "\n"
+
+    def do_exit(self):
+        """ Exits the script. """
+        raise ScriptExit
+
+    def do_ignoreexceptions(self, boolean):
+        """ Sets the ignore exceptions setting. If exceptions are ignored, the
+        script does not stop if one is encountered. """
+        self.ignoreExceptions = self._toBoolean(boolean)
+
+    def do_ensureversion(self, version, lines=0):
+        """ Ensures that the version of MoinMoin is greater or equal than
+            version. If lines is unspecified, the script aborts. Otherwise,
+            the next lines (amount specified by lines) are not executed.
+
+        @param version: required version of MoinMoin (e.g. "1.3.4")
+        @param lines:   lines to ignore
+        """
+        from MoinMoin.version import release
+        version_int = [int(x) for x in version.split(".")]
+        release = [int(x) for x in release.split(".")]
+        if version_int > release:
+            if lines > 0:
+                self.goto = lines
+            else:
+                raise RuntimeScriptException(_("The package needs a newer version"
+                                               " of MoinMoin (at least %s).") %
+                                             version)
+
+    def do_setthemename(self, themename):
+        """ Sets the name of the theme which will be altered next. """
+        self.themename = wikiutil.taintfilename(str(themename))
+
+    def do_copythemefile(self, filename, type, target):
+        """ Copies a theme-related file (CSS, PNG, etc.) into a directory of the
+        current theme.
+
+        @param filename: name of the file in this package
+        @param type:   the subdirectory of the theme directory, e.g. "css"
+        @param target: filename, e.g. "screen.css"
+        """
+        _ = self.request.getText
+        if self.themename is None:
+            raise RuntimeScriptException(_("The theme name is not set."))
+        sa = getattr(self.request, "sareq", None)
+        if sa is None:
+            raise RuntimeScriptException(_("Installing theme files is only supported "
+                                           "for standalone type servers."))
+        htdocs_dir = sa.server.htdocs
+        theme_file = os.path.join(htdocs_dir, self.themename,
+                                  wikiutil.taintfilename(type),
+                                  wikiutil.taintfilename(target))
+        theme_dir = os.path.dirname(theme_file)
+        if not os.path.exists(theme_dir):
+            os.makedirs(theme_dir, 0777 & config.umask)
+        self._extractToFile(filename, theme_file)
+
+    def do_installplugin(self, filename, visibility, ptype, target):
+        """
+        Installs a python code file into the appropriate directory.
+
+        @param filename: name of the file in this package
+        @param visibility: 'local' will copy it into the plugin folder of the
+            current wiki. 'global' will use the folder of the MoinMoin python
+            package.
+        @param ptype: the type of the plugin, e.g. "parser"
+        @param target: the filename of the plugin, e.g. wiki.py
+        """
+        visibility = visibility.lower()
+        ptype = wikiutil.taintfilename(ptype.lower())
+
+        if visibility == 'global':
+            basedir = os.path.dirname(__import__("MoinMoin").__file__)
+        elif visibility == 'local':
+            basedir = self.request.cfg.plugin_dir
+
+        target = os.path.join(basedir, ptype, wikiutil.taintfilename(target))
+
+        self._extractToFile(filename, target)
+        wikiutil._wiki_plugins = {}
+
+    def do_installpackage(self, pagename, filename):
+        """
+        Installs a package.
+
+        @param pagename: Page where the file is attached. Or in 2.0, the file itself.
+        @param filename: Filename of the attachment (just applicable for MoinMoin < 2.0)
+        """
+        _ = self.request.getText
+
+        attachments = Page(self.request, pagename).getPagePath("attachments", check_create=0)
+        package = ZipPackage(self.request, os.path.join(attachments, wikiutil.taintfilename(filename)))
+
+        if package.isPackage():
+            if not package.installPackage():
+                raise RuntimeScriptException(_("Installation of '%(filename)s' failed.") % {
+                    'filename': filename} + "\n" + package.msg)
+        else:
+            raise RuntimeScriptException(_('The file %s is not a MoinMoin package file.' % filename))
+
+        self.msg += package.msg
+
+    def do_addrevision(self, filename, pagename, author=u"Scripting Subsystem", comment=u"", trivial = u"No"):
+        """ Adds a revision to a page.
+
+        @param filename: name of the file in this package
+        @param pagename: name of the target page
+        @param author:   user name of the editor (optional)
+        @param comment:  comment related to this revision (optional)
+        @param trivial:  boolean, if it is a trivial edit
+        """
+        _ = self.request.getText
+        trivial = self._toBoolean(trivial)
+
+        page = PageEditor(self.request, pagename, do_editor_backup=0, uid_override=author)
+        page.saveText(self.extract_file(filename), 0, trivial=trivial, comment=comment)
+
+        page.clean_acl_cache()
+
+    def do_deletepage(self, pagename, comment="Deleted by the scripting subsystem."):
+        """ Marks a page as deleted (like the DeletePage action).
+
+        @param pagename: page to delete
+        @param comment:  the related comment (optional)
+        """
+        _ = self.request.getText
+        page = PageEditor(self.request, pagename, do_editor_backup=0)
+        if not page.exists():
+            raise RuntimeScriptException(_("The page %s does not exist.") % pagename)
+
+        page.deletePage(comment)
+
+    def do_replaceunderlay(self, filename, pagename):
+        """ Overwrites underlay pages. Implementational detail: This needs to be
+            kept in sync with the page class.
+
+        @param filename: name of the file in the package
+        @param pagename: page to be overwritten
+        """
+        page = Page(self.request, pagename)
+
+        pagedir = page.getPagePath(use_underlay=1, check_create=1)
+
+        revdir = os.path.join(pagedir, 'revisions')
+        cfn = os.path.join(pagedir,'current')
+
+        revstr = '%08d' % 1
+        if not os.path.exists(revdir):
+            os.mkdir(revdir)
+            os.chmod(revdir, 0777 & config.umask)
+
+        f = open(cfn, 'w')
+        f.write(revstr + "\n")
+        f.close()
+        os.chmod(cfn, 0666 & config.umask)
+
+        pagefile = os.path.join(revdir, revstr)
+        self._extractToFile(filename, pagefile)
+        os.chmod(pagefile, 0666 & config.umask)
+
+        # Clear caches
+        try:
+            del self.request.cfg.DICTS_DATA
+        except AttributeError:
+            pass
+        self.request.pages = {}
+        caching.CacheEntry(self.request, 'wikidicts', 'dicts_groups').remove()
+        page.clean_acl_cache()
+
+    def runScript(self, commands):
+        """ Runs the commands.
+
+        @param commands: list of strings which contain a command each
+        @return True on success
+        """
+        _ = self.request.getText
+
+        headerline = unpackLine(commands[0])
+
+        if headerline[0].lower() != "MoinMoinPackage".lower():
+            raise PackageException(_("Invalid package file header."))
+
+        self.revision = int(headerline[1])
+        if self.revision > MAX_VERSION:
+            raise PackageException(_("Package file format unsupported."))
+
+        lineno = 1
+        success = True
+
+        for line in commands[1:]:
+            lineno += 1
+            if self.goto > 0:
+                self.goto -= 1
+                continue
+
+            if line.startswith("#"):
+                continue
+            elements = unpackLine(line)
+            fnname = elements[0].strip().lower()
+            if fnname == '':
+                continue
+            try:
+                fn = getattr(self, "do_" + fnname)
+            except AttributeError:
+                self.msg += u"Exception RuntimeScriptException (line %i): %s\n" % (
+                    lineno, _("Unknown function %s in line %i.") % (elements[0], lineno))
+                success = False
+                break
+
+            try:
+                fn(*elements[1:])
+            except ScriptExit:
+                break
+            except TypeError, e:
+                self.msg += u"Exception %s (line %i): %s\n" % (e.__class__.__name__, lineno, unicode(e))
+                success = False
+                break
+            except RuntimeScriptException, e:
+                if not self.ignoreExceptions:
+                    self.msg += u"Exception %s (line %i): %s\n" % (e.__class__.__name__, lineno, unicode(e))
+                    success = False
+                    break
+
+        return success
+
+class Package:
+    """ A package consists of a bunch of files which can be installed. """
+    def __init__(self, request):
+        self.request = request
+        self.msg = ""
+
+    def installPackage(self):
+        """ Opens the package and executes the script. """
+
+        _ = self.request.getText
+
+        if not self.isPackage():
+            raise PackageException(_("The file %s was not found in the package.") % MOIN_PACKAGE_FILE)
+
+        commands = self.getScript().splitlines()
+
+        return self.runScript(commands)
+
+    def getScript(self):
+        """ Returns the script. """
+        return self.extract_file(MOIN_PACKAGE_FILE).decode("utf-8").replace(u"\ufeff", "")
+
+    def extract_file(self, filename):
+        """ Returns the contents of a file in the package. """
+        raise NotImplementedException
+
+    def filelist(self):
+        """ Returns a list of all files. """
+        raise NotImplementedException
+
+    def isPackage(self):
+        """ Returns true if this package is recognised. """
+        raise NotImplementedException
+
+class ZipPackage(Package, ScriptEngine):
+    """ A package that reads its files from a .zip file. """
+    def __init__(self, request, filename):
+        """ Initialise the package.
+
+        @param request RequestBase instance
+        @param filename filename of the .zip file
+        """
+
+        Package.__init__(self, request)
+        ScriptEngine.__init__(self)
+        self.filename = filename
+
+        self._isZipfile = zipfile.is_zipfile(filename)
+        if self._isZipfile:
+            self.zipfile = zipfile.ZipFile(filename)
+        # self.zipfile.getinfo(name)
+
+    def extract_file(self, filename):
+        """ Returns the contents of a file in the package. """
+        _ = self.request.getText
+        try:
+            return self.zipfile.read(filename.encode("cp437"))
+        except KeyError:
+            raise RuntimeScriptException(_(
+                "The file %s was not found in the package.") % filename)
+
+    def filelist(self):
+        """ Returns a list of all files. """
+        return self.zipfile.namelist()
+
+    def isPackage(self):
+        """ Returns true if this package is recognised. """
+        return self._isZipfile and MOIN_PACKAGE_FILE in self.zipfile.namelist()
+
+if __name__ == '__main__':
+    args = sys.argv
+    if len(args)-1 not in (2, 3) or args[1] not in ('l', 'i'):
+        print >>sys.stderr, """MoinMoin Package Installer v%(version)i
+
+%(myname)s action packagefile [request URL]
+
+action      - Either "l" for listing the script or "i" for installing.
+packagefile - The path to the file containing the MoinMoin installer package
+request URL - Just needed if you are running a wiki farm, used to differentiate
+              the correct wiki.
+
+Example:
+
+%(myname)s i ../package.zip
+
+""" % {"version": MAX_VERSION, "myname": os.path.basename(args[0])}
+        raise SystemExit
+
+    packagefile = args[2]
+    if len(args) > 3:
+        request_url = args[3]
+    else:
+        request_url = "localhost/"
+
+    # Setup MoinMoin environment
+    from MoinMoin.request import RequestCLI
+    request = RequestCLI(url = 'localhost/')
+    request.form = request.args = request.setup_args()
+
+    package = ZipPackage(request, packagefile)
+    if not package.isPackage():
+        print "The specified file %s is not a package." % packagefile
+        raise SystemExit
+
+    if args[1] == 'l':
+        print package.getScript()
+    elif args[1] == 'i':
+        if package.installPackage():
+            print "Installation was successful!"
+        else:
+            print "Installation failed."
+        if package.msg:
+            print package.msg