From ec13f2d15f02b94e129340c83f092ac2e8df9fb9 Mon Sep 17 00:00:00 2001
From: Martijn Vermaat <martijn@vermaat.name>
Date: Thu, 12 Dec 2013 15:30:29 +0100
Subject: [PATCH] Use entrypoints instead of bin scripts

---
 bin/mutalyzer-cache-sync                      | 49 ----------
 bin/mutalyzer-json-service.wsgi               | 45 ---------
 bin/mutalyzer-mapping-import                  | 89 -----------------
 bin/mutalyzer-mapping-update                  | 56 -----------
 bin/mutalyzer-soap-service.wsgi               | 46 ---------
 bin/mutalyzer-website.wsgi                    | 56 -----------
 mutalyzer/entrypoints/__init__.py             |  3 +
 .../entrypoints/batch_processor.py            | 43 +++++---
 mutalyzer/entrypoints/cache_sync.py           | 52 ++++++++++
 mutalyzer/entrypoints/mapping_import.py       | 97 +++++++++++++++++++
 mutalyzer/entrypoints/mapping_update.py       | 56 +++++++++++
 .../entrypoints/mutalyzer.py                  | 58 +++++------
 mutalyzer/entrypoints/service_json.py         | 52 ++++++++++
 mutalyzer/entrypoints/service_soap.py         | 53 ++++++++++
 mutalyzer/entrypoints/website.py              | 72 ++++++++++++++
 setup.py                                      | 22 +++--
 16 files changed, 451 insertions(+), 398 deletions(-)
 delete mode 100755 bin/mutalyzer-cache-sync
 delete mode 100755 bin/mutalyzer-json-service.wsgi
 delete mode 100755 bin/mutalyzer-mapping-import
 delete mode 100755 bin/mutalyzer-mapping-update
 delete mode 100755 bin/mutalyzer-soap-service.wsgi
 delete mode 100755 bin/mutalyzer-website.wsgi
 create mode 100644 mutalyzer/entrypoints/__init__.py
 rename bin/mutalyzer-batchd => mutalyzer/entrypoints/batch_processor.py (61%)
 mode change 100755 => 100644
 create mode 100644 mutalyzer/entrypoints/cache_sync.py
 create mode 100644 mutalyzer/entrypoints/mapping_import.py
 create mode 100644 mutalyzer/entrypoints/mapping_update.py
 rename bin/mutalyzer => mutalyzer/entrypoints/mutalyzer.py (76%)
 mode change 100755 => 100644
 create mode 100644 mutalyzer/entrypoints/service_json.py
 create mode 100644 mutalyzer/entrypoints/service_soap.py
 create mode 100644 mutalyzer/entrypoints/website.py

