changeset 2315:3322182f3fe2

Merged in RogerHaase/moin-2.0 (pull request #185) changed default locations of virtualenv and pip download cache; added small helper scripts in repo root to reduce keying
author Thomas Waldmann <thomas.j.waldmann@googlemail.com>
date Sun, 02 Feb 2014 15:05:16 +0100
parents d5fa8dae5055 (current diff) 42d4f773ca4a (diff)
children b942154b2d74
files Makefile quickinstall.py
diffstat 10 files changed, 998 insertions(+), 377 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Fri Jan 24 02:31:43 2014 +0100
+++ b/.hgignore	Sun Feb 02 15:05:16 2014 +0100
@@ -7,12 +7,7 @@
 ^dlc/
 ^moin.egg-info/
 ^MoinMoin/_tests/wiki/data/cache/
-^wiki/data/cache/
-^wiki/data/default/data/
-^wiki/data/default/meta/
-^wiki/data/content/
-^wiki/data/userprofiles/
-^wiki/index/
+^wiki/
 ^instance/
 ^wikiconfig_.+\.py
 ^MoinMoin/translations/.*/LC_MESSAGES/messages.mo$
@@ -34,3 +29,11 @@
 ^upload.py
 ^build/
 \..*sw[op]$
+^activate.bat$
+^deactivate.bat$
+^moin.bat$
+^m.bat$
+^activate$
+^moin$
+^m$
+^m-.*\.txt$
--- a/Makefile	Fri Jan 24 02:31:43 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-#
-# Makefile for MoinMoin
-#
-
-# location for the wikiconfig.py we use for testing:
-export PYTHONPATH=$(PWD)
-
-all:
-	python setup.py build
-
-test:
-	py.test --pep8 -rs
-
-dist: clean-devwiki
-	-rm MANIFEST
-	python setup.py sdist
-
-docs:
-	make -C docs html
-
-# this needs the sphinx-autopackage script in the toplevel dir:
-apidoc:
-	sphinx-apidoc -f -o docs/devel/api MoinMoin
-
-interwiki:
-	wget -U MoinMoin/Makefile -O contrib/interwiki/intermap.txt "http://master19.moinmo.in/InterWikiMap?action=raw"
-	chmod 664 contrib/interwiki/intermap.txt
-
-pylint:
-	@pylint --disable-msg=W0142,W0511,W0612,W0613,C0103,C0111,C0302,C0321,C0322 --disable-msg-cat=R MoinMoin
-
-clean: clean-devwiki clean-pyc clean-orig clean-rej
-	-rm -rf build
-
-clean-devwiki:
-	-rm -rf wiki/data/content
-	-rm -rf wiki/data/userprofiles
-	-rm -rf wiki/index
-
-clean-pyc:
-	find . -name "*.pyc" -exec rm -rf "{}" \; 
-
-clean-orig:
-	find . -name "*.orig" -exec rm -rf "{}" \; 
-
-clean-rej:
-	find . -name "*.rej" -exec rm -rf "{}" \; 
-
-.PHONY: all dist docs interwiki check-tabs pylint \
-	clean clean-devwiki clean-pyc clean-orig clean-rej
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/script/win/__init__.py	Sun Feb 02 15:05:16 2014 +0100
@@ -0,0 +1,6 @@
+# Copyright: 2013 MoinMoin:RogerHaase
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+    MoinMoin - Multi-platform alternatives for unix utilities
+"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/script/win/dos2unix.py	Sun Feb 02 15:05:16 2014 +0100
@@ -0,0 +1,46 @@
+#!/usr/bin/python
+# Copyright: 2013 by MoinMoin:RogerHaase
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+Alternative for unix dos2unix utility that may be run on either windows or unix. Does not implement
+typical unix dos2unix command line syntax.
+
+If passed parameter is a directory, all files in that directory are converted to unix line endings.
+Sub-directories are not processed.  If passed parameter is a filename, only that filename is converted.
+
+Usage: python <path_to>dos2unix.py <target_directory_or_filename>
+"""
+
+import os
+import sys
+
+
+def convert_file(filename):
+    """Replace DOS line endings with unix line endings."""
+    with open(filename, "rb") as f:
+        data = f.read()
+    if '\0' in data:
+        # is binary file
+        return
+    newdata = data.replace("\r\n", "\n")
+    if newdata != data:
+        with open(filename, "wb") as f:
+            f.write(newdata)
+
+
+if __name__ == "__main__":
+    if len(sys.argv) == 2:
+        target = sys.argv[1]
+        if os.path.isdir(target):
+            for (dirpath, dirnames, filenames) in os.walk(target):
+                break
+            for filename in filenames:
+                convert_file(os.path.join(target, filename))
+        elif os.path.isfile(target):
+            convert_file(target)
+        else:
+            print "Error: %s does not exist." % target
+    else:
+        print "Error: incorrect parameters passed."
+        print "usage: python <path_to>dos2unix.py <target_directory>"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/script/win/wget.py	Sun Feb 02 15:05:16 2014 +0100
@@ -0,0 +1,20 @@
+#!/usr/bin/python
+# Copyright: 2013 by MoinMoin:RogerHaase
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+Alternative for unix wget utility that may be run on either windows or unix. Does not implement
+typical unix wget command line syntax.
+
+Usage:  python <path_to>wget.py <url> <output_file>
+"""
+
+import sys
+import urllib
+
+
+if len(sys.argv) == 3:
+    urllib.urlretrieve(sys.argv[1], sys.argv[2])
+else:
+    print "Error: incorrect parameters passed."
+    print "Usage:  python <path_to>wget.py <url> <output_file>"
--- a/docs/admin/install.rst	Fri Jan 24 02:31:43 2014 +0100
+++ b/docs/admin/install.rst	Sun Feb 02 15:05:16 2014 +0100
@@ -4,168 +4,153 @@
 
 Downloading
 ===========
-For moin2, there is currently no packaged download available, so you have to get
-it from the repository.
-You can use one of two repository URLs and either use Mercurial to keep a 
-constantly updated copy of the code or download an archive of the files in tar.gz format:
-
-Using Mercurial to clone one of the repositories::
+The recommended way to download moin2 is to clone
+the moin2 Mercurial repository or its mirror. Open a terminal
+window or a command prompt, cd to the directory that will hold
+your project root directory and enter either one of the commands
+below::
 
  hg clone http://hg.moinmo.in/moin/2.0 moin-2.0
+
  OR
+
  hg clone http://bitbucket.org/thomaswaldmann/moin-2.0 moin-2.0
 
 Now make sure your work directory is using the default branch::
 
  hg up -C default
 
-Alternatively, visit http://hg.moinmo.in/moin/2.0 with your web browser and download the archive
-(usually for the "default" branch) and unpack it.
+An alternative installation method is to download the bz2 archive
+from http://hg.moinmo.in/moin/2.0 and unpack it. Once unpacked,
+continue to follow the instructions below.
 
 Installing
 ==========
 Before you can run moin, you need to install it:
 
-Developer install
------------------
-Using your standard Python install or a virtualenv directory
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Please make sure you have `virtualenv` installed, it includes `pip`.
-
-If you just want to run moin in-place in your mercurial working directory
-with your normal system installation of Python, run the following command
-from your mercurial moin2 working directory. You should not run this as an
-administrator or root user; use your standard user account::
-
- python quickinstall.py
-
-This will use virtualenv to create a directory `../venv-PROJECT-PYTHON/`
-(PROJECT is same as your current project directory, e.g. moin-2.0, PYTHON is
-the name of your python interpreter, e.g. python), create a virtual environment
-for MoinMoin and then install moin2 including all dependencies into that
-directory.
-
-`pip` will automatically fetch all dependencies from PyPI and install them, so
-this may take a while.
-It will also compile the translations (`*.po` files) to binary `*.mo` files.
-
-Please review the output of the quickinstall script, and check whether there were fatal errors.
-
-Further, the quickinstall script will create a `moin` script for your
-platform which you can use for starting the built-in server or invoke moin script commands.
-
-After you activated the virtual environment, the built-in server script, which is named 
-`moin`, will be in the standard PATH, so you can just run the command `moin` on the command line.
-
-**Note:** in this special mode, it won't copy the MoinMoin code to the virtualenv directory,
-it will run everything from your work dir, so you can modify code and directly try it out.
-You only need to do this installation procedure once.
-
-Using a different Python
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you rather like a different Python, just use it to invoke the quickinstall.py
-script - the same python will then be used for the virtual env also::
-
- /opt/pypy/bin/pypy quickinstall.py  # for linux
-
-
-Activating the virtual env
---------------------------
-
-IMPORTANT: you always need to activate the virtual environment before running
-anything that executes moin code! Otherwise it won't find the moin command,
-nor the moin code nor the libraries it needs. Also, if you want to install
-additional software into the virtual environment, activate it before running pip!::
-
- source ../venv-moin-2.0-python/bin/activate  # for linux (or other posix OSes)
- # or
- ..\venv-moin-2.0-python\Scripts\activate.bat  # for windows
-
-As you have activated the virtual env now, the moin command should be in your
-path now, so you can invoke it using "moin".
-
-Note: the quickinstall script outputs the correct commands for activating
-the virtual env and for the moin executable file.
-
-Letting moin find the wiki configuration
-----------------------------------------
+Using your standard user account, run the following command
+from the project root directory. Replace <python> in the command
+below with the path to a python 2.7 executable. This is usually
+just "python", but may be "python2.7", "/opt/pypy/bin/pypy"
+or even <some-other-path-to-python>::
 
-moin needs to find the wiki configuration. If you want it to run in the most
-simple way without giving parameters to the moin command, it is easiest if
-you are in the same directory as the configuration files, e.g. wikiconfig.py.
-
-If you are working from a repository workdir, this is the top level
-directory and there is already a ready-to-use wikiconfig.py.
-
-In case you want to just give the configuration file location, make sure you
-use an **absolute path**. moin will try to find its configuration in this
-order:
-
-- command line argument `--config /path/to/wikiconfig.py`
-- environment variable `MOINCFG=/path/to/wikiconfig.py`
-- current directory, file `wikiconfig_local.py`
-- current directory, file `wikiconfig.py`
-
-Initializing index and/or storage
----------------------------------
-If you have an existing storage AND a valid index (for this storage’s content and for this moin version),
-you can skip this section.
-
-If you start from scratch, ie no storage and no index created yet,
-you need to create an empty storage and an empty index::
-
- # create storage and index:
- moin index-create -s -i
-
-Loading some items
-------------------
-If you don't want to have a completely empty wiki, you can optionally load
-some example items into the storage like this::
+ <python> quickinstall.py
 
- # load some example items:
- moin load --file contrib/serialized/items.moin
-
-Building the index
-------------------
-If you have some items in your storage, but no index built yet, you need
-to build an index::
-
- moin index-build
-
-
-Installing PIL / pillow
-~~~~~~~~~~~~~~~~~~~~~~~
-For some image processing functions that MoinMoin uses like resizing and rotating,
-you need PIL, which is the Python Imaging Library (sometimes also referred to as
-python-imaging). Instead of PIL, you can also use pillow, which is a compatible
-fork of PIL (with more active maintenance and it also has been ported to Python 3).
+ OR
 
-Windows users who want to install PIL should skip the remainder of this section and read
-Troubleshooting -- PIL Installation Under Windows below.
-
-If you install PIL with pip, then pip will try to find a jpeg support library and associated development
-headers on your system and if you do not have them, there will be no jpeg support in PIL.
-So, if you want jpeg support, make sure you have the jpeg libs/headers::
+ <python> quickinstall.py <path-to-venv> --download-cache <path-to-cache>
 
- # install jpeg library and development headers:
- sudo apt-get install libjpeg62-dev  # Ubuntu / Debian-based
- yum install libjpeg-turbo-devel  # Fedora / Redhat-based
-
-Now activate your virtual environment and install PIL into it::
+The above will download all dependent packages to a cache,
+install the packages in a virtual environment, and compile the translations
+(`*.po` files) to binary `*.mo` files. This process may take several minutes.
 
- pip install pil  # for Linux (or other POSIX OSes)
+The default cache and virtual environment directory names are:
 
-Alternatively, if you prefer to use pillow::
+ * ~/.pip/pip-download-cache # windows: ~\\pip\\pip-download-cache
+ * ../<PROJECT>-venv-<PYTHON>/
 
- pip install pillow  # for Linux (or other POSIX OSes)
+where <PROJECT> is the name of the project root directory, and <PYTHON>
+is the name of your python interpreter. As noted above, the default
+names may be overridden.
 
+Check the output of quickinstall.py to determine whether there were
+fatal errors. The output messages will normally state that stdout
+and stderr messages were written to a file, a few key success/failure
+messages will be extracted and written to the terminal window, and
+finally a message to type "m" to display a menu.
+
+If there are failure messages, see the troubleshooting section below.
+
+Typing 'm" will display a menu similar to::
+
+    usage: "m <target>" where <target> is:
+
+    quickinstall    update virtual environment with required packages
+    docs            create moin html documentation
+    extras          install OpenID, Pillow, pymongo, sqlalchemy, ldap, upload.py
+    interwiki       refresh contrib\interwiki\intermap.txt (hg version control)
+    log <target>    view detailed log generated by <target>, omit to see list
+
+    new-wiki        create empty wiki
+    sample          create wiki and load sample data
+    restore *       create wiki and restore wiki\backup.moin *option, specify file
+    import <dir>    import a moin 1.9 wiki/data instance from <dir>
+
+    run             run built-in wiki server with local OS and logging options
+    backup *        roll 3 prior backups and create new backup *option, specify file
+
+    css             run Stylus to update CSS files
+    tests           run tests, output goes to pytest.txt and pytestpep8.txt
+    coding-std      correct scripts that taint the repository with trailing spaces..
+    api             update moin api docs (files are under hg version control)
+    dist            delete wiki data, then create distribution archive in /dist
+
+    del-all         same as running the 4 del-* commands below
+    del-orig        delete all files matching *.orig
+    del-pyc         delete all files matching *.pyc
+    del-rej         delete all files matching *.rej
+    del-wiki        create a backup, then delete all wiki data
+
+While most of the above menu choices may be executed now, new users should
+do::
+
+ m sample
+
+to create a wiki instance and load it with sample data. Next, run the
+built-in wiki server::
+
+ m run
+
+As the server starts, about 20 log messages will be output to the
+terminal window.  Point your browser to http://127.0.0.1:8080, the
+sample Home page will appear and more log messages will be output
+to the terminal window. Do a quick test by accessing some of the
+demo items and do a modify and save. If all goes well, your installation
+is complete. The built-in wiki server may be stopped by typing ctrl-C
+in the terminal window.
+
+Next Steps
+==========
+
+If you plan on contributing to the moin2 project, there are more
+instructions waiting for you under the Development topic.
+
+If you plan on just using moin2 as a desktop wiki (and maybe
+help by reporting bugs), then some logical menu choices are:
+
+ * `m docs` - to create docs, see User tab, Documentation (local)
+ * `m extras` - to install Pillow for manipulating images
+ * `m del-wiki` - get rid of the sample data
+ * `m new-wiki` or `m import ...` - no data or moin 1.9 data
+ * `m backup` - backup wiki data as needed or as scheduled
+
+Warning: Backing up data at this point may provide a false sense
+of security because no migration tool has been developed to migrate
+data between moin2 versions.  In its current alpha state, there
+may be code changes that impact the structure of the wiki data or
+indexes. Should this occur, you must start over with an empty
+wiki and somehow copy and paste the contents of all the old wiki
+items into the new wiki. While no such changes are planned,
+they have happened in the past and may happen in the future.
+
+If you installed moin2 by cloning the Moin2 Mercurial repository,
+then you will likely want to install updates on a periodic basis.
+To determine if there are updates available, open a terminal
+window or command prompt, cd to your project root, and enter the
+command below::
+
+  hg incoming
+
+If there are any updates, a brief description of each update will
+be displayed. To add the updates to your cloned repository, do::
+
+  hg pull -u
 
 Troubleshooting
----------------
+===============
 
 PyPi down
-~~~~~~~~~
+---------
 Now and then, PyPi might be down or unreachable.
 
 There are mirrors b.pypi.python.org, c.pypi.python.org, d.pypi.python.org
@@ -175,49 +160,45 @@
  [global]
  index-url = http://c.pypi.python.org/simple
 
-In case that doesn't work either, try our mini pypi that should have all
-packages you need for moin::
-
- # put this into ~/.pip/pip.conf
- [global]
- index-url = http://pypi.moinmo.in/simple
-
 Bad Network Connection
-~~~~~~~~~~~~~~~~~~~~~~
-If you have a poor or limited network connection, you may run into trouble with the commands issued by
-the quickinstall script.
-You may see tracebacks from pip, timeout errors, etc. See the output of the quickinstall script.
-
-If this is the case, try it manually::
-
- # create a virtual environment:
- virtualenv env
-
- # enter your virtual environment:
- source env/bin/activate
+----------------------
+If you have a poor or limited network connection, you may run into
+trouble with the commands issued by the quickinstall.py script.
+You may see tracebacks from pip, timeout errors, etc. within the output
+of the quickinstall script.
 
- # confirm the problems by running:
- pip install -e .
-
-Now install each package into your virtual env manually:
-
-* Find the required packages by looking at "install_requires" within `setup.py`.
-* Download each required package from http://pypi.python.org/
-* Install each of them individually::
-
-    pip install package.tar
+If this is the case, you may try rerunning the "python quickinstall.py"
+script multiple times. With each subsequent run, packages that are
+all ready cached (view the contents of pip-download-cache) will not
+be downloaded again. Hopefully, any temporary download errors will
+cease with multiple tries.
 
-* Now try again::
-
-    pip install -e .
-
-Repeat these steps until you don't see fatal errors.
+ActiveState Python
+------------------
+While ActiveState bundles pip and virtualenv in its distribution,
+there are two missing files. The result is the following error
+messages followed by a traceback::
 
-PIL/pillow Installation Under Windows
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-PIL version 1.1.7 does not install correctly via "pip install pil" on Windows.
-Some users have had success using "pip install pillow", a fork of PIL fixing
-a packaging issue. Other users have resorted to installing PIL 1.1.6 in the
-main Python directory using the Windows installers available at
-http://www.pythonware.com/products/pil/
 
+  Cannot find sdist setuptools-*.tar.gz
+  Cannot find sdist pip-*.tar.gz
+
+To install the missing files, do the following and then rerun
+"python quickinstall.py"::
+
+  \Python27\Scripts\pip.exe uninstall virtualenv
+  \Python27\Scripts\easy_install virtualenv
+
+Other Issues
+------------
+
+If you encounter some other issue not described above, try
+researching the unresolved issues at
+https://bitbucket.org/thomaswaldmann/moin-2.0/issues?status=new&status=open.
+If you find a similar issue, please
+add a note saying you also have the problem and add any new
+information that may assist in the problem resolution.
+
+If you cannot find a similar issue please create a new issue.
+Or, if you are not sure what to do, join us on IRC at #moin-dev
+and describe the problem you have encountered.
--- a/docs/admin/requirements.rst	Fri Jan 24 02:31:43 2014 +0100
+++ b/docs/admin/requirements.rst	Sun Feb 02 15:05:16 2014 +0100
@@ -2,15 +2,43 @@
 Requirements
 ============
 
-MoinMoin requires Python 2.7.x.
-We usually test using CPython and this is what we recommend.
+MoinMoin requires Python 2.7.x. A CPython distribution is
+recommended because it will likely be the fastest and most stable.
+Most developers use a CPython distribution for testing.
+Typical linux distributions will either have Python 2.7 installed by
+default or will have a package manager that will install Python 2.7
+as a secondary Python.
+Windows users may download CPython distributions from  http://www.python.org/ or
+http://www.activestate.com/.
 
-You can also try PyPy, it seems to work.
+An alternative implementation of Python, PyPy, is available
+from http://pypy.org/.
+
+The `virtualenv` Python package is also required. The
+installation process for `virtualenv` varies with your OS and
+Python distribution.
+Many linux distributions have a package manager that may do
+the installation. Windows users (and perhaps others) may download
+setuptools from https://pypi.python.org/pypi/setuptools. Once setuptools is installed, do "`easy_install virtualenv`". Current ActiveState
+distributions include virtualenv in the installation bundle.
+If all else fails, try Google.
+
+Mercurial (hg) is required should you wish to contribute
+patches to the moin2 development effort. Even if you do not
+intend to contribute, Mercurial is highly recommended as it
+will make it easy for you to obtain fixes and enhancements
+from the moin2 repositories. Mercurial can be installed
+with most linux package managers or downloaded
+from http://mercurial.selenic.com/. As an alternative,
+most Windows users will prefer to install TortoiseHG
+(includes Mercurial) from
+http://tortoisehg.bitbucket.org/.
+
 
 Servers
 =======
 
-For moin, you can use any server compatible with WSGI:
+For moin2, you can use any server compatible with WSGI:
 
 * the builtin "moin" server is recommended for desktop wikis, testing,
   debugging, development, adhoc-wikis, etc.
@@ -24,24 +52,15 @@
 Dependencies
 ============
 
-For dependency information, please see setup.py.
-
-If you use easy_install or pip or our ``quickinstall`` script, then
-dependencies are usually automatically dealt with.
-
+Dependent packages will be automatically downloaded and installed during the moin2 installation process. For a list of dependencies, see setup.py.
 
 Clients
 =======
 On the client side, you need:
 
-* a decent web browser that supports W3C standards HTML 5 and CSS 2.1 as well
-  as JavaScript:
+* a web browser that supports W3C standards HTML 5, CSS 2.1, and JavaScript:
 
-  - any current version of Firefox, Chrome, Opera, Safari, Internet Explorer
-    (IE9 or IE10) should work.
-  - usage of older Internet Explorer versions is not recommended and not
-    supported because they are known for causing issues.
-    For Windows 7 (or 8) Microsoft provides IE9 or IE10.
-* Java browser plugin (optional, needed if you want to use TWikiDraw or
-  AnyWikiDraw drawing applets).
+  - any current version of Firefox, Chrome, Opera, Safari, Maxthon, Internet Explorer (IE9 or newer).
+  - use of older Internet Explorer versions is not recommended and not supported.
 
+* a Java browser plugin is required only if you want to use the TWikiDraw or AnyWikiDraw drawing applets.
--- a/docs/devel/development.rst	Fri Jan 24 02:31:43 2014 +0100
+++ b/docs/devel/development.rst	Sun Feb 02 15:05:16 2014 +0100
@@ -23,119 +23,174 @@
 
 * http://bitbucket.org/thomaswaldmann/moin-2.0/issues
 
-Code Repositories (using Mercurial DVCS):
+Code Repositories (using Mercurial DVCS http://mercurial.selenic.com/):
 
 * http://hg.moinmo.in/moin/2.0  (main repository)
 * http://bitbucket.org/thomaswaldmann/moin-2.0  (bitbucket mirror for your
   convenience, simplifying forking and contributing)
 
-Code review (get feedback about code changes):
+Code review (always use this to get feedback about code changes):
 
-* http://codereview.appspot.com/
+* http://code.google.com/p/rietveld/wiki/CodeReviewHelp
+* http://codereview.appspot.com/ (list of current codereview activity)
 
-Pastebin (temporary stuff - do not use for code reviews, do not use from issue
-tracker or for any long-term needed stuff):
+Pastebin (temporary storage - do not use for code review or any long-term need):
 
 * http://rn0.ru/
 
-
 Typical development workflow
 ============================
 
-This is the typical workflow for non-trivial changes and developers that likely
-want to contribute more than one change:
-
-* create your own development environment (only once):
-
-  - create a google account (if you don't have one already, it's free), so you
-    can use codereview.appspot.com
-  - create a bitbucket account (if you don't have one already, it's free)
-  - clone ("fork") the main repository on bitbucket, so you have your own bb
-    repo to publish your work
-  - clone your own bb repo to your local development machine
-  - do a development install from your local repo - read the moin2 install docs
-    for detailled instructions.
-  - join #moin-dev IRC channel and stay there whenever possible
-
-* find some stuff to work on:
-
-  - look at the issue tracker to find some stuff you can solve
-  - in case you want to work on some (non-trivial) new issue or idea that is
-    not on the issue tracker yet, first create an issue there with a detailled
-    description of it
-  - discuss with / get feedback from other developers on the #moin-dev IRC
-    channel
-
-* work on the stuff:
+This is the typical workflow for anyone that wants to contribute to the development of Moin2.
 
-  - to avoid double work, add a comment on the issue tracker that you are
-    working on that issue
-  - work in your local repo on your local development machine (make sure you
-    work in the right branch)
-  - concentrate on one issue / one topic, create a clean set of changes (that
-    means not doing more than needed to fix the issue, but also it means fixing
-    the issue completely and everywhere)
-  - write good, clean, easy-to-understand code.
-  - obey PEP-8
-  - do not fix or change unrelated stuff while working, but rather create new
-    issues on the tracker, so it's not forgotten
-  - regularly run the unit tests ("make test"), the amount of failing tests
-    shall not increase due to your changes
-  - if you fix something that had no test, first try to write a (correct, but
-    still failing) test for it, then fix the code and see the test not failing
-    any more
-  - if you implement new functionality, write tests for it first, then
-    implement it
-  - do an own review of your changes. Use hg diff, hg status - read everything
-    you changed - slowly, looking for stuff that can be improved. Fix
-    everything you find that way before requesting feedback from others.
-  - get feedback from other developers about your changes:
-   
-    + put them on codereview (just run python upload.py in your local repo -
-      if it is not first upload, reuse the same ID to update the already
-      existing codereview)
-    + post the codereview URL to #moin-dev IRC channel asking for review
-    + if you want to get feedback on non-code stuff, either use the issue
-      tracker or a pastebin (only use pastebins for temporary stuff)
-  - repeat until everybody is happy with it
-  - do some final testing - practically and using the unit tests
-  - commit your changes to your local repo, use a meaningful commit comment
+create your development environment
+-----------------------------------
 
-* publish your stuff and request it being merged:
+* if you do not have a bitbucket account, create one at https://bitbucket.org
+* fork the main repository on bitbucket: https://bitbucket.org/thomaswaldmann/moin-2.0
+* clone the main repository to your local development machine
 
-  - push the changeset to your public bitbucket repo
-  - create a pull request to request that your changes get pulled into the
-    main repository
-  - optionally, tell about it on the IRC channel
-  - if you fixed an issue from the issue tracker, make sure the issue gets
-    closed after your fix has been merged.
+  - cd to parent directory of your future repo
+  - "hg clone https://bitbucket.org/thomaswaldmann/moin-2.0 moin-2.0"
+* ensure you are in default branch "hg update default"
+* create the virtualenv and download packages: "python quickinstall.py"
+* create a wiki instance and load sample data: "m sample"
+* start the built-in server: "m run"
+* point your browser at http://127.0.0.1:8080/ to access your development wiki
+* key ctrl+C to stop the built-in server
 
+add more tools, exercise tools
+------------------------------
+
+* if you do not have a google account, create one at http://codereview.appspot.com
+* download upload.py from http://code.google.com/p/rietveld/wiki/CodeReviewHelp
+  to your repo root, then practice using codereview:
+* make a trivial change to any source file, do "python upload.py"
+* inspect your patch set at http://codereview.appspot.com, add a comment
+* click the "Publish and Mail comments" link, check your email inbox
+* make another trivial change to same source file, do "python upload.py -i nnn"
+  where nnn is ID of existing codereview
+* inspect your patch set again, compare patch set 1 to patch set 2
+* click the "Delete" link to delete patchset 2
+* revert the changes on your local repo "hg revert --all"
+* run the unit tests ("m tests"), note any existing test failures
+* install NodeJS with Linux package manager; Windows users may download from http://nodejs.org/download/
+* install stylus
+
+  - "sudo npm install stylus -g" or windows "npm install stylus -g"
+  - "stylus -V"  # show version number to prove it works
+* run Stylus to regenerate CSS files: "m css", verify nothing changed: "hg diff"
+* run "m coding-std" to see if there are any coding errors
+* run "m api" to see any uncommitted API doc changes
+* use "hg revert --all" to revert any changes from above
+* optional: create local docs "m docs"
+* set options on your favorite editor or IDE
+
+  - convert tabs to 4 spaces
+  - delete trailing blanks on file save
+  - use unix line endings (use Windows line endings on .bat and .cmd files)
+  - use mono-spaced font for editing
+* if you are new to mercurial, read a tutorial (http://hginit.com/),
+  consider printing a cheatsheet
+* if you want a Python IDE, try http://www.jetbrains.com/pycharm/ Free Community Edition
+* if you want a graphical interface to Mercurial, install SourceTree (best for mac) or TortoiseHG (best for Windows)
+* join #moin-dev IRC channel; ask questions, learn what other developers are doing
+
+find a task to work on
+----------------------
+
+* look at the issue tracker to find a task you can solve
+* in case you want to work on some (non-trivial) new issue or idea that is
+  not on the issue tracker, create an issue with a detailed description
+* discuss your chosen task with other developers on the #moin-dev IRC
+  channel
+* to avoid duplicate work, add a comment on the issue tracker that you are
+  working on that issue
+* just before you start to code changes, update your local repo: "hg pull -u"
+
+develop a testing strategy
+--------------------------
+
+* if you fix something that had no test, first try to write a correct,
+  but failing test for it, then fix the code and see a successful test
+* if you implement new functionality, write tests for it first, then
+  implement it
+* make a plan for using a browser to test your changes; which wiki pages are
+  effected, how many browsers must be tested
+* run "m tests" to determine if there are any existing test failures before you make changes
+
+develop a working solution
+--------------------------
+
+* work in your local repo on your local development machine
+  (be sure you work in the right branch)
+* concentrate on one issue / one topic, create a clean set of changes
+  (that means not doing more than needed to fix the issue, but also it
+  means fixing the issue completely and everywhere)
+* write good, clean, easy-to-understand code
+* obey PEP-8
+* do not fix or change code unrelated to your task, if you find
+  unrelated bugs, create new issues on the tracker
+* regularly run the unit tests ("m tests"), the amount of failing tests
+  shall not increase due to your changes
+
+review your working solution
+----------------------------
+
+* use hg diff, hg status - read everything you changed - slowly, look for
+  things that can be improved
+
+  - if you have TortoiseHG or SourceTree, use those graphical tools to review changes
+* look for poor variable names, spelling errors in comments, accidental addition
+  or deletion of blank lines, complex code without comments, missing/extra spaces
+* fix everything you find before requesting feedback from others
+* run tests again "m tests"
+
+get feedback from other developers
+----------------------------------
+
+* add changes to codereview: run "python upload.py" in your local repo
+
+  - to update a codereview, "python upload.py -i nnn" where nnn is ID
+* carefully review your changes again on codereview
+
+  - if you find errors, delete the patchset, fix and upload again
+* if you have questions or want to explain something, add comments and click
+  "Publish+Mail Comments"
+* post the codereview URL to #moin-dev IRC channel asking for review
+* repeat until everybody is happy with your changes
+
+publish your change
+-------------------
+
+* do some final testing - practically and using the unit tests
+* commit your changes to your local repo, use a concise commit comment
+  describing the change
+* pull any changes made by others from the main repo on Bitbucket, then
+  merge and commit
+* push the changeset to your public bitbucket repo
+* create a pull request so your changes will get pulled into the
+  main repository
+* optionally, request a pull on the IRC channel
+* if you fixed an issue from the issue tracker, be sure the issue gets
+  closed after your fix has been pulled into main repo.
+* celebrate, loop back to "find a task to work on"
+
+update your virtualenv
+----------------------
+
+Every week or so, do "m quickinstall" to install new releases of
+dependent packages. If any new packages are installed, do a
+quick check for breakages by running tests, starting the
+build-in server, modify an item, etc.
 
 Alternate contribution workflows
 ================================
-If the above workflow looks like overkill (e.g. for simple changes) or you
-can't work with the tools we usually use, you can also do it like this:
-
-* find an existing issue on the issue tracker about the issue you were fixing
-  (or create a new one), make sure to give (or update) all the details, like:
-
-  - precise version number / changeset hashes of the original code your patch
-    is based on
-  - precise description of the issue, how to reproduce it, tracebacks, ...
-  - why your fix is correct / how you tested it
-* create a patch using the diff tool, attach patch.txt to the issue:
-
-    diff -urN originalcodetree/ modifiedcodetree/ > patch.txt
-
-* if you fixed an issue from the issue tracker, make sure the issue gets
-  closed after your fix has been committed to the main repo.
-
-For trivial fixes (like typos), you can also try just grabbing a developer
-on IRC, telling filename, line number and get it fixed by him.
-
-Note: if developers find that the required changes are not that simple or are
-potentially causing other issues, codereview or other parts of the full
-workflow might be needed.
+If the above workflow looks like overkill (e.g. for simple changes)
+or you can't work with the tools we usually use, then just create or
+update an issue on the issue tracker
+https://bitbucket.org/thomaswaldmann/moin-2.0/issues)
+or join us on IRC #moin-dev.
 
 
 MoinMoin architecture
@@ -159,10 +214,6 @@
 * CKeditor, the GUI editor for (x)html
 * TWikiDraw, AnyWikiDraw, svgdraw drawing tools
 
-.. todo::
-
-   add some nice gfx
-
 
 How MoinMoin works
 ==================
@@ -237,7 +288,8 @@
 -------------------------
 How does moin know what the HTML rendering of an item looks like?
 
-Each Item has some contenttype that is stored in the metadata, also called the input contenttype.
+Each Item has some contenttype that is stored in the metadata, also called
+the input contenttype.
 We also know what we want as output, also called the output contenttype.
 
 Moin uses converters to transform the input data into the output data in
@@ -260,7 +312,7 @@
 Finally, the dom-tree will reach the output converter, which will transform it
 into the desired output format, such as `text/html`.
 
-This is just one example of a supported transformation. There are quite a few 
+This is just one example of a supported transformation. There are quite a few
 converters in `MoinMoin.converter` supporting different input formats,
 dom-dom transformations and output formats.
 
@@ -284,7 +336,7 @@
 To run the tests, activate your virtual env and invoke py.test from the
 toplevel directory::
 
-    make test  # easiest way (all tests, pep8, skipped info)
+    m tests  # easiest way (all tests, pep8, skipped info)
     py.test --pep8  # run all tests, including pep8 checks
     py.test -rs  # run all tests and outputs information about skipped tests
     py.test -k somekeyword  # run the tests matching somekeyword only
@@ -318,9 +370,7 @@
 
 Creating docs
 -------------
-Sphinx can create all kinds of documentation formats. The most
-popular ones are::
+Sphinx can create all kinds of documentation formats. The most common are
+the local HTML docs that are linked to under the User tab.
 
-    cd docs
-    make html  # create html docs (to browse online or in the filesystem)
-
+    m docs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make.py	Sun Feb 02 15:05:16 2014 +0100
@@ -0,0 +1,489 @@
+#!/usr/bin/python
+# Copyright: 2013 MoinMoin:RogerHaase
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+make.py provides a menu of commands frequently used by moin2 developers and desktop wiki users.
+
+    - duplicates some common moin commands, do "moin --help" for all alternatives
+    - adds default file names for selected moin commands (backup, restore, ...)
+    - creates log files for functions with large output, extracts success/failure messages
+    - displays error messages when user tries to run commands out of sequence
+
+usage (to display a menu of commands):
+    - unix:     ./m
+    - windows:  m
+
+For make.py to work, it needs to know the name of a python executable and the location of a
+virtual env. These needs are met by running "python quickinstall.py" after cloning the moin2
+repository. quickinstall.py creates these files or symlinks in the repo root:
+    - unix: m, activate, moin
+    - windows: m.bat, activate.bat, deactivate.bat, moin.bat
+
+Executing m.bat or ./m will run make.py. The name of the python executable is within the m.bat or ./m
+script.  The location of the virtual env is within the activate and moin symlinks or activate.bat and
+moin.bat scripts. Depending upon the command to be executed, some mix of the python executable, moin,
+or activate will be used to construct a command string to pass to a subprocess call.
+"""
+
+import os
+import sys
+import subprocess
+import glob
+import shutil
+import fnmatch
+from collections import Counter
+
+import MoinMoin  # validate python version
+
+
+# text files created by commands with high volume output
+QUICKINSTALL = 'm-quickinstall.txt'
+PYTEST = 'm-pytest.txt'
+PEP8 = 'm-pep8.txt'
+CODING_STD = 'm-coding-std.txt'
+DOCS = 'm-docs.txt'
+NEWWIKI = 'm-new-wiki.txt'
+DELWIKI = 'm-delete-wiki.txt'
+BACKUPWIKI = 'm-backup-wiki.txt'
+EXTRAS = 'm-extras.txt'
+DIST = 'm-create-dist.txt'
+# default files used for backup and restore
+BACKUP_FILENAME = 'wiki/backup.moin'
+JUST_IN_CASE_BACKUP = 'wiki/deleted-backup.moin'
+
+
+if os.name == 'nt':
+    M = 'm'  # customize help to local OS
+    WINDOWS_OS = True
+else:
+    M = './m'
+    WINDOWS_OS = False
+
+
+# commands that create log files; "tests" creates 2 log files - pytest + pep8
+CMD_LOGS = {
+    'quickinstall': QUICKINSTALL,
+    'pytest': PYTEST,
+    'pep8': PEP8,
+    # 'coding-std': CODING_STD,  # not logged due to small output
+    'docs': DOCS,
+    'new-wiki': NEWWIKI,
+    'del-wiki': DELWIKI,
+    'backup': BACKUPWIKI,
+    'extras': EXTRAS,
+    'dist': DIST,
+}
+
+
+help = r"""
+
+usage: "%s <target>" where <target> is:
+
+quickinstall    update virtual environment with required packages
+docs            create moin html documentation
+extras          install OpenID, Pillow, pymongo, sqlalchemy, ldap, upload.py
+interwiki       refresh contrib\interwiki\intermap.txt (hg version control)
+log <target>    view detailed log generated by <target>, omit to see list
+
+new-wiki        create empty wiki
+sample          create wiki and load sample data
+restore *       create wiki and restore wiki\backup.moin *option, specify file
+import <dir>    import a moin 1.9 wiki/data instance from <dir>
+
+run             run built-in wiki server with local OS and logging options
+backup *        roll 3 prior backups and create new backup *option, specify file
+
+css             run Stylus to update CSS files
+tests           run tests, output goes to pytest.txt and pytestpep8.txt
+coding-std      correct scripts that taint the repository with trailing spaces..
+api             update moin api docs (files are under hg version control)
+dist            delete wiki data, then create distribution archive in /dist
+
+del-all         same as running the 4 del-* commands below
+del-orig        delete all files matching *.orig
+del-pyc         delete all files matching *.pyc
+del-rej         delete all files matching *.rej
+del-wiki        create a backup, then delete all wiki data
+""" % M
+
+
+def search_for_phrase(filename):
+    """Search a text file for key phrases and print the lines of interest or print a count by phrase."""
+    files = {
+        # filename: (list of phrases)
+        QUICKINSTALL: ('could not find', 'error', 'fail', 'timeout', 'traceback', 'success', 'cache location', 'must be deactivated', ),
+        NEWWIKI: ('error', 'fail', 'timeout', 'traceback', 'success', ),
+        BACKUPWIKI: ('error', 'fail', 'timeout', 'traceback', 'success', ),
+        # use of 'error ' below is to avoid matching .../Modules/errors.o....
+        EXTRAS: ('error ', 'error:', 'error.', 'error,', 'fail', 'timeout', 'traceback', 'success', 'already satisfied', 'active version', 'installed', 'finished', ),
+        PYTEST: ('seconds =', ),
+        PEP8: ('seconds =', ),
+        CODING_STD: ('remove trailing blanks', 'dos line endings', 'unix line endings', 'remove empty lines', ),
+        DIST: ('creating', 'copying', 'adding', 'hard linking', ),
+        DOCS: ('build finished', 'build succeeded', 'traceback', 'failed', 'error', 'usage', 'importerror', 'Exception occurred', )
+    }
+    # for these file names, display a count of occurrances rather than each found line
+    print_counts = (CODING_STD, DIST, )
+
+    with open(filename, "r") as f:
+        lines = f.readlines()
+    name = os.path.split(filename)[1]
+    phrases = files[name]
+    counts = Counter()
+    for idx, line in enumerate(lines):
+        line = line.lower()
+        for phrase in phrases:
+            if phrase in line:
+                if filename in print_counts:
+                    counts[phrase] += 1
+                else:
+                    print idx + 1, line.rstrip()
+                    break
+    for key in counts:
+        print 'The phrase "%s" was found %s times.' % (key, counts[key])
+
+
+def wiki_exists():
+    """Return truthy if a wiki exists."""
+    return glob.glob('wiki/index/_all_revs_*.toc')
+
+
+def make_wiki(command):
+    """Process command to create a new wiki."""
+    if wiki_exists():
+        print 'Error: a wiki exists, delete it and try again.'
+    else:
+        print 'Output messages redirected to %s' % NEWWIKI
+        with open(NEWWIKI, 'w') as messages:
+            result = subprocess.call(command, shell=True, stderr=messages, stdout=messages)
+        if result == 0:
+            print '\nSuccess: a new wiki has been created.'
+        else:
+            print 'Important messages from %s are shown below:' % NEWWIKI
+            search_for_phrase(NEWWIKI)
+            print '\nError: attempt to create wiki failed. Do "%s log new-wiki" to see complete log.' % M
+
+
+def delete_files(pattern):
+    """Recursively delete all files matching pattern."""
+    matches = []
+    for root, dirnames, filenames in os.walk(os.path.abspath(os.path.dirname(__file__))):
+        for filename in fnmatch.filter(filenames, pattern):
+            matches.append(os.path.join(root, filename))
+    for match in matches:
+        os.remove(match)
+    print 'Deleted %s files matching "%s".' % (len(matches), pattern)
+
+
+class Commands(object):
+    """Each cmd_ method processes a choice on the menu."""
+    def __init__(self):
+        pass
+
+    def cmd_quickinstall(self, *args):
+        """create or update a virtual environment with the required packages"""
+        command = '%s quickinstall.py %s' % (sys.executable, ' '.join(args))
+        print 'Running quickinstall.py... output messages redirected to %s' % QUICKINSTALL
+        with open(QUICKINSTALL, 'w') as messages:
+            result = subprocess.call(command, shell=True, stderr=messages, stdout=messages)
+        if result != 0:
+            open(QUICKINSTALL, 'a').write('Error: quickinstall passed non-zero return code: %s' % result)
+        print 'Searching %s, important messages are shown below... Do "%s log quickinstall" to see complete log.\n' % (QUICKINSTALL, M)
+        search_for_phrase(QUICKINSTALL)
+
+    def cmd_docs(self, *args):
+        """create local Sphinx html documentation"""
+        if WINDOWS_OS:
+            command = 'activate.bat & cd docs & make.bat html'  # windows separates commands with "&"
+        else:
+            # in terminal "source activate" works, but shell requires "source ./activate"
+            command = 'source ./activate; cd docs; make html'  # unix separates commands with ";"
+        print 'Creating HTML docs... output messages written to %s.' % DOCS
+        with open(DOCS, 'w') as messages:
+            result = subprocess.call(command, shell=True, stderr=messages, stdout=messages)
+        print 'Searching %s, important messages are shown below...\n' % DOCS
+        search_for_phrase(DOCS)
+        if result == 0:
+            print 'HTML docs successfully created in docs/_build/html.'
+        else:
+            print 'Error: creation of HTML docs failed with return code "%s". Do "%s log docs" to see complete log.' % (result, M)
+
+    def cmd_extras(self, *args):
+        """install optional packages: OpenID, Pillow, pymongo, sqlalchemy, ldap; and upload.py"""
+        upload = '%s MoinMoin/script/win/wget.py https://codereview.appspot.com/static/upload.py upload.py' % sys.executable
+        if WINDOWS_OS:
+            print 'Installing OpenId, Pillow, pymongo, sqlalchemy, upload.py... output messages written to %s.' % EXTRAS
+            # easy_install is used for windows because it installs binary packages, pip does not
+            command = 'activate.bat & easy_install python-openid & easy_install pillow & easy_install pymongo & easy_install sqlalchemy' + ' & ' + upload
+            # TODO: "easy_install python-ldap" fails on windows
+            # try google: installing python-ldap in a virtualenv on windows
+            # or, download from http://www.lfd.uci.edu/~gohlke/pythonlibs/#python-ldap
+            #   activate.bat
+            #   easy_install <path to downloaded .exe file>
+        else:
+            print 'Installing OpenId, Pillow, pymongo, sqlalchemy, ldap, upload.py... output messages written to %s.' % EXTRAS
+            command = 'source ./activate; pip install python-openid; pip install pillow; pip install pymongo; pip install sqlalchemy; pip install python-ldap' + '; ' + upload
+        with open(EXTRAS, 'w') as messages:
+            subprocess.call(command, shell=True, stderr=messages, stdout=messages)
+        print 'Important messages from %s are shown below. Do "%s log extras" to see complete log.' % (EXTRAS, M)
+        search_for_phrase(EXTRAS)
+
+    def cmd_interwiki(self, *args):
+        """refresh contrib\interwiki\intermap.txt"""
+        print 'Refreshing contrib\interwiki\intermap.txt...'
+        command = '%s MoinMoin/script/win/wget.py http://master19.moinmo.in/InterWikiMap?action=raw contrib/interwiki/intermap.txt' % sys.executable
+        subprocess.call(command, shell=True)
+
+    def cmd_log(self, *args):
+        """View a log file with the default text editor"""
+
+        def log_help(logs):
+            """Print list of available logs to view."""
+            print "usage: %s log <target> where <target> is:\n\n" % M
+            choices = '{0: <16}- {1}'
+            for log in sorted(logs):
+                if os.path.isfile(CMD_LOGS[log]):
+                    print choices.format(log, CMD_LOGS[log])
+                else:
+                    print choices.format(log, '* file does not exist')
+
+        logs = set(CMD_LOGS.keys())
+        if args and args[0] in logs and os.path.isfile(CMD_LOGS[args[0]]):
+            if WINDOWS_OS:
+                command = 'start %s' % CMD_LOGS[args[0]]
+            else:
+                command = '${VISUAL:-${FCEDIT:-${EDITOR:-less}}} %s' % CMD_LOGS[args[0]]
+            subprocess.call(command, shell=True)
+        else:
+            log_help(logs)
+
+    def cmd_new_wiki(self, *args):
+        """create empty wiki"""
+        if WINDOWS_OS:
+            command = 'moin.bat index-create -s -i'
+        else:
+            command = './moin index-create -s -i'
+        print 'Creating a new empty wiki...'
+        make_wiki(command)  # share code with loading sample data and restoring backups
+
+    def cmd_sample(self, *args):
+        """create wiki and load sample data"""
+        if WINDOWS_OS:
+            command = 'moin.bat index-create -s -i & moin.bat load --file contrib/serialized/items.moin & moin.bat index-build'
+        else:
+            command = './moin index-create -s -i; ./moin load --file contrib/serialized/items.moin; ./moin index-build'
+        print 'Creating a new wiki populated with sample data...'
+        make_wiki(command)
+
+    def cmd_restore(self, *args):
+        """create wiki and load data from wiki/backup.moin or user specified path"""
+        if WINDOWS_OS:
+            command = 'moin.bat index-create -s -i & moin.bat load --file %s & moin.bat index-build'
+        else:
+            command = './moin index-create -s -i; ./moin load --file %s; ./moin index-build'
+        filename = BACKUP_FILENAME
+        if args:
+            filename = args[0]
+        if os.path.isfile(filename):
+            command = command % filename
+            print 'Creating a new wiki and loading it with data from %s...' % filename
+            make_wiki(command)
+        else:
+            print 'Error: cannot create wiki because %s does not exist.' % filename
+
+    def cmd_import(self, *args):
+        """import a moin 1.9 wiki directory named dir"""
+        if WINDOWS_OS:
+            command = 'moin.bat import19 -s -i --data_dir %s'
+        else:
+            command = './moin import19 -s -i --data_dir %s'
+        if args:
+            dirname = args[0]
+            if os.path.isdir(dirname):
+                command = command % dirname
+                print 'Creating a new wiki populated with data from %s...' % dirname
+                make_wiki(command)
+            else:
+                print 'Error: cannot create wiki because %s does not exist.' % dirname
+        else:
+            print 'Error: a path to the Moin 1.9 wiki/data data directory is required.'
+
+    def cmd_run(self, *args):
+        """run built-in wiki server with local options"""
+        if wiki_exists():
+            if os.path.isfile('logging.conf'):
+                if WINDOWS_OS:
+                    logfile = 'set MOINLOGGINGCONF=logging.conf & '
+                else:
+                    logfile = 'MOINLOGGINGCONF=logging.conf; export MOINLOGGINGCONF; '
+            else:
+                logfile = ''
+            if WINDOWS_OS:
+                command = '%smoin.bat moin %s --threaded' % (logfile, ' '.join(args))
+            else:
+                command = '%s./moin moin %s' % (logfile, ' '.join(args))
+            try:
+                subprocess.call(command, shell=True)
+            except KeyboardInterrupt:
+                pass  # on windows pass eliminates traceback but "Terminate batch job..." message is displayed twice
+        else:
+            print 'Error: a wiki must be created before running the built-in server.'
+
+    def cmd_backup(self, *args):
+        """roll 3 prior backups and create new wiki/backup.moin or backup to user specified file"""
+        if wiki_exists():
+            filename = BACKUP_FILENAME
+            if args:
+                filename = args[0]
+                print 'Creating a wiki backup to %s...' % filename
+            else:
+                print 'Creating a wiki backup to %s after rolling 3 prior backups...' % filename
+                b3 = BACKUP_FILENAME.replace('.', '3.')
+                b2 = BACKUP_FILENAME.replace('.', '2.')
+                b1 = BACKUP_FILENAME.replace('.', '1.')
+                if os.path.exists(b3):
+                    os.remove(b3)
+                for src, dst in ((b2, b3), (b1, b2), (BACKUP_FILENAME, b1)):
+                    if os.path.exists(src):
+                        os.rename(src, dst)
+            if WINDOWS_OS:
+                command = 'moin.bat save --all-backends --file %s' % filename
+            else:
+                command = './moin save --all-backends --file %s' % filename
+            with open(BACKUPWIKI, 'w') as messages:
+                result = subprocess.call(command, shell=True, stderr=messages, stdout=messages)
+            if result == 0:
+                print 'Success: wiki was backed up to %s' % filename
+            else:
+                print 'Important messages from %s are shown below. Do "%s log backup" to see complete log.' % (BACKUPWIKI, M)
+                search_for_phrase(BACKUPWIKI)
+                print '\nError: attempt to backup wiki failed.'
+        else:
+            print 'Error: cannot backup wiki because it has not been created.'
+
+    def cmd_css(self, *args):
+        """run Stylus to update CSS files"""
+        print 'Running Stylus to update CSS files...'
+        if WINDOWS_OS:
+            command = r'cd MoinMoin\themes\modernized\static\css\stylus & stylus --include-css --compress < main.styl > ../common.css'
+        else:
+            command = 'cd MoinMoin/themes/modernized/static/css/stylus; stylus --include-css --compress < main.styl > ../common.css'
+        result = subprocess.call(command, shell=True)
+
+        if WINDOWS_OS:
+            command = r'cd MoinMoin\themes\foobar\static\css\stylus & stylus --include-css --compress < main.styl > ../common.css'
+        else:
+            command = 'cd MoinMoin/themes/foobar/static/css/stylus; stylus --include-css --compress < main.styl > ../common.css'
+        result2 = subprocess.call(command, shell=True)
+
+        if result == 0 and result2 == 0:
+            print 'Success: CSS files updated.'
+        else:
+            print 'Error: stylus failed to update css files, see error messages above.'
+
+    def cmd_tests(self, *args):
+        """run tests, output goes to pytest.txt and pytestpep8.txt"""
+        print 'Running tests... output written to %s and %s.' % (PYTEST, PEP8)
+        if WINDOWS_OS:
+            command = 'activate.bat & py.test.exe > %s 2>&1 & py.test.exe --pep8 -k pep8 --clearcache > %s 2>&1' % (PYTEST, PEP8)
+        else:
+            command = 'source ./activate; py.test > %s 2>&1; py.test --pep8 -k pep8 --clearcache > %s 2>&1' % (PYTEST, PEP8)
+        result = subprocess.call(command, shell=True)
+        print 'Summary message from %s is shown below. Do "%s log pytest" to see complete log.' % (PYTEST, M)
+        search_for_phrase(PYTEST)
+        print 'Summary message from %s is shown below. Do "%s log pep8" to see complete log.' % (PEP8, M)
+        search_for_phrase(PEP8)
+
+    def cmd_coding_std(self, *args):
+        """correct scripts that taint the HG repository and clutter subsequent code reviews"""
+        print 'Checking for trailing blanks, DOS line endings, Unix line endings, empty lines at eof...'
+        command = '%s contrib/pep8/coding_std.py' % sys.executable
+        subprocess.call(command, shell=True)
+
+    def cmd_api(self, *args):
+        """update Sphinx API docs, these docs are under hg version control"""
+        print 'Refreshing api docs...'
+        if WINDOWS_OS:
+            command = 'activate.bat & sphinx-apidoc -f -o docs/devel/api MoinMoin & %s MoinMoin/script/win/dos2unix.py docs/devel/api' % sys.executable
+        else:
+            command = 'source ./activate; sphinx-apidoc -f -o docs/devel/api MoinMoin'
+        result = subprocess.call(command, shell=True)
+
+    def cmd_dist(self, *args):
+        """create distribution archive in dist/"""
+        print 'Deleting wiki data, then creating distribution archive in /dist, output written to %s.' % DIST
+        self.cmd_del_wiki(*args)
+        command = '%s setup.py sdist' % sys.executable
+        with open(DIST, 'w') as messages:
+            result = subprocess.call(command, shell=True, stderr=messages, stdout=messages)
+        print 'Summary message from %s is shown below:' % DIST
+        search_for_phrase(DIST)
+        if result == 0:
+            print 'Success: a distribution archive was created in /dist.'
+        else:
+            print 'Error: create dist failed with return code = %s. Do "%s log dist" to see complete log.' % (result, M)
+
+    def cmd_del_all(self, *args):
+        """same as running the 4 del-* commands below"""
+        self.cmd_del_orig(*args)
+        self.cmd_del_pyc(*args)
+        self.cmd_del_rej(*args)
+        self.cmd_del_wiki(*args)
+
+    def cmd_del_orig(self, *args):
+        """delete all files matching *.orig"""
+        delete_files('*.orig')
+
+    def cmd_del_pyc(self, *args):
+        """delete all files matching *.pyc"""
+        delete_files('*.pyc')
+
+    def cmd_del_rej(self, *args):
+        """delete all files matching *.rej"""
+        delete_files('*.rej')
+
+    def cmd_del_wiki(self, *args):
+        """create a just-in-case backup, then delete all wiki data"""
+        if WINDOWS_OS:
+            command = 'moin.bat save --all-backends --file %s' % JUST_IN_CASE_BACKUP
+        else:
+            command = './moin save --all-backends --file %s' % JUST_IN_CASE_BACKUP
+        if wiki_exists():
+            print 'Creating a backup named %s; then deleting all wiki data and indexes...' % JUST_IN_CASE_BACKUP
+            with open(DELWIKI, 'w') as messages:
+                result = subprocess.call(command, shell=True, stderr=messages, stdout=messages)
+            if result != 0:
+                print 'Error: backup failed with return code = %s. Complete log is in %s.' % (result, DELWIKI)
+        # destroy wiki even if backup fails
+        if os.path.isdir('wiki/data') or os.path.isdir('wiki/index'):
+            shutil.rmtree('wiki/data')
+            shutil.rmtree('wiki/index')
+            print 'Wiki data successfully deleted.'
+        else:
+            print 'Wiki data not deleted because it does not exist.'
+
+
+if __name__ == '__main__':
+    # create a set of valid menu choices
+    commands = Commands()
+    choices = set()
+    names = dir(commands)
+    for name in names:
+        if name.startswith('cmd_'):
+            choices.add(name)
+
+    if len(sys.argv) == 1 or sys.argv[1] == '-h' or sys.argv[1] == '--help':
+        print help
+    else:
+        if sys.argv[1] != 'quickinstall' and not (os.path.isfile('activate') or os.path.isfile('activate.bat')):
+            print 'Error: files created by quickinstall are missing, run "%s quickinstall" and try again.' % M
+        else:
+            choice = 'cmd_%s' % sys.argv[1]
+            choice = choice.replace('-', '_')
+            if choice in choices:
+                choice = getattr(commands, choice)
+                choice(*sys.argv[2:])
+            else:
+                print help
+                print 'Error: unknown menu selection "%s"' % sys.argv[1]
--- a/quickinstall.py	Fri Jan 24 02:31:43 2014 +0100
+++ b/quickinstall.py	Sun Feb 02 15:05:16 2014 +0100
@@ -13,42 +13,67 @@
 import MoinMoin  # validate python version
 import argparse
 import logging
-import os.path
+import os
 import subprocess
 import sys
 try:
     import virtualenv
 except ImportError:
     sys.exit("""
-Error: import virtualenv failed, either
-  virtualenv is not installed (see installation docs)
-or
-  the virtual environment must be deactivated before rerunning quickinstall.py
+Error: import virtualenv failed, either virtualenv is not installed (see installation docs)
+or the virtual environment must be deactivated before rerunning quickinstall.py
 """)
 
+from make import Commands, WINDOWS_OS, M
+
+
+WIN_INFO = 'm.bat, activate.bat, deactivate.bat, and moin.bat are created by quickinstall.py'
+NIX_INFO = 'the m bash script and the activate and moin symlinks are created by quickinstall.py'
+
+
+def create_m():
+    """Create an 'm.bat or 'm' bash script that will run make.py using this Python"""
+    if WINDOWS_OS:
+        with open('m.bat', 'w') as f:
+            f.write(':: {}\n\n@{} make.py %*\n'.format(WIN_INFO, sys.executable))
+    else:
+        with open('m', 'w') as f:
+            f.write('# {}\n\n{} make.py $*\n'.format(NIX_INFO, sys.executable))
+            os.fchmod(f.fileno(), 0775)
+
 
 class QuickInstall(object):
-    def __init__(self, source, venv=None):
+    def __init__(self, source, venv=None, download_cache=None):
         self.dir_source = source
-        if not venv:
+        if venv is None:
             base, source_name = os.path.split(source)
-            venv = os.path.join(base, 'venv-{}-{}'.format(source_name, os.path.basename(sys.executable)))
+            executable = os.path.basename(sys.executable).split('.exe')[0]
+            venv = os.path.join(base, '{}-venv-{}'.format(source_name, executable))
+        if download_cache is None:
+            # make cache sibling of ~/pip/pip.log or ~/.pip/pip.log
+            if WINDOWS_OS:
+                download_cache = '~/pip/pip-download-cache'
+            else:
+                # XXX: move cache to XDG cache dir
+                download_cache = '~/.pip/pip-download-cache'
 
+        venv = os.path.abspath(venv)
         venv_home, venv_lib, venv_inc, venv_bin = virtualenv.path_locations(venv)
         self.dir_venv = venv_home
         self.dir_venv_bin = venv_bin
+        self.download_cache = os.path.normpath(os.path.expanduser(download_cache))
 
     def __call__(self):
         self.do_venv()
         self.do_install()
         self.do_catalog()
+        self.do_helpers()
 
         sys.stdout.write("""
-Succesfully created or updated venv
-  {0}
-You can run MoinMoin as
-  {1}
-""".format(self.dir_venv, os.path.join(self.dir_venv_bin, 'moin')))
+Pip cache location is at {0}
+
+Successfully created or updated venv at {1}
+""".format(self.download_cache, self.dir_venv))
 
     def do_venv(self):
         virtualenv.create_environment(self.dir_venv)
@@ -57,9 +82,8 @@
         args = [
             os.path.join(self.dir_venv_bin, 'pip'),
             'install',
-            # XXX: move cache to XDG cache dir
             '--download-cache',
-            os.path.join(os.path.dirname(self.dir_venv), '.pip-download-cache'),
+            self.download_cache,
             '--editable',
             self.dir_source,
         ]
@@ -80,12 +104,46 @@
             '--directory', os.path.join(os.path.dirname(__file__), 'MoinMoin', 'translations'),
         ))
 
+    def create_wrapper(self, filename, target):
+        """Create files in the repo root that wrap files in <path-to-virtual-env>\Scripts."""
+        target = os.path.join(self.dir_venv_bin, target)
+        with open(filename, 'w') as f:
+            f.write(':: {}\n\n@call {} %*\n'.format(WIN_INFO, target))
+
+    def do_helpers(self):
+        """Create small helper scripts or symlinks in repo root, avoid keying the long path to virtual env."""
+        create_m()  # recreate m.bat or ./m to insure it is consistent with activate and moin
+        if WINDOWS_OS:
+            # windows commands are: activate | deactivate | moin
+            self.create_wrapper('activate.bat', 'activate.bat')
+            self.create_wrapper('deactivate.bat', 'deactivate.bat')
+            self.create_wrapper('moin.bat', 'moin.exe')
+        else:
+            # linux commands are: source activate | deactivate | ./moin
+            if os.path.exists('activate'):
+                os.unlink('activate')
+            if os.path.exists('moin'):
+                os.unlink('moin')
+            os.symlink(os.path.join(self.dir_venv_bin, 'activate'), 'activate')  # no need to define deactivate on unix
+            os.symlink(os.path.join(self.dir_venv_bin, 'moin'), 'moin')
+
 
 if __name__ == '__main__':
-    logging.basicConfig(level=logging.INFO)
+    if os.path.isfile('m') or os.path.isfile('m.bat'):
+        # create the virtual env
+        logging.basicConfig(level=logging.INFO)
 
-    parser = argparse.ArgumentParser()
-    parser.add_argument('venv', metavar='VENV', nargs='?', help='location of v(irtual)env')
-    args = parser.parse_args()
+        parser = argparse.ArgumentParser()
+        parser.add_argument('venv', metavar='VENV', nargs='?', help='location of v(irtual)env')
+        parser.add_argument('--download_cache', dest='download_cache', help='location of pip download cache')
+        args = parser.parse_args()
 
-    QuickInstall(os.path.dirname(os.path.realpath(sys.argv[0])), venv=args.venv)()
+        QuickInstall(os.path.dirname(os.path.realpath(sys.argv[0])), venv=args.venv, download_cache=args.download_cache)()
+    else:
+        # run this same script (quickinstall.py) again to create the virtual env
+        create_m()  # create file so above IF will be true next time around
+        # Use the make.py subprocess so user will see a few success/failure messages instead of ~500 info messages.
+        commands = Commands()
+        choice = getattr(commands, 'cmd_quickinstall')
+        choice(*sys.argv[1:])  # <override-path-to-venv> --download_cache <override-path-to-cache>
+        print '\n> > > Type "%s" for menu < < <' % M