diff --git a/bin/mutalyzer-cache-sync b/bin/mutalyzer-cache-sync
deleted file mode 100755
index 5e012949..00000000
--- a/bin/mutalyzer-cache-sync
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/usr/bin/env python
-"""
-Synchronize the database cache with other Mutalyzer instances.
-
-Usage:
-  {command} remote_wsdl url_template days
-
-  remote_wsdl:  Location of the remote WSDL description.
-  url_template: URL to remote downloads, where {{file}} is to be substituted
-                by the filename.
-  days:         Number of days to go back in the remote cache.
-
-
-This program is intended to be run daily from cron. Example:
-
-  25 5 * * *  mutalyzer-cache-sync 'http://dom1/?wsdl' 'http://dom1/{file}' 7
-  55 5 * * *  mutalyzer-cache-sync 'http://dom2/?wsdl' 'http://dom2/{file}' 7
-"""
-
-
-import sys
-
-from mutalyzer.output import Output
-from mutalyzer.sync import CacheSync
-from mutalyzer import Db
-from mutalyzer.util import format_usage
-
-
-def cache_sync(remote_wsdl, url_template, days):
-    """
-    Synchronize the database cache with other Mutalyzer instances.
-    """
-    output = Output(__file__)
-    database = Db.Cache()
-
-    sync = CacheSync(output, database)
-    sync.sync_with_remote(remote_wsdl, url_template, days)
-
-
-if __name__ == '__main__':
-    if len(sys.argv) < 4:
-        print format_usage()
-        sys.exit(1)
-    try:
-        days = int(sys.argv[3])
-    except ValueError:
-        print 'Last argument must be an integer.'
-        sys.exit(1)
-    cache_sync(sys.argv[1], sys.argv[2], int(sys.argv[3]))
diff --git a/bin/mutalyzer-json-service.wsgi b/bin/mutalyzer-json-service.wsgi
deleted file mode 100755
index bb7dc7d9..00000000
--- a/bin/mutalyzer-json-service.wsgi
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env python
-"""
-WSGI interface to the Mutalyzer HTTP/RPC+JSON web service.
-
-The WSGI interface is exposed through the module variable 'application'.
-
-Example Apache/mod_wsgi configuration:
-
-  WSGIScriptAlias /json /usr/local/bin/mutalyzer-json-service.wsgi
-
-Be sure to have this line first if you also define a / alias, like this:
-
-  WSGIScriptAlias /services /usr/local/bin/mutalyzer-json-service.wsgi
-  WSGIScriptAlias / /usr/local/bin/mutalyzer-website.wsgi
-
-You can also use the built-in HTTP server by running this file directly.
-
-To start the built-in HTTP server on port 8082:
-
-  /usr/local/bin/mutalyzer-json-service.wsgi 8082
-"""
-
-
-import sys
-from wsgiref.simple_server import make_server
-from spyne.server.wsgi import WsgiApplication
-from mutalyzer.services import json
-
-
-DEFAULT_PORT = 8082
-
-
-application = WsgiApplication(json.application)
-
-
-if __name__ == '__main__':
-    port = DEFAULT_PORT
-    if len(sys.argv) > 1:
-        try:
-            port = int(sys.argv[1])
-        except ValueError:
-            print 'Not a valid port number: %s' % sys.argv[1]
-            sys.exit(1)
-    print 'Listening at http://localhost:%d/' % port
-    make_server('localhost', port, application).serve_forever()
diff --git a/bin/mutalyzer-mapping-import b/bin/mutalyzer-mapping-import
deleted file mode 100755
index e680dc10..00000000
--- a/bin/mutalyzer-mapping-import
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/env python
-"""
-Update the database with mapping information for the given gene or genomic
-reference.
-
-Usage:
-  {command} source database gene|reference
-
-  source:    Import source, 'gene' for importing a specific gene from the UCSC
-             or 'reference' for importing from genomic reference.
-  database:  Database to update (i.e. 'hg18' or 'hg19').
-  gene:      Name of gene to import all transcripts mappings for from the UCSC
-             database (e.g. 'TTN').
-  reference: Genomic reference to import all genes from (e.g. 'NC_012920.1').
-             Not that currently no exon locations are supported, this has only
-             been tested on mtDNA.
-
-
-This program is intended to be run manually whenever transcript mappings for
-specific genes are required that are not yet in our database (i.e. they are
-not yet available from the NCBI, or they are mtDNA genes). It will not
-overwrite transcript/version entries that are already in our database.
-"""
-
-
-import sys
-
-from mutalyzer.output import Output
-from mutalyzer.mapping import UCSCUpdater, ReferenceUpdater
-from mutalyzer.util import format_usage
-
-
-def import_gene(database, gene):
-    """
-    Update the database with information from the UCSC.
-
-    @arg database: Database to update (i.e. 'hg18' or 'hg19').
-    @type database: string
-    @arg gene: Gene name to get transcript mapping info for.
-    @type gene: string
-
-    @todo: Also report how much was added/updated.
-    """
-    output = Output(__file__)
-    output.addMessage(__file__, -1, 'INFO',
-                      'Starting UCSC mapping data update (gene: %s)' % gene)
-
-    updater = UCSCUpdater(database)
-    updater.load(gene)
-    updater.merge()
-
-    output.addMessage(__file__, -1, 'INFO',
-                      'UCSC mapping data update end (gene: %s)' % gene)
-
-
-def import_reference(database, reference):
-    """
-    Update the database with information from the given reference.
-
-    @arg database: Database to update (i.e. 'hg18' or 'hg19').
-    @type database: string
-    @arg reference: Reference to get gene mappings from.
-    @type reference: string
-
-    @todo: Also report how much was added/updated.
-    """
-    output = Output(__file__)
-    output.addMessage(__file__, -1, 'INFO',
-                      'Starting reference mapping data update (reference: %s)' % reference)
-
-    updater = ReferenceUpdater(database)
-    updater.load(reference, output)
-    updater.merge()
-
-    output.addMessage(__file__, -1, 'INFO',
-                      'Reference mapping data update end (reference: %s)' % reference)
-
-
-if __name__ == '__main__':
-    if len(sys.argv) != 4:
-        print format_usage()
-        sys.exit(1)
-    if sys.argv[1] == 'gene':
-        import_gene(*sys.argv[2:])
-    elif sys.argv[1] == 'reference':
-        import_reference(*sys.argv[2:])
-    else:
-        print format_usage()
-        sys.exit(1)
diff --git a/bin/mutalyzer-mapping-update b/bin/mutalyzer-mapping-update
deleted file mode 100755
index 8fe6bb35..00000000
--- a/bin/mutalyzer-mapping-update
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python
-"""
-Update the database with mapping information from the NCBI.
-
-Usage:
-  {command} database mapping_file assembly
-
-  database:     Database to update (i.e. 'hg18' or 'hg19').
-  mapping_file: Path to the NCBI mapping information.
-  assembly:     Use only entries from this assembly (this is the 'group_name'
-                column in the NCBI mapping file).
-
-
-This program is intended to be run daily from cron. Example:
-
-  25 6 * * *  mutalyzer-mapping-update hg19 /tmp/seq_gene.md reference
-"""
-
-
-import sys
-
-from mutalyzer.output import Output
-from mutalyzer.mapping import NCBIUpdater
-from mutalyzer.util import format_usage
-
-
-def main(database, mapping_file, assembly):
-    """
-    Update the database with information from the NCBI.
-
-    @arg database: Database to update (i.e. 'hg18' or 'hg19').
-    @type database: string
-    @arg mapping_file: Path to NCBI mapping information.
-    @type mapping_file: string
-    @arg assembly: Use only entries from this assembly (this is the
-        'group_name' column in the NCBI mapping file).
-    @type assembly: string
-
-    @todo: Also report how much was added/updated.
-    """
-    output = Output(__file__)
-    output.addMessage(__file__, -1, 'INFO',
-                      'Starting NCBI mapping data update')
-
-    updater = NCBIUpdater(database)
-    updater.load(mapping_file, assembly)
-    updater.merge()
-
-    output.addMessage(__file__, -1, 'INFO', 'NCBI mapping data update end')
-
-
-if __name__ == '__main__':
-    if len(sys.argv) != 4:
-        print format_usage()
-        sys.exit(1)
-    main(*sys.argv[1:])
diff --git a/bin/mutalyzer-soap-service.wsgi b/bin/mutalyzer-soap-service.wsgi
deleted file mode 100755
index 31f7b797..00000000
--- a/bin/mutalyzer-soap-service.wsgi
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env python
-"""
-WSGI interface to the Mutalyzer SOAP web service.
-
-The WSGI interface is exposed through the module variable 'application'.
-
-Example Apache/mod_wsgi configuration:
-
-  WSGIScriptAlias /services /usr/local/bin/mutalyzer-soap-service.wsgi
-
-Be sure to have this line first if you also define a / alias, like this:
-
-  WSGIScriptAlias /services /usr/local/bin/mutalyzer-soap-service.wsgi
-  WSGIScriptAlias / /usr/local/bin/mutalyzer-website.wsgi
-
-You can also use the built-in HTTP server by running this file directly.
-
-To start the built-in HTTP server on port 8081:
-
-  /usr/local/bin/mutalyzer-soap-service.wsgi 8081
-"""
-
-
-import sys
-from wsgiref.simple_server import make_server
-from spyne.server.wsgi import WsgiApplication
-from mutalyzer.services import soap
-
-
-DEFAULT_PORT = 8081
-
-
-application = WsgiApplication(soap.application)
-
-
-if __name__ == '__main__':
-    port = DEFAULT_PORT
-    if len(sys.argv) > 1:
-        try:
-            port = int(sys.argv[1])
-        except ValueError:
-            print 'Not a valid port number: %s' % sys.argv[1]
-            sys.exit(1)
-    print 'Listening at http://localhost:%d/' % port
-    print 'WDSL file is at http://localhost:%d/?wsdl' % port
-    make_server('localhost', port, application).serve_forever()
diff --git a/bin/mutalyzer-website.wsgi b/bin/mutalyzer-website.wsgi
deleted file mode 100755
index 237e7279..00000000
--- a/bin/mutalyzer-website.wsgi
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python
-"""
-WSGI interface to the Mutalyzer website.
-
-The WSGI interface is exposed through the module variable 'application'.
-Static files are not handled by this interface and should be served through
-the '/static' url prefix separately.
-
-Example Apache/mod_wsgi configuration:
-
-  Alias /static /var/www/mutalyzer/static
-  WSGIScriptAlias / /usr/local/bin/mutalyzer-website.wsgi
-
-You can also use the built-in HTTP server by running this file directly.
-Note, however, that static files are only found by this server in a 'static'
-subdirectory of the current working directory. If you're running Mutalyzer
-from its source code directory, you can satisfy this by creating a quick
-symbolic link:
-
-  ln -s mutalyzer/templates/static
-
-Another common practice is to use Nginx to directly serve the static files
-and act as a reverse proxy server to the Mutalyzer HTTP server.
-
-Example Nginx configuration:
-
-  server {
-    listen 80;
-    location /static/ {
-      root /var/www/mutalyzer/static;
-      if (-f $request_filename) {
-        rewrite ^/static/(.*)$  /static/$1 break;
-      }
-    }
-    location / {
-      proxy_read_timeout 300;  # 5 minutes
-      proxy_pass http://127.0.0.1:8080;
-    }
-  }
-
-Now start the built-in HTTP server on port 8080:
-
-  mutalyzer-website.wsgi 8080
-
-@todo: Integrate webservice.py (http://webpy.org/cookbook/webservice/).
-"""
-
-
-from mutalyzer import website
-
-
-application = website.app.wsgifunc()
-
-
-if __name__ == '__main__':
-    website.app.run()
diff --git a/mutalyzer/entrypoints/__init__.py b/mutalyzer/entrypoints/__init__.py
new file mode 100644
index 00000000..9644b1aa
--- /dev/null
+++ b/mutalyzer/entrypoints/__init__.py
@@ -0,0 +1,3 @@
+"""
+Entry points to Mutalyzer.
+"""
diff --git a/bin/mutalyzer-batchd b/mutalyzer/entrypoints/batch_processor.py
old mode 100755
new mode 100644
similarity index 61%
rename from bin/mutalyzer-batchd
rename to mutalyzer/entrypoints/batch_processor.py
index 84f7869f..18cffd72
--- a/bin/mutalyzer-batchd
+++ b/mutalyzer/entrypoints/batch_processor.py
@@ -1,39 +1,37 @@
-#!/usr/bin/env python
 """
-Daemon for processing scheduled batch jobs.
+Mutalyzer batch processor.
 
-The process can be shutdown gracefully by sending a SIGINT (Ctrl+C) or SIGTERM
-signal.
-
-@todo: Get rid of ugly exception logging.
-@todo: Reload configuration without restarting (for example, on SIGHUP).
+.. todo: Get rid of ugly exception logging.
+.. todo: Reload configuration without restarting (for example, on SIGHUP).
 """
 
 
+import argparse
 import signal
 import sys
 import time
 import traceback
 
-from mutalyzer import config
-from mutalyzer.Db import Batch, Counter
-from mutalyzer.Scheduler import Scheduler
+from .. import config
+from .. import Db
+from .. import Scheduler
 
 
-def daemonize():
+def process():
     """
     Run forever in a loop processing scheduled batch jobs.
     """
-    database = Batch()
-    counter = Counter()
-    scheduler = Scheduler(database)
+    database = Db.Batch()
+    counter = Db.Counter()
+    scheduler = Scheduler.Scheduler(database)
 
     def handle_exit(signum, stack_frame):
         if scheduler.stopped():
             sys.stderr.write('mutalyzer-batchd: Terminated\n')
             sys.exit(1)
         if signum == signal.SIGINT:
-            sys.stderr.write('mutalyzer-batchd: Hitting Ctrl+C again will terminate any running job!\n')
+            sys.stderr.write('mutalyzer-batchd: Hitting Ctrl+C again will '
+                             'terminate any running job!\n')
         scheduler.stop()
 
     signal.signal(signal.SIGTERM, handle_exit)
@@ -59,5 +57,18 @@ def daemonize():
     sys.exit(0)
 
 
+def main():
+    """
+    Command line interface to the batch processor.
+    """
+    parser = argparse.ArgumentParser(
+        description='Mutalyzer batch processor.',
+        epilog='The process can be shutdown gracefully by sending a SIGINT '
+        '(Ctrl+C) or SIGTERM signal.')
+
+    parser.parse_args()
+    process()
+
+
 if __name__ == '__main__':
-    daemonize()
+    main()
diff --git a/mutalyzer/entrypoints/cache_sync.py b/mutalyzer/entrypoints/cache_sync.py
new file mode 100644
index 00000000..f238ce62
--- /dev/null
+++ b/mutalyzer/entrypoints/cache_sync.py
@@ -0,0 +1,52 @@
+"""
+Synchronize the database cache with other Mutalyzer instances.
+
+This program is intended to be run daily from cron. Example:
+
+    25 5 * * *  mutalyzer-cache-sync 'http://dom1/?wsdl' 'http://dom1/{file}' -H 7
+    55 5 * * *  mutalyzer-cache-sync 'http://dom2/?wsdl' 'http://dom2/{file}' -H 7
+"""
+
+
+import argparse
+
+from .. import Db
+from .. import output
+from .. import sync
+
+
+def sync_cache(remote_wsdl, url_template, history=7):
+    """
+    Synchronize the database cache with other Mutalyzer instances.
+    """
+    output = output.Output(__file__)
+    database = Db.Cache()
+
+    cache_sync = sync.CacheSync(output, database)
+    cache_sync.sync_with_remote(remote_wsdl, url_template, history)
+
+
+def main():
+    """
+    Command-line interface to the cache synchronizer.
+    """
+    parser = argparse.ArgumentParser(
+        description='Mutalyzer cache synchronizer.')
+    parser.add_argument(
+        'wsdl', metavar='WSDL',
+        help='location of the remote WSDL description')
+    parser.add_argument(
+        'url_template', metavar='URL_TEMPLATE',
+        help='URL for remote downloads, in which the filename is to be '
+        'substituted for {{file}}')
+    parser.add_argument(
+        '-H', '--history', metavar='DAYS', dest='history', type=int,
+        default=7, help='number of days to go back in the remote cache '
+        '(default: 7)')
+
+    args = parser.parse_args()
+    sync_cache(args.wsdl, args.url_template, history=args.history)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/mutalyzer/entrypoints/mapping_import.py b/mutalyzer/entrypoints/mapping_import.py
new file mode 100644
index 00000000..4982ad1a
--- /dev/null
+++ b/mutalyzer/entrypoints/mapping_import.py
@@ -0,0 +1,97 @@
+"""
+Update the database with mapping information for the given gene or genomic
+reference.
+"""
+
+
+import argparse
+
+from .. import output
+from .. import mapping
+
+
+def import_gene(database, gene):
+    """
+    Update the database with information from the UCSC.
+
+    .. todo: Also report how much was added/updated.
+    """
+    o = output.Output(__file__)
+    o.addMessage(__file__, -1, 'INFO',
+                 'Starting UCSC mapping data update (gene: %s)' % gene)
+
+    updater = mapping.UCSCUpdater(database)
+    updater.load(gene)
+    updater.merge()
+
+    o.addMessage(__file__, -1, 'INFO',
+                 'UCSC mapping data update end (gene: %s)' % gene)
+
+
+def import_reference(database, reference):
+    """
+    Update the database with information from the given reference.
+
+    .. todo: Also report how much was added/updated.
+
+    .. note: Currently no exon locations are supported, this has only been
+       tested on mtDNA.
+    """
+    o = output.Output(__file__)
+    o.addMessage(__file__, -1, 'INFO',
+                 'Starting reference mapping data update (reference: %s)' % reference)
+
+    updater = mapping.ReferenceUpdater(database)
+    updater.load(reference, o)
+    updater.merge()
+
+    o.addMessage(__file__, -1, 'INFO',
+                 'Reference mapping data update end (reference: %s)' % reference)
+
+
+def main():
+    """
+    Command-line interface to the mapping importer.
+    """
+    database_parser = argparse.ArgumentParser(add_help=False)
+    database_parser.add_argument(
+        '-d', '--database', metavar='DATABASE', dest='database',
+        default='hg19', help='database to import to (default: hg19)')
+
+    parser = argparse.ArgumentParser(
+        description='Mutalyzer mapping importer.',
+        epilog='This program is intended to be run manually whenever '
+        'transcript mappings for specific genes are required that are not '
+        'yet in our database (i.e., they are not yet available from the '
+        'NCBI, or they are mtDNA genes). It will not overwrite '
+        'transcript/version entries that are already in our database.',
+        parents=[database_parser])
+
+    subparsers = parser.add_subparsers(
+        title='subcommands', dest='subcommand', help='subcommand help')
+
+    p = subparsers.add_parser(
+        'gene', help='import gene', parents=[database_parser],
+        description='Import gene mapping from the UCSC.')
+    p.add_argument(
+        'gene', metavar='GENE_SYMBOL',
+        help='gene to import all transcript mappings for from the UCSC '
+        'database (example: TTN)')
+
+    p = subparsers.add_parser(
+        'reference', help='import reference', parents=[database_parser],
+        description='Import genomic reference file')
+    p.add_argument(
+        'reference', metavar='FILE',
+        help='genomic reference to import all genes from (example: '
+        'NC_012920.1)')
+
+    args = parser.parse_args()
+    if args.subcommand == 'gene':
+        import_gene(args.database, args.gene)
+    if args.subcommand == 'reference':
+        import_reference(args.database, args.reference)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/mutalyzer/entrypoints/mapping_update.py b/mutalyzer/entrypoints/mapping_update.py
new file mode 100644
index 00000000..d63fbe7b
--- /dev/null
+++ b/mutalyzer/entrypoints/mapping_update.py
@@ -0,0 +1,56 @@
+"""
+Update the database with mapping information from the NCBI.
+
+This program is intended to be run daily from cron. Example:
+
+    25 6 * * *  mutalyzer-mapping-update hg19 /tmp/seq_gene.md reference
+"""
+
+
+import argparse
+
+from .. import output
+from .. import mapping
+
+
+def update_mapping(database, mapping_file, assembly):
+    """
+    Update the database with information from the NCBI.
+
+    .. todo: Also report how much was added/updated.
+    """
+    o = output.Output(__file__)
+    o.addMessage(__file__, -1, 'INFO',
+                 'Starting NCBI mapping data update')
+
+    updater = mapping.NCBIUpdater(database)
+    updater.load(mapping_file, assembly)
+    updater.merge()
+
+    o.addMessage(__file__, -1, 'INFO', 'NCBI mapping data update end')
+
+
+def main():
+    """
+    Command-line interface to the mapping updater.
+    """
+    parser = argparse.ArgumentParser(
+        description='Mutalyzer mapping updater.')
+
+    parser.add_argument(
+        'mapping', metavar='FILE',
+        help='Path to the NCBI mapping information (example: seq_gene.md)')
+    parser.add_argument(
+        'assembly', metavar='ASSEMBLY',
+        help='use only entries from this assembly (this is the group_name '
+        'column in the NCBI mapping file)')
+    parser.add_argument(
+        '-d', '--database', metavar='DATABASE', dest='database',
+        default='hg19', help='database to update (default: hg19)')
+
+    args = parser.parse_args()
+    update_mapping(args.database, args.mapping, args.assembly)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/bin/mutalyzer b/mutalyzer/entrypoints/mutalyzer.py
old mode 100755
new mode 100644
similarity index 76%
rename from bin/mutalyzer
rename to mutalyzer/entrypoints/mutalyzer.py
index 67c4e33f..d123482f
--- a/bin/mutalyzer
+++ b/mutalyzer/entrypoints/mutalyzer.py
@@ -1,38 +1,28 @@
-#!/usr/bin/env python
 """
-Command-line interface to the nomenclature checker.
+Mutalyzer command-line name checker.
 
-Usage:
-  {command} variant
-
-  variant: The variant description to check.
-
-
-@todo: Refactor this file.
+.. todo: Refactor this file.
 """
 
 
-import sys
-import os
+import argparse
 
-from mutalyzer import variantchecker
-from mutalyzer.output import Output
-from mutalyzer.util import format_usage
-from mutalyzer import describe
+from .. import describe
+from .. import output
+from .. import variantchecker
 
-def main(cmd):
-    """
-    Command line interface to the name checker.
 
-    @todo: documentation
+def check_name(description):
+    """
+    Run the name checker.
     """
-    O = Output(__file__)
+    O = output.Output(__file__)
 
-    O.addMessage(__file__, -1, "INFO", "Received variant " + cmd)
+    O.addMessage(__file__, -1, "INFO", "Received variant " + description)
 
-    RD = variantchecker.check_variant(cmd, O)
+    RD = variantchecker.check_variant(description, O)
 
-    O.addMessage(__file__, -1, "INFO", "Finished processing variant " + cmd)
+    O.addMessage(__file__, -1, "INFO", "Finished processing variant " + description)
 
     ### OUTPUT BLOCK ###
     gn = O.getOutput("genename")
@@ -116,16 +106,20 @@ def main(cmd):
         print extractedProt
         #print "+++ %s" % O.getOutput("myTranscriptDescription")
 
-    #if
-    ### OUTPUT BLOCK ###
-    del O
-#main
 
+def main():
+    """
+    Command-line interface to the name checker.
+    """
+    parser = argparse.ArgumentParser(
+        description='Mutalyzer command-line name checker.')
+    parser.add_argument(
+        'description', metavar='DESCRIPTION',
+        help='variant description to run the name checker on')
 
-if __name__ == '__main__':
+    args = parser.parse_args()
+    check_name(args.description)
 
-    if len(sys.argv) < 2:
-        print format_usage()
-        sys.exit(1)
 
-    main(sys.argv[1])
+if __name__ == '__main__':
+    main()
diff --git a/mutalyzer/entrypoints/service_json.py b/mutalyzer/entrypoints/service_json.py
new file mode 100644
index 00000000..9dd1952e
--- /dev/null
+++ b/mutalyzer/entrypoints/service_json.py
@@ -0,0 +1,52 @@
+"""
+WSGI interface to the Mutalyzer HTTP/RPC+JSON webservice.
+
+Example Apache/mod_wsgi configuration:
+
+    WSGIScriptAlias /json /usr/local/bin/mutalyzer-service-json
+
+Be sure to have this line first if you also define a / alias, like this:
+
+    WSGIScriptAlias /json /usr/local/bin/mutalyzer-service-json
+    WSGIScriptAlias / /usr/local/bin/mutalyzer-website
+
+You can also use the built-in HTTP server by running this file directly.
+"""
+
+
+import argparse
+import sys
+
+from wsgiref.simple_server import make_server
+from spyne.server.wsgi import WsgiApplication
+
+from ..services import json
+
+
+application = WsgiApplication(json.application)
+
+
+def debugserver(port):
+    """
+    Run the webservice with the Python built-in HTTP server.
+    """
+    sys.stderr.write('Listening on http://localhost:%d/\n' % port)
+    make_server('localhost', port, application).serve_forever()
+
+
+def main():
+    """
+    Command-line interface to the HTTP/RPC+JSON webservice.
+    """
+    parser = argparse.ArgumentParser(
+        description='Mutalyzer HTTP/RPC+JSON webservice.')
+    parser.add_argument(
+        '-p', '--port', metavar='NUMBER', dest='port', type=int,
+        default=8082, help='port to run the webservice on (default: 8082)')
+
+    args = parser.parse_args()
+    debugserver(args.port)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/mutalyzer/entrypoints/service_soap.py b/mutalyzer/entrypoints/service_soap.py
new file mode 100644
index 00000000..eb91a82e
--- /dev/null
+++ b/mutalyzer/entrypoints/service_soap.py
@@ -0,0 +1,53 @@
+"""
+WSGI interface to the Mutalyzer SOAP webservice.
+
+Example Apache/mod_wsgi configuration:
+
+    WSGIScriptAlias /soap /usr/local/bin/mutalyzer-service-soap
+
+Be sure to have this line first if you also define a / alias, like this:
+
+    WSGIScriptAlias /soap /usr/local/bin/mutalyzer-service-soap
+    WSGIScriptAlias / /usr/local/bin/mutalyzer-website
+
+You can also use the built-in HTTP server by running this file directly.
+"""
+
+
+import argparse
+import sys
+
+from wsgiref.simple_server import make_server
+from spyne.server.wsgi import WsgiApplication
+
+from ..services import soap
+
+
+application = WsgiApplication(soap.application)
+
+
+def debugserver(port):
+    """
+    Run the webservice with the Python built-in HTTP server.
+    """
+    sys.stderr.write('Listening on http://localhost:%d/\n' % port)
+    sys.stderr.write('WDSL file is at http://localhost:%d/?wsdl\n' % port)
+    make_server('localhost', port, application).serve_forever()
+
+
+def main():
+    """
+    Command-line interface to the SOAP webservice.
+    """
+    parser = argparse.ArgumentParser(
+        description='Mutalyzer SOAP webservice.')
+    parser.add_argument(
+        '-p', '--port', metavar='NUMBER', dest='port', type=int,
+        default=8081, help='port to run the webservice on (default: 8081)')
+
+    args = parser.parse_args()
+    debugserver(args.port)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/mutalyzer/entrypoints/website.py b/mutalyzer/entrypoints/website.py
new file mode 100644
index 00000000..3a16feff
--- /dev/null
+++ b/mutalyzer/entrypoints/website.py
@@ -0,0 +1,72 @@
+"""
+WSGI interface to the Mutalyzer website.
+
+The WSGI interface is exposed through the module variable 'application'.
+Static files are not handled by this interface and should be served through
+the '/static' url prefix separately.
+
+Example Apache/mod_wsgi configuration:
+
+    Alias /static /var/www/mutalyzer/static
+    WSGIScriptAlias / /usr/local/bin/mutalyzer-website
+
+You can also use the built-in HTTP server by running this file directly.
+Note, however, that static files are only found by this server in a 'static'
+subdirectory of the current working directory. If you're running Mutalyzer
+from its source code directory, you can satisfy this by creating a quick
+symbolic link:
+
+    ln -s mutalyzer/templates/static
+
+Another common practice is to use Nginx to directly serve the static files
+and act as a reverse proxy server to the Mutalyzer HTTP server.
+
+Example Nginx configuration:
+
+    server {
+      listen 80;
+      location /static/ {
+        root /var/www/mutalyzer/static;
+        if (-f $request_filename) {
+          rewrite ^/static/(.*)$  /static/$1 break;
+        }
+      }
+      location / {
+        proxy_read_timeout 300;  # 5 minutes
+        proxy_pass http://127.0.0.1:8080;
+      }
+    }
+"""
+
+
+import argparse
+
+from .. import website
+
+
+application = website.app.wsgifunc()
+
+
+def debugserver():
+    """
+    Run the website with the Python built-in HTTP server.
+    """
+    website.app.run()
+
+
+def main():
+    """
+    Command-line interface to the website.
+    """
+    parser = argparse.ArgumentParser(
+        description='Mutalyzer website.')
+    parser.add_argument(
+        'port', metavar='NUMBER', type=int, nargs='?', default=8080,
+        help='port to run the website on (default: 8080)')
+
+    args = parser.parse_args()
+    debugserver()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/setup.py b/setup.py
index 707c8b0f..d7f16f16 100644
--- a/setup.py
+++ b/setup.py
@@ -40,16 +40,20 @@ setup(
     license='Not distributable',
     platforms=['any'],
     install_requires=install_requires,
-    packages=['mutalyzer', 'mutalyzer.parsers', 'mutalyzer.services'],
+    packages=['mutalyzer',
+              'mutalyzer.entrypoints',
+              'mutalyzer.parsers',
+              'mutalyzer.services'],
     include_package_data=True,
-    scripts=['bin/mutalyzer',
-             'bin/mutalyzer-batchd',
-             'bin/mutalyzer-cache-sync',
-             'bin/mutalyzer-mapping-update',
-             'bin/mutalyzer-mapping-import',
-             'bin/mutalyzer-soap-service.wsgi',
-             'bin/mutalyzer-json-service.wsgi',
-             'bin/mutalyzer-website.wsgi'],
+    entry_points = {'console_scripts': [
+        'mutalyzer = mutalyzer.entrypoints.mutalyzer:main',
+        'mutalyzer-batch-processor = mutalyzer.entrypoints.batch_processor:main',
+        'mutalyzer-cache-sync = mutalyzer.entrypoints.cache_sync:main',
+        'mutalyzer-mapping-import = mutalyzer.entrypoints.mapping_import:main',
+        'mutalyzer-mapping-update = mutalyzer.entrypoints.mapping_update:main',
+        'mutalyzer-service-json = mutalyzer.entrypoints.service_json:main',
+        'mutalyzer-service-soap = mutalyzer.entrypoints.service_soap:main',
+        'mutalyzer-website = mutalyzer.entrypoints.website:main']},
     zip_safe=False
 )
 
-- 
GitLab