<commit_before>#! /usr/bin/python2
# vim: set fileencoding=utf-8
"""Plot dataset in (reduced) dimension 2 or 3."""
import sys
try:
    # old version of matplotlib on some computers
    del sys.path[sys.path.index('/usr/lib/pymodules/python2.7')]
except ValueError:
    pass
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import prettyplotlib as ppl
import matplotlib as mpl
from sklearn import manifold, decomposition  # , datasets
import calc_tsne

def plot_embedding(figure, index, method, run_time, data, classes, dimension):
    """Scatter subplot `data` with colors corresponding to `classes` on
    `figure` at position `index` in `dimension`D. Title is made of `method`
    and `run_time`."""
    common = dict(c=classes, cmap=mpl.cm.Spectral, alpha=0.85)
    if dimension == 2:
        axe = figure.add_subplot(5, 3, 1 + index)
        ppl.scatter(data[:, 0], data[:, 1], **common)
    elif dimension == 3:
        axe = figure.add_subplot(5, 3, 1 + index, projection="3d")
        ppl.scatter(data[:, 0], data[:, 1], data[:, 2], **common)
    else:
        raise ValueError(dimension)
    plt.title("{} ({:.2g} sec)".format(method, run_time))
    axe.xaxis.set_major_formatter(mpl.ticker.NullFormatter())
    axe.yaxis.set_major_formatter(mpl.ticker.NullFormatter())
    if dimension == 3:
        axe.zaxis.set_major_formatter(mpl.ticker.NullFormatter())
    plt.axis('tight')

def choose_decomposition_method(method, n_components):
    """Return the decomposition corresponding to `method`."""
    if method == 'PCA':
        return decomposition.PCA(n_components)
    elif method == 'Randomized PCA':
        return decomposition.RandomizedPCA(n_components)
    elif method == 'Kernel PCA':
        return decomposition.KernelPCA(n_components, kernel='rbf')
    elif method == 'Sparse PCA':
        return decomposition.SparsePCA(n_components, n_jobs=1)
    elif method == 'SVD':
        return decomposition.TruncatedSVD(n_components)
    elif method == 'Factor Analysis':
        return decomposition.FactorAnalysis(n_components)
    elif method == 'ICA':
        return decomposition.FastICA(n_components)
    raise ValueError('{} is not a known method'.format(method))

def choose_manifold_method(method, n_components, n_neighbors):
    """Return the manifold corresponding to `method`."""
    method = method.lower()
    if method in ['standard', 'ltsa', 'hessian', 'modified']:
        # solver = 'auto' if method != 'standard' else 'dense'
        return manifold.LocallyLinearEmbedding(n_neighbors, n_components,
                                               eigen_solver='dense',
                                               method=method)
    elif method == 'isomap':
        return manifold.Isomap(n_neighbors, n_components)
    elif method == 'mds':
        return manifold.MDS(n_components, max_iter=200, n_init=1)
    elif method == 'spectral':
        return manifold.SpectralEmbedding(n_components=n_components,
                                          n_neighbors=n_neighbors)
    elif method == 't-sne':
        return calc_tsne.tSNE(n_components)
    raise ValueError('{} is not a known method'.format(method))

def compute_embedding(high, method, n_components=2, n_neighbors=None):
    """Reduce dimension of `high` to `n_components` using `method`
    (parametrized by `n_neighbors`)"""
    n_neighbors = n_neighbors or (n_components * (n_components + 3) / 2) + 4
    try:
        projector = choose_manifold_method(method, n_components, n_neighbors)
    except ValueError:
        projector = choose_decomposition_method(method, n_components)
    start = time()
    lower = projector.fit_transform(high)
    return lower, time() - start

if __name__ == '__main__':
    from timeit import default_timer as time
    from ClosestNeighbor import load_matrix
    # pylint: disable=C0103
    city = sys.argv[1].strip().lower()
    nb_dim = 2 if len(sys.argv) <= 2 else int(sys.argv[2])
    features = load_matrix(city)['v']
    features[:, 5] = features[:, 5] / 8e5
    cats = (8*features[:, 5]).astype(int)
    Axes3D
    # n_points = 300
    # X, color = datasets.samples_generator.make_s_curve(n_points,
    # features, cats = datasets.samples_generator.make_swiss_roll(n_points,
    #                                                             noise=0.1,
    #                                                           random_state=0)
    fig = plt.figure(figsize=(34, 38))
    title = "{} venues of {} projected to {} dimensions"
    title = title.format(features.shape[0], city.title(), nb_dim)
    print(title)
    plt.suptitle(title, fontsize=14)
    # ax = fig.add_subplot(241, projection='3d')
    # plt.scatter(X[:, 0], X[:, 1], X[:, 2], c=color, cmap=mpl.cm.Spectral)

    methods = ['Standard', 'LTSA', 'Hessian', 'Modified', 'Isomap', 'MDS',
               'Spectral', 't-SNE', 'PCA', 'Randomized PCA', 'Kernel PCA',
               'Sparse PCA', 'SVD', 'Factor Analysis', 'ICA']

    for i, method in enumerate(methods):
        reduced, how_long = compute_embedding(features, method, nb_dim)
        plot_embedding(fig, i, method, how_long, reduced, cats, nb_dim)
        print("{}: {:.2g} sec".format(method, how_long))
    outfile = '{}_DR_{}.png'.format(city, nb_dim)
    plt.savefig(outfile, frameon=False, bbox_inches='tight',
                pad_inches=0.05)
<commit_msg>compute t-SNE in several cities and save the result as tsv<commit_after>#! /usr/bin/python2
# vim: set fileencoding=utf-8
"""Plot dataset in (reduced) dimension 2 or 3."""
import sys
try:
    # old version of matplotlib on some computers
    del sys.path[sys.path.index('/usr/lib/pymodules/python2.7')]
except ValueError:
    pass
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import prettyplotlib as ppl
import matplotlib as mpl
from sklearn import manifold, decomposition  # , datasets
import calc_tsne
import scipy.io as sio

def plot_embedding(figure, index, method, run_time, data, classes, dimension):
    """Scatter subplot `data` with colors corresponding to `classes` on
    `figure` at position `index` in `dimension`D. Title is made of `method`
    and `run_time`."""
    common = dict(c=classes, cmap=mpl.cm.Spectral, alpha=0.8, s=45)
    if dimension == 2:
        axe = figure.add_subplot(1, 1, 1 + index)
        ppl.scatter(data[:, 0], data[:, 1], **common)
    elif dimension == 3:
        axe = figure.add_subplot(5, 3, 1 + index, projection="3d")
        ppl.scatter(data[:, 0], data[:, 1], data[:, 2], **common)
    else:
        raise ValueError(dimension)
    plt.title("{} ({:.2g} sec)".format(method, run_time))
    axe.xaxis.set_major_formatter(mpl.ticker.NullFormatter())
    axe.yaxis.set_major_formatter(mpl.ticker.NullFormatter())
    if dimension == 3:
        axe.zaxis.set_major_formatter(mpl.ticker.NullFormatter())
    plt.axis('tight')

def choose_decomposition_method(method, n_components):
    """Return the decomposition corresponding to `method`."""
    if method == 'PCA':
        return decomposition.PCA(n_components)
    elif method == 'Randomized PCA':
        return decomposition.RandomizedPCA(n_components)
    elif method == 'Kernel PCA':
        return decomposition.KernelPCA(n_components, kernel='rbf')
    elif method == 'Sparse PCA':
        return decomposition.SparsePCA(n_components, n_jobs=1)
    elif method == 'SVD':
        return decomposition.TruncatedSVD(n_components)
    elif method == 'Factor Analysis':
        return decomposition.FactorAnalysis(n_components)
    elif method == 'ICA':
        return decomposition.FastICA(n_components)
    raise ValueError('{} is not a known method'.format(method))

def choose_manifold_method(method, n_components, n_neighbors):
    """Return the manifold corresponding to `method`."""
    method = method.lower()
    if method in ['standard', 'ltsa', 'hessian', 'modified']:
        # solver = 'auto' if method != 'standard' else 'dense'
        return manifold.LocallyLinearEmbedding(n_neighbors, n_components,
                                               eigen_solver='dense',
                                               method=method)
    elif method == 'isomap':
        return manifold.Isomap(n_neighbors, n_components)
    elif method == 'mds':
        return manifold.MDS(n_components, max_iter=200, n_init=1)
    elif method == 'spectral':
        return manifold.SpectralEmbedding(n_components=n_components,
                                          n_neighbors=n_neighbors)
    elif method == 't-sne':
        return calc_tsne.tSNE(n_components, perplexity=18, theta=0.3)
    raise ValueError('{} is not a known method'.format(method))

def compute_embedding(high, method, n_components=2, n_neighbors=None):
    """Reduce dimension of `high` to `n_components` using `method`
    (parametrized by `n_neighbors`)"""
    n_neighbors = n_neighbors or (n_components * (n_components + 3) / 2) + 4
    try:
        projector = choose_manifold_method(method, n_components, n_neighbors)
    except ValueError:
        projector = choose_decomposition_method(method, n_components)
    start = time()
    lower = projector.fit_transform(high)
    return lower, time() - start

if __name__ == '__main__':
    from timeit import default_timer as time
    from ClosestNeighbor import load_matrix
    import numpy as np
    # pylint: disable=C0103
    city = sys.argv[1].strip().lower()
    nb_dim = 2 if len(sys.argv) <= 2 else int(sys.argv[2])
    features = None
    origin = None
    cities = ['helsinki', 'stockholm', 'prague', 'paris', 'barcelona',
              # 'moscow', 'helsinki'
              # 'rome', 'berlin', 'amsterdam',
              ]
    for idx, city in enumerate(cities):
        mat = load_matrix(city)['v']
        coming_from = float(idx)*np.ones((1, mat.shape[0])).ravel()
        if features is not None:
            features = np.vstack([features, mat])
            origin = np.hstack([origin, coming_from]).ravel()
        else:
            features = mat
            origin = coming_from
    # features = load_matrix(city)['v']
    features[:, 5] = features[:, 5] / 8e5
    cats = (8*features[:, 5]).astype(int)
    Axes3D
    # n_points = 300
    # X, color = datasets.samples_generator.make_s_curve(n_points,
    # features, cats = datasets.samples_generator.make_swiss_roll(n_points,
    #                                                             noise=0.1,
    #                                                           random_state=0)
    # fig = plt.figure(figsize=(25, 18))
    # title = "{} venues of {} projected to {} dimensions"
    # title = title.format(features.shape[0], city.title(), nb_dim)
    # print(title)
    # plt.suptitle(title, fontsize=14)
    # ax = fig.add_subplot(241, projection='3d')
    # plt.scatter(X[:, 0], X[:, 1], X[:, 2], c=color, cmap=mpl.cm.Spectral)

    methods = ['Standard', 'LTSA', 'Hessian', 'Modified', 'Isomap', 'MDS',
               'Spectral', 't-SNE', 'PCA', 'Randomized PCA', 'Kernel PCA',
               'Sparse PCA', 'SVD', 'Factor Analysis', 'ICA']
    methods = ['t-SNE']

    # for i, method in enumerate(methods):
    #     reduced, how_long = compute_embedding(features, method, nb_dim)
    #     sio.savemat('tsne_'+city, {'cl': cats, 'data': reduced})
    #     plot_embedding(fig, i, method, how_long, reduced, cats, nb_dim)
    #     print("{}: {:.3f} sec".format(method, how_long))
    # outfile = '{}_DR_{}.png'.format(city, nb_dim)
    # plt.savefig(outfile, frameon=False, bbox_inches='tight',
    #             pad_inches=0.05)
    city_name = np.array(map(lambda x: cities[int(x)], origin))
    reduced, how_long = compute_embedding(features, 't-SNE', 2)
    to_export = np.hstack([reduced, cats.reshape((reduced.shape[0], 1)),
                           city_name.reshape((reduced.shape[0], 1))])
    np.savetxt('EU.tsv', to_export, comments='', delimiter='\t', fmt='%s',
               header='posx posy cat city'.replace(' ', '\t'))
<|endoftext|>


<commit_before>import logging
import config
import redis
from rq import Queue
from modules.groupComparisons.groupcomparisons import groupcomparisons
from modules.loggingFunctions import initialize_logging

# logging
log_file = initialize_logging()
log = logging.getLogger(__name__)

redis_url = config.REDIS_URL
redis_conn = redis.from_url(redis_url)
multiples_q = Queue('multiples', connection=redis_conn, default_timeout=600)

def blob_gc_enqueue(query, target):
    job_gc = multiples_q.enqueue(groupcomparisons, query, target, result_ttl=-1)
    log.info('JOB ID IS: ' + job_gc.get_id())
    return job_gc.get_id()
<commit_msg>CHANGE: have group comparisons run on the priority<commit_after>import logging
import config
import redis
from rq import Queue
from modules.groupComparisons.groupcomparisons import groupcomparisons
from modules.loggingFunctions import initialize_logging

# logging
log_file = initialize_logging()
log = logging.getLogger(__name__)

redis_url = config.REDIS_URL
redis_conn = redis.from_url(redis_url)
priority_q = Queue('priority', connection=redis_conn, default_timeout=600)

def blob_gc_enqueue(query, target):
    job_gc = priority_q.enqueue(groupcomparisons, query, target, result_ttl=-1)
    log.info('JOB ID IS: ' + job_gc.get_id())
    return job_gc.get_id()
<|endoftext|>


<commit_before>"""
Copyright [2009-2017] EMBL-European Bioinformatics Institute
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

"""
Usage:

1. Make sure that rfsearch, rfmake and other commands are in PATH.

2. python automate_rfsearch.py -i /path/to/emerge_file -d /path/to/output

Analysing result:

# check that all LSF jobs completed successfully
ls | wc -l
find . -type f -name lsf_output.txt | xargs grep 'Success' | wc -l

# count the number of lines above the best reversed hit
find . -type f -name outlist -exec sh -c 'sed -n "0,/REVERSED/p" {} | wc -l' \; -print

# get overlapping Rfam families
find . -type f -name overlap | xargs grep -o -P "RF\d{5}" | sort | uniq
"""

import argparse
import csv
import os
import re
import sys

def parse_input_file(filename):
    """
    Read input data and standardise sequences and names.
    Example input is provided in `example.tsv`.
    """
    with open(filename, 'r') as tsv:
        reader = csv.DictReader(tsv, delimiter='\t')
        for row in reader:
            sequence = row['Sequence (RNA or DNA)'].replace('-', '').upper()
            sequence = re.sub(r'\s', '', sequence)
            if len(sequence) < 50:
                continue
            yield {
                'sequence': sequence,
                'name': row['ncRNA name'].replace(' ', '_').replace("'", '').replace('/', '-'),
                'row_id': row['No.'],
            }

def run(args):
    """
    * create FASTA file
    * predict secondary structure
    * make SEED
    * launch rfsearch
    """
    for rna in parse_input_file(args.inputfile):
        folder = '%s_%s' % (rna['row_id'], rna['name'])
        rna_dir = os.path.join(args.destination, folder)
        if not os.path.exists(rna_dir):
            os.mkdir(rna_dir)
        else:
            overlap = os.path.join(rna_dir, 'overlap')
            if os.path.exists(overlap):
                continue
        os.chdir(rna_dir)
        with open('input.fasta', 'w') as fasta:
            fasta.write('>%s\n%s\n' % (rna['name'], rna['sequence']))
        cmd = ('module load mpi/openmpi-x86_64 && '
               'bsub -o {0}/lsf_output.txt -e {0}/lsf_error.txt -g /emerge '
                     '"cd {0} && '
                     'predict_ss.pl -infile input.fasta -outfile SEED -r && '
                     'rfsearch.pl -nodesc -t 30 -cnompi -relax && '
                     'rfmake.pl -t 50 -a -forcethr && '
                     'cd .. && '
                     'rqc-overlap.pl {1}"').format(rna_dir, folder)
        print cmd
        if not args.test:
            os.system(cmd)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-d', '--destination', default=os.getcwd(), help='Specify folder where the output will be created')
    parser.add_argument('-i', '--inputfile', default='example.tsv', help='Specify input file with E-Merge data')
    parser.add_argument('-t', '--test', action='store_true', help='Test mode: print commands and exit')
    parser.set_defaults(test=False)
    args = parser.parse_args()

    if not args.inputfile:
        print 'Please specify input file'
        sys.exit()

    run(args)
<commit_msg>Skip Emerge sequences that are too short or already in Rfam<commit_after>"""
Copyright [2009-2017] EMBL-European Bioinformatics Institute
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

"""
Usage:

1. Make sure that rfsearch, rfmake and other commands are in PATH.

2. python automate_rfsearch.py -i /path/to/emerge_file -d /path/to/output

Analysing result:

# check that all LSF jobs completed successfully
ls | wc -l
find . -type f -name lsf_output.txt | xargs grep 'Success' | wc -l

# count the number of lines above the best reversed hit
find . -type f -name outlist -exec sh -c 'sed -n "0,/REVERSED/p" {} | wc -l' \; -print

# get overlapping Rfam families
find . -type f -name overlap | xargs grep -o -P "RF\d{5}" | sort | uniq
"""

import argparse
import csv
import os
import re
import sys

def parse_input_file(filename):
    """
    Read input data and standardise sequences and names.
    Example input is provided in `example.tsv`.
    """
    skipped = {
        'length': 0,
        'in_rfam': 0,
    }
    MIN_LENGTH = 50
    with open(filename, 'r') as tsv:
        reader = csv.DictReader(tsv, delimiter='\t')
        for row in reader:
            sequence = row['Sequence (RNA or DNA)'].replace('-', '').upper()
            sequence = re.sub(r'\s', '', sequence)
            if len(sequence) < MIN_LENGTH:
                skipped['length'] += 1
                continue
            if row['In Rfam? http://rfam.xfam.org/search'].startswith('RF0'):
                skipped['in_rfam'] += 1
                continue
            yield {
                'sequence': sequence,
                'name': row['ncRNA name'].replace(' ', '_').replace("'", '').replace('/', '-'),
                'row_id': row['No.'],
            }
        print 'Skipped %i sequences shorter than %i nucleotides' % (skipped['length'], MIN_LENGTH)
        print 'Skipped %i sequences already in Rfam' % (skipped['in_rfam'])

def run(args):
    """
    * create FASTA file
    * predict secondary structure
    * make SEED
    * launch rfsearch
    """
    for rna in parse_input_file(args.inputfile):
        folder = '%s_%s' % (rna['row_id'], rna['name'])
        rna_dir = os.path.join(args.destination, folder)
        if not os.path.exists(rna_dir):
            os.mkdir(rna_dir)
        else:
            overlap = os.path.join(rna_dir, 'overlap')
            if os.path.exists(overlap):
                continue
        os.chdir(rna_dir)
        with open('input.fasta', 'w') as fasta:
            fasta.write('>%s\n%s\n' % (rna['name'], rna['sequence']))
        cmd = ('module load mpi/openmpi-x86_64 && '
               'bsub -o {0}/lsf_output.txt -e {0}/lsf_error.txt -g /emerge '
                     '"cd {0} && '
                     'predict_ss.pl -infile input.fasta -outfile SEED -r && '
                     'rfsearch.pl -nodesc -t 30 -cnompi -relax && '
                     'rfmake.pl -t 50 -a -forcethr && '
                     'cd .. && '
                     'rqc-overlap.pl {1}"').format(rna_dir, folder)
        print cmd
        if not args.test:
            os.system(cmd)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-d', '--destination', default=os.getcwd(), help='Specify folder where the output will be created')
    parser.add_argument('-i', '--inputfile', default='example.tsv', help='Specify input file with E-Merge data')
    parser.add_argument('-t', '--test', action='store_true', help='Test mode: print commands and exit')
    parser.set_defaults(test=False)
    args = parser.parse_args()

    if not args.inputfile:
        print 'Please specify input file'
        sys.exit()

    run(args)
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""`This`_ is a tool to manage using git as a deployment management tool

.. _This: https://gerrit.wikimedia.org/r/gitweb?p=sartoris.git
"""
__license__ = """\
Copyright (c) 2012-2013 Wikimedia Foundation <info@wikimedia.org>

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\
"""

import logging
import argparse
import os
import sys
from re import search
import subprocess
from dulwich.config import StackedConfig
from datetime import datetime
import json

exit_codes = {
    1: 'Operation failed.  Exiting.',
    2: 'A deployment has already been started.  Exiting.',
    3: 'Please enter valid arguments.',
    4: 'Missing lock file.',
    5: 'Could not reset.',
    6: 'Diff failed.',
    7: 'Missing tag(s).',
    8: 'Could not find last deploy.',
    9: 'show_tag failed.',
    20: 'Cannot find top level directory for the git repository. Exiting.',
    21: 'Missing system configuration item "hook-dir". Exiting.',
    22: 'Missing repo configuration item "tag-prefix". '
        'Please configure this using:'
        '\n\tgit config tag-prefix <repo>',
    30: 'No deploy started. Please run: git deploy start',
    31: 'Failed to write tag on sync. Exiting.',
    32: 'Failed to write the .deploy file. Exiting.',
    40: 'Failed to run sync script. Exiting.',
    50: 'Failed to read the .deploy file. Exiting.',
}

class SartorisError(Exception):
    """ Basic exception class for UserMetric types """
    def __init__(self, message="Git deploy error.", exit_code=1):
        Exception.__init__(self, message)
        self._exit_code = int(exit_code)

    @property
    def exit_code(self):
        return self._exit_code

# NullHandler was added in Python 3.1.
try:
    NullHandler = logging.NullHandler
except AttributeError:
    class NullHandler(logging.Handler):
        def emit(self, record):
            pass

# Add a do-nothing NullHandler to the module logger to prevent "No handlers
# could be found" errors. The calling code can still add other, more useful
# handlers, or otherwise configure logging.
log = logging.getLogger(__name__)
log.addHandler(NullHandler())

def parseargs(argv):
    """Parse command line arguments.

    Returns *args*, the list of arguments left over after processing.

    :param argv: a list of command line arguments, usually :data:`sys.argv`.
    """

    parser = argparse.ArgumentParser(
        description="This script performs ",
        epilog="",
        conflict_handler="resolve",
        usage="sartoris [-q --quiet] [-s --silent] [-v --verbose] [method]"
    )

    parser.allow_interspersed_args = False

    defaults = {
        "quiet": 0,
        "silent": False,
        "verbose": 0,
    }

    # Global options.
    parser.add_argument("method")
    parser.add_argument("-q", "--quiet",
                        default=defaults["quiet"], action="count",
                        help="decrease the logging verbosity")
    parser.add_argument("-s", "--silent",
                        default=defaults["silent"], action="store_true",
                        help="silence the logger")
    parser.add_argument("-v", "--verbose",
                        default=defaults["verbose"], action="count",
                        help="increase the logging verbosity")

    args = parser.parse_args()
    return args

class Sartoris(object):

    # Module level attribute for tagging datetime format
    DATE_TIME_TAG_FORMAT = '%Y%m%d-%H%M%S'

    # Name of deployment directory
    DEPLOY_DIR = '.git/deploy/'

    # Name of lock file
    LOCK_FILE_HANDLE = 'lock'

    __instance = None                           # class instance

    def __init__(self, *args, **kwargs):
        """ Initialize class instance """
        self.__class__.__instance = self
        self._configure()
        self._tag = None                    # Stores tag state

    def __new__(cls, *args, **kwargs):
        """ This class is Singleton, return only one instance """
        if not cls.__instance:
            cls.__instance = super(Sartoris, cls).__new__(cls, *args, **kwargs)
        return cls.__instance

    def _configure(self):
        """ Parse configuration from git config """
        sc = StackedConfig(StackedConfig.default_backends())
        self.config = {}

        # Get top level directory of project
        proc = subprocess.Popen(['git', 'rev-parse', '--show-toplevel'],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE)
        self.config['top_dir'] = proc.communicate()[0]

        if proc.returncode != 0:
            exit_code = 20
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            sys.exit(exit_code)

        self.config['deploy_file'] = self.config['top_dir'] + '/.git'

        try:
            self.config['hook_dir'] = sc.get('deploy', 'hook-dir')
        except KeyError:
            exit_code = 21
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            sys.exit(exit_code)

        try:
            self.config['repo_name'] = sc.get('deploy', 'tag-prefix')
        except KeyError:
            exit_code = 22
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            sys.exit(exit_code)
        self.config['sync_dir'] = '{0}/sync'.format(self.config['hook_dir'])

    def _check_lock(self):
        """ Returns boolean flag on lock file existence """
        return os.path.exists(self.DEPLOY_DIR + self.LOCK_FILE_HANDLE)

    def _create_lock(self):
        """ Create a lock file """
        with open(self.DEPLOY_DIR + self.LOCK_FILE_HANDLE, 'rb'):
            pass

    def _get_commit_sha_for_tag(self, tag):
        """ Obtain the commit sha of an associated tag
                e.g. `git rev-list $TAG | head -n 1` """
        # @TODO replace with dulwich

        cmd = "git rev-list {0} | head -n 1".format(tag)
        proc = subprocess.Popen(cmd.split(),
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        tag = proc.communicate()[0].strip()

        if not proc.returncode:
            return tag
        else:
            raise SartorisError(message=exit_codes[8], exit_code=8)

    def start(self, args):
        """
            * write a lock file
            * add a start tag
        """
        # @TODO use dulwich package implement git functionality rather
        #       than shell commands - http://www.samba.org/~jelmer/dulwich/

        # Create lock file - check if it already exists
        # @TODO catch exceptions for any os callable attributes
        if self._check_lock():
            raise SartorisError(message=exit_codes[2])

        log.debug(__name__ + '::Creating lock file.')
        self._create_lock()

        # Tag the repo at this point
        repo_name = self.config['repo_name']
        log.debug(__name__ + '::Adding `start` tag for repo.')

        timestamp = datetime.now().strftime(self.DATE_TIME_TAG_FORMAT)
        _tag = '{0}-start-{1}'.format(repo_name, timestamp)
        subprocess.call(['git', 'tag', '-a', _tag, '-m',
                         '"Tag for {0}"'.format(repo_name)])
        return 0

    def abort(self, args):
        """
            * reset state back to start tag
            * remove lock file
        """

        # Get the commit hash of the current tag
        commit_sha = self._get_commit_sha_for_tag(self._tag)

        # 1. hard reset the index to the desired tree
        # 2. move the branch pointer back to the previous HEAD
        # 3. commit revert
        # @TODO replace with dulwich
        if subprocess.call("git reset --hard {0}".format(commit_sha).split()):
            raise SartorisError(message=exit_codes[5], exit_code=5)
        if subprocess.call("git reset --soft HEAD@{1}".split()):
            raise SartorisError(message=exit_codes[5], exit_code=5)
        if subprocess.call("git commit -m 'Revert to {0}'".format(commit_sha).
                           split()):
            raise SartorisError(message=exit_codes[5], exit_code=5)

        # Remove lock file
        if os.listdir(self.DEPLOY_DIR).__contains__(self.LOCK_FILE_HANDLE):
            os.remove(self.LOCK_FILE_HANDLE)
        else:
            raise SartorisError(message=exit_codes[4])
        return 0

    def sync(self, args, no_deps=False, force=False):
        """
            * add a sync tag
            * write a .deploy file with the tag information
            * call a sync hook with the prefix (repo) and tag info
        """
        #TODO: do git calls in dulwich, rather than shelling out
        if 'lock' not in os.listdir('.git/deploy'):
            exit_code = 30
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            return exit_code
        repo_name = self.config['repo_name']
        _tag = "{0}-sync-{1}".format(repo_name,
                                     datetime.now().strftime(
                                         self.DATE_TIME_TAG_FORMAT))
        proc = subprocess.Popen(['/usr/bin/git tag', '-a', _tag])
        proc.communicate()

        if proc.returncode != 0:
            exit_code = 31
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            return exit_code
        # Write .deploy file
        try:
            deploy_file = open(self.config['deploy_file'], 'w')
            deploy_file.write(json.dumps({'repo': repo_name, 'tag': _tag}))
            deploy_file.close()
        except OSError:
            exit_code = 32
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            return exit_code
        return self._sync()

    def _sync(self, tag, force):
        repo_name = self.config['repo_name']
        sync_script = '{0}/{1}.sync'.format(self.config["sync_dir"], repo_name)
        #TODO: use a pluggable sync system rather than shelling out
        if os.path.exists(sync_script):
            proc = subprocess.Popen([sync_script,
                                     '--repo="{0}"'.format(repo_name),
                                     '--tag="{0}"'.format(_tag),
                                     '--force="{0}"'.format(force)])
            proc_out = proc.communicate()[0]
            log.info(proc_out)

            if proc.returncode != 0:
                exit_code = 40
                log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
                return exit_code
        os.unlink('.git/deploy')
        return 0

    def resync(self, args):
        """
            * write a lock file
            * call sync hook with the prefix (repo) and tag info
            * remove lock file
        """
        if self._check_lock():
            exit_code = 2
            log.error(__name__ + '::' + exit_codes[exit_code])
            return exit_code
        self._create_lock()
        repo_name = self.config['repo_name']
        try:
            deploy_file = open(self.config['deploy_file'], 'r')
            deploy_info = deploy_file.read()
            deploy_info = json.loads(deploy_info)
        except OSError:
            exit_code = 50
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            return exit_code
        return self._sync(repo_name, deploy_info["tag"], self.config(force))

    def revert(self, args):
        """
            * write a lock file
            * write previous deploy info into .deploy
            * call sync hook with the prefix (repo) and tag info
            * remove lock file
        """
        raise NotImplementedError()

    def show_tag(self, args):
        """
            * display current tagged release
        """

        # Get latest "sync" tag
        proc = subprocess.Popen("git tag".split())
        tag_last = ''
        for line_out in proc.communicate()[0].split('\n'):
            if search(r'sync', line_out):
                tag_last = line_out

        if proc.returncode:
            raise SartorisError(message=exit_codes[8], exit_code=8)
        elif not tag_last:
            raise SartorisError(message=exit_codes[9], exit_code=9)
        log.info(tag_last)
        return 0

    def log_deploys(self, args):
        """
            * show last x deploys
        """
        raise NotImplementedError()

    def diff(self, args):
        """
            * show a git diff of the last deploy and it's previous deploy
        """

        # Get the last two tags - assumes tagging on deployment only
        proc = subprocess.Popen("git tag".split(),
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,)

        # Get the last two tags
        sync_tags = filter(lambda x: search(r'sync',x),
            proc.communicate()[0].split('\n'))

        # Check the return code & whether at least two sync tags were
        # returned
        if proc.returncode:
            raise SartorisError(message=exit_codes[6], exit_code=6)
        elif len(sync_tags) < 2:
            raise SartorisError(message=exit_codes[7], exit_code=7)

        # Get the associated commit hashes for those tags
        sha_1 = self._get_commit_sha_for_tag(sync_tags[0])
        sha_2 = self._get_commit_sha_for_tag(sync_tags[1])

        # Produce the diff
        # @TODO replace with dulwich
        proc = subprocess.Popen("git diff {0} {1}".format(sha_2, sha_1).
                                split(),
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        proc.communicate()

        if not proc.returncode:
            line = proc.stdout.readline()
            while line:
                print line
                line = proc.stdout.readline()
        else:
            raise SartorisError(message=exit_codes[6], exit_code=6)
        return 0

def main(argv, out=None, err=None):
    """Main entry point.

    Returns a value that can be understood by :func:`sys.exit`.

    :param argv: a list of command line arguments, usually :data:`sys.argv`.
    :param out: stream to write messages; :data:`sys.stdout` if None.
    :param err: stream to write error messages; :data:`sys.stderr` if None.
    """
    if out is None:  # pragma: nocover
        out = sys.stdout
    if err is None:  # pragma: nocover
        err = sys.stderr
    args = parseargs(argv)
    level = logging.WARNING - ((args.verbose - args.quiet) * 10)
    if args.silent:
        level = logging.CRITICAL + 1

    log_format = "%(asctime)s %(levelname)-8s %(message)s"
    handler = logging.StreamHandler(err)
    handler.setFormatter(logging.Formatter(fmt=log_format,
                         datefmt='%b-%d %H:%M:%S'))
    log.addHandler(handler)
    log.setLevel(level)

    log.debug("Ready to run")

    # Inline call to functionality - if Sartoris does not possess this
    #  attribute flag with logger
    if not args.method:
        print args.help
        return 3

    if hasattr(Sartoris(), args.method) and callable(getattr(Sartoris(),
                                                     args.method)):
        try:
            getattr(Sartoris(), args.method)(args)
        except SartorisError as e:
            log.error(e.message)
            return e.exit_code
    else:
        log.error(__name__ + '::No function called %(method)s.' % {
            'method': args.method})

def cli():
    sys.exit(main(sys.argv))

if __name__ == "__main__":  # pragma: nocover
    cli()
<commit_msg>Fix. stream for stdout, stderr in Popen call in show_tag.<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""`This`_ is a tool to manage using git as a deployment management tool

.. _This: https://gerrit.wikimedia.org/r/gitweb?p=sartoris.git
"""
__license__ = """\
Copyright (c) 2012-2013 Wikimedia Foundation <info@wikimedia.org>

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\
"""

import logging
import argparse
import os
import sys
from re import search
import subprocess
from dulwich.config import StackedConfig
from datetime import datetime
import json

exit_codes = {
    1: 'Operation failed.  Exiting.',
    2: 'A deployment has already been started.  Exiting.',
    3: 'Please enter valid arguments.',
    4: 'Missing lock file.',
    5: 'Could not reset.',
    6: 'Diff failed.',
    7: 'Missing tag(s).',
    8: 'Could not find last deploy.',
    9: 'show_tag failed.',
    20: 'Cannot find top level directory for the git repository. Exiting.',
    21: 'Missing system configuration item "hook-dir". Exiting.',
    22: 'Missing repo configuration item "tag-prefix". '
        'Please configure this using:'
        '\n\tgit config tag-prefix <repo>',
    30: 'No deploy started. Please run: git deploy start',
    31: 'Failed to write tag on sync. Exiting.',
    32: 'Failed to write the .deploy file. Exiting.',
    40: 'Failed to run sync script. Exiting.',
    50: 'Failed to read the .deploy file. Exiting.',
}

class SartorisError(Exception):
    """ Basic exception class for UserMetric types """
    def __init__(self, message="Git deploy error.", exit_code=1):
        Exception.__init__(self, message)
        self._exit_code = int(exit_code)

    @property
    def exit_code(self):
        return self._exit_code

# NullHandler was added in Python 3.1.
try:
    NullHandler = logging.NullHandler
except AttributeError:
    class NullHandler(logging.Handler):
        def emit(self, record):
            pass

# Add a do-nothing NullHandler to the module logger to prevent "No handlers
# could be found" errors. The calling code can still add other, more useful
# handlers, or otherwise configure logging.
log = logging.getLogger(__name__)
log.addHandler(NullHandler())

def parseargs(argv):
    """Parse command line arguments.

    Returns *args*, the list of arguments left over after processing.

    :param argv: a list of command line arguments, usually :data:`sys.argv`.
    """

    parser = argparse.ArgumentParser(
        description="This script performs ",
        epilog="",
        conflict_handler="resolve",
        usage="sartoris [-q --quiet] [-s --silent] [-v --verbose] [method]"
    )

    parser.allow_interspersed_args = False

    defaults = {
        "quiet": 0,
        "silent": False,
        "verbose": 0,
    }

    # Global options.
    parser.add_argument("method")
    parser.add_argument("-q", "--quiet",
                        default=defaults["quiet"], action="count",
                        help="decrease the logging verbosity")
    parser.add_argument("-s", "--silent",
                        default=defaults["silent"], action="store_true",
                        help="silence the logger")
    parser.add_argument("-v", "--verbose",
                        default=defaults["verbose"], action="count",
                        help="increase the logging verbosity")

    args = parser.parse_args()
    return args

class Sartoris(object):

    # Module level attribute for tagging datetime format
    DATE_TIME_TAG_FORMAT = '%Y%m%d-%H%M%S'

    # Name of deployment directory
    DEPLOY_DIR = '.git/deploy/'

    # Name of lock file
    LOCK_FILE_HANDLE = 'lock'

    __instance = None                           # class instance

    def __init__(self, *args, **kwargs):
        """ Initialize class instance """
        self.__class__.__instance = self
        self._configure()
        self._tag = None                    # Stores tag state

    def __new__(cls, *args, **kwargs):
        """ This class is Singleton, return only one instance """
        if not cls.__instance:
            cls.__instance = super(Sartoris, cls).__new__(cls, *args, **kwargs)
        return cls.__instance

    def _configure(self):
        """ Parse configuration from git config """
        sc = StackedConfig(StackedConfig.default_backends())
        self.config = {}

        # Get top level directory of project
        proc = subprocess.Popen(['git', 'rev-parse', '--show-toplevel'],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE)
        self.config['top_dir'] = proc.communicate()[0]

        if proc.returncode != 0:
            exit_code = 20
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            sys.exit(exit_code)

        self.config['deploy_file'] = self.config['top_dir'] + '/.git'

        try:
            self.config['hook_dir'] = sc.get('deploy', 'hook-dir')
        except KeyError:
            exit_code = 21
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            sys.exit(exit_code)

        try:
            self.config['repo_name'] = sc.get('deploy', 'tag-prefix')
        except KeyError:
            exit_code = 22
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            sys.exit(exit_code)
        self.config['sync_dir'] = '{0}/sync'.format(self.config['hook_dir'])

    def _check_lock(self):
        """ Returns boolean flag on lock file existence """
        return os.path.exists(self.DEPLOY_DIR + self.LOCK_FILE_HANDLE)

    def _create_lock(self):
        """ Create a lock file """
        with open(self.DEPLOY_DIR + self.LOCK_FILE_HANDLE, 'rb'):
            pass

    def _get_commit_sha_for_tag(self, tag):
        """ Obtain the commit sha of an associated tag
                e.g. `git rev-list $TAG | head -n 1` """
        # @TODO replace with dulwich

        cmd = "git rev-list {0} | head -n 1".format(tag)
        proc = subprocess.Popen(cmd.split(),
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        tag = proc.communicate()[0].strip()

        if not proc.returncode:
            return tag
        else:
            raise SartorisError(message=exit_codes[8], exit_code=8)

    def start(self, args):
        """
            * write a lock file
            * add a start tag
        """
        # @TODO use dulwich package implement git functionality rather
        #       than shell commands - http://www.samba.org/~jelmer/dulwich/

        # Create lock file - check if it already exists
        # @TODO catch exceptions for any os callable attributes
        if self._check_lock():
            raise SartorisError(message=exit_codes[2])

        log.debug(__name__ + '::Creating lock file.')
        self._create_lock()

        # Tag the repo at this point
        repo_name = self.config['repo_name']
        log.debug(__name__ + '::Adding `start` tag for repo.')

        timestamp = datetime.now().strftime(self.DATE_TIME_TAG_FORMAT)
        _tag = '{0}-start-{1}'.format(repo_name, timestamp)
        subprocess.call(['git', 'tag', '-a', _tag, '-m',
                         '"Tag for {0}"'.format(repo_name)])
        return 0

    def abort(self, args):
        """
            * reset state back to start tag
            * remove lock file
        """

        # Get the commit hash of the current tag
        commit_sha = self._get_commit_sha_for_tag(self._tag)

        # 1. hard reset the index to the desired tree
        # 2. move the branch pointer back to the previous HEAD
        # 3. commit revert
        # @TODO replace with dulwich
        if subprocess.call("git reset --hard {0}".format(commit_sha).split()):
            raise SartorisError(message=exit_codes[5], exit_code=5)
        if subprocess.call("git reset --soft HEAD@{1}".split()):
            raise SartorisError(message=exit_codes[5], exit_code=5)
        if subprocess.call("git commit -m 'Revert to {0}'".format(commit_sha).
                           split()):
            raise SartorisError(message=exit_codes[5], exit_code=5)

        # Remove lock file
        if os.listdir(self.DEPLOY_DIR).__contains__(self.LOCK_FILE_HANDLE):
            os.remove(self.LOCK_FILE_HANDLE)
        else:
            raise SartorisError(message=exit_codes[4])
        return 0

    def sync(self, args, no_deps=False, force=False):
        """
            * add a sync tag
            * write a .deploy file with the tag information
            * call a sync hook with the prefix (repo) and tag info
        """
        #TODO: do git calls in dulwich, rather than shelling out
        if 'lock' not in os.listdir('.git/deploy'):
            exit_code = 30
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            return exit_code
        repo_name = self.config['repo_name']
        _tag = "{0}-sync-{1}".format(repo_name,
                                     datetime.now().strftime(
                                         self.DATE_TIME_TAG_FORMAT))
        proc = subprocess.Popen(['/usr/bin/git tag', '-a', _tag])
        proc.communicate()

        if proc.returncode != 0:
            exit_code = 31
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            return exit_code
        # Write .deploy file
        try:
            deploy_file = open(self.config['deploy_file'], 'w')
            deploy_file.write(json.dumps({'repo': repo_name, 'tag': _tag}))
            deploy_file.close()
        except OSError:
            exit_code = 32
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            return exit_code
        return self._sync()

    def _sync(self, tag, force):
        repo_name = self.config['repo_name']
        sync_script = '{0}/{1}.sync'.format(self.config["sync_dir"], repo_name)
        #TODO: use a pluggable sync system rather than shelling out
        if os.path.exists(sync_script):
            proc = subprocess.Popen([sync_script,
                                     '--repo="{0}"'.format(repo_name),
                                     '--tag="{0}"'.format(_tag),
                                     '--force="{0}"'.format(force)])
            proc_out = proc.communicate()[0]
            log.info(proc_out)

            if proc.returncode != 0:
                exit_code = 40
                log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
                return exit_code
        os.unlink('.git/deploy')
        return 0

    def resync(self, args):
        """
            * write a lock file
            * call sync hook with the prefix (repo) and tag info
            * remove lock file
        """
        if self._check_lock():
            exit_code = 2
            log.error(__name__ + '::' + exit_codes[exit_code])
            return exit_code
        self._create_lock()
        repo_name = self.config['repo_name']
        try:
            deploy_file = open(self.config['deploy_file'], 'r')
            deploy_info = deploy_file.read()
            deploy_info = json.loads(deploy_info)
        except OSError:
            exit_code = 50
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            return exit_code
        return self._sync(repo_name, deploy_info["tag"], self.config(force))

    def revert(self, args):
        """
            * write a lock file
            * write previous deploy info into .deploy
            * call sync hook with the prefix (repo) and tag info
            * remove lock file
        """
        raise NotImplementedError()

    def show_tag(self, args):
        """
            * display current tagged release
        """

        # Get latest "sync" tag
        proc = subprocess.Popen("git tag".split(),
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE)
        tag_last = ''
        for line_out in proc.communicate()[0].split('\n'):
            if search(r'sync', line_out):
                tag_last = line_out

        if proc.returncode:
            raise SartorisError(message=exit_codes[8], exit_code=8)
        elif not tag_last:
            raise SartorisError(message=exit_codes[9], exit_code=9)
        log.info(tag_last)
        return 0

    def log_deploys(self, args):
        """
            * show last x deploys
        """
        raise NotImplementedError()

    def diff(self, args):
        """
            * show a git diff of the last deploy and it's previous deploy
        """

        # Get the last two tags - assumes tagging on deployment only
        proc = subprocess.Popen("git tag".split(),
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,)

        # Get the last two tags
        sync_tags = filter(lambda x: search(r'sync',x),
            proc.communicate()[0].split('\n'))

        # Check the return code & whether at least two sync tags were
        # returned
        if proc.returncode:
            raise SartorisError(message=exit_codes[6], exit_code=6)
        elif len(sync_tags) < 2:
            raise SartorisError(message=exit_codes[7], exit_code=7)

        # Get the associated commit hashes for those tags
        sha_1 = self._get_commit_sha_for_tag(sync_tags[0])
        sha_2 = self._get_commit_sha_for_tag(sync_tags[1])

        # Produce the diff
        # @TODO replace with dulwich
        proc = subprocess.Popen("git diff {0} {1}".format(sha_2, sha_1).
                                split(),
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        proc.communicate()

        if not proc.returncode:
            line = proc.stdout.readline()
            while line:
                print line
                line = proc.stdout.readline()
        else:
            raise SartorisError(message=exit_codes[6], exit_code=6)
        return 0

def main(argv, out=None, err=None):
    """Main entry point.

    Returns a value that can be understood by :func:`sys.exit`.

    :param argv: a list of command line arguments, usually :data:`sys.argv`.
    :param out: stream to write messages; :data:`sys.stdout` if None.
    :param err: stream to write error messages; :data:`sys.stderr` if None.
    """
    if out is None:  # pragma: nocover
        out = sys.stdout
    if err is None:  # pragma: nocover
        err = sys.stderr
    args = parseargs(argv)
    level = logging.WARNING - ((args.verbose - args.quiet) * 10)
    if args.silent:
        level = logging.CRITICAL + 1

    log_format = "%(asctime)s %(levelname)-8s %(message)s"
    handler = logging.StreamHandler(err)
    handler.setFormatter(logging.Formatter(fmt=log_format,
                         datefmt='%b-%d %H:%M:%S'))
    log.addHandler(handler)
    log.setLevel(level)

    log.debug("Ready to run")

    # Inline call to functionality - if Sartoris does not possess this
    #  attribute flag with logger
    if not args.method:
        print args.help
        return 3

    if hasattr(Sartoris(), args.method) and callable(getattr(Sartoris(),
                                                     args.method)):
        try:
            getattr(Sartoris(), args.method)(args)
        except SartorisError as e:
            log.error(e.message)
            return e.exit_code
    else:
        log.error(__name__ + '::No function called %(method)s.' % {
            'method': args.method})

def cli():
    sys.exit(main(sys.argv))

if __name__ == "__main__":  # pragma: nocover
    cli()
<|endoftext|>


<commit_before>#!/usr/bin/env python
import logging
logger = logging.getLogger()

import argparse
import sys
import os
import string
import re

if __name__ == "__main__":

    parser = argparse.ArgumentParser(description="Filters uncertainty symbols ?,* and + from MLST call files.")
    parser.add_argument("-o", "--output", type=str, help="Output folder with filtered files.")
    parser.add_argument("files", nargs="+", help="MLST call files to filter.")
    parser.add_argument('-ll', '--loglevel', type=str, default="INFO", choices=['DEBUG','INFO','WARNING','ERROR','CRITICAL'], help='Set the logging level')
    param = parser.parse_args()
    logging.basicConfig(level=param.loglevel, format='%(asctime)s (%(relativeCreated)d ms) -> %(levelname)s:%(message)s', datefmt='%I:%M:%S %p')

    # make out folder if needed:
    if not os.path.isdir(param.output):
        os.makedirs(param.output)
    # remove all but digits:
    all_chars = string.maketrans('','')
    nodigs = all_chars.translate(all_chars, string.digits)
    for file in param.files:
        with open(file) as f_in, open(os.path.join(param.output,os.path.basename(file)), "wb") as f_out:
            header = f_in.readline()
            f_out.write(header)
            genotype = f_in.readline().strip().split()
            genotype = "\t".join([genotype[0]] + [x.translate(all_chars, nodigs) for x in genotype[1:]])
            # genotype = "\t".join([genotype[0]] + [x for x in genotype[1:]])
            genotype = re.sub(pattern='\t\t', repl='\t-1\t', string=genotype)
            f_out.write("%s\n" % genotype)
<commit_msg>Fixed filter to account for consecutive N's<commit_after>#!/usr/bin/env python
import logging
logger = logging.getLogger()

import argparse
import sys
import os
import string
import re

if __name__ == "__main__":

    parser = argparse.ArgumentParser(description="Filters uncertainty symbols ?,* and + from MLST call files.")
    parser.add_argument("-o", "--output", type=str, help="Output folder with filtered files.")
    parser.add_argument("files", nargs="+", help="MLST call files to filter.")
    parser.add_argument('-ll', '--loglevel', type=str, default="INFO", choices=['DEBUG','INFO','WARNING','ERROR','CRITICAL'], help='Set the logging level')
    param = parser.parse_args()
    logging.basicConfig(level=param.loglevel, format='%(asctime)s (%(relativeCreated)d ms) -> %(levelname)s:%(message)s', datefmt='%I:%M:%S %p')

    # make out folder if needed:
    if not os.path.isdir(param.output):
        os.makedirs(param.output)
    # remove all but digits:
    all_chars = string.maketrans('','')
    nodigs = all_chars.translate(all_chars, string.digits)
    for file in param.files:
        with open(file) as f_in, open(os.path.join(param.output,os.path.basename(file)), "wb") as f_out:
            header = f_in.readline()
            f_out.write(header)
            genotype = f_in.readline().strip().split()
            genotype = "\t".join([genotype[0]] + [x.translate(all_chars, nodigs) for x in genotype[1:]])
            # genotype = "\t".join([genotype[0]] + [x for x in genotype[1:]])
            while re.search(pattern='\t\t', string = genotype):
                genotype = re.sub(pattern='\t\t', repl='\t-1\t', string=genotype)
            f_out.write("%s\n" % genotype)
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""`This`_ is a tool to manage using git as a deployment management tool

.. _This: https://gerrit.wikimedia.org/r/gitweb?p=sartoris.git
"""
__license__ = """\
Copyright (c) 2012-2013 Wikimedia Foundation <info@wikimedia.org>

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\
"""

import logging
import argparse
import os
import sys
import subprocess
from dulwich.config import StackedConfig
from datetime import datetime

exit_codes = {
    1: 'Operation failed.  Exiting.',
    2: 'A deployment has already been started.  Exiting.',
    3: 'Please enter valid arguments.',
    4: 'Missing lock file.',
    5: 'Could not reset.',
    20: 'Cannot find top level directory for the git repository. Exiting.',
    21: 'Missing system configuration item "hook-dir". Exiting.',
    22: 'Missing repo configuration item "tag-prefix". '
        'Please configure this using:'
        '\n\tgit config tag-prefix <repo>',
    30: 'No deploy started. Please run: git deploy start',
    31: 'Failed to write tag on sync. Exiting.',
    32: 'Failed to write the .deploy file. Exiting.',
    40: 'Failed to run sync script. Exiting.',
    50: 'Failed to read the .deploy file. Exiting.',
}

class SartorisError(Exception):
    """ Basic exception class for UserMetric types """
    def __init__(self, message="Git deploy error.",exit_code=1):
        Exception.__init__(self, message)
        self._exit_code = int(exit_code)

    @property
    def exit_code(self): return self._exit_code

# NullHandler was added in Python 3.1.
try:
    NullHandler = logging.NullHandler
except AttributeError:
    class NullHandler(logging.Handler):
        def emit(self, record):
            pass

# Add a do-nothing NullHandler to the module logger to prevent "No handlers
# could be found" errors. The calling code can still add other, more useful
# handlers, or otherwise configure logging.
log = logging.getLogger(__name__)
log.addHandler(NullHandler())

def parseargs(argv):
    """Parse command line arguments.

    Returns *args*, the list of arguments left over after processing.

    :param argv: a list of command line arguments, usually :data:`sys.argv`.
    """

    parser = argparse.ArgumentParser(
        description="This script performs ",
        epilog="",
        conflict_handler="resolve",
        usage="sartoris [-q --quiet] [-s --silent] [-v --verbose] [method]"
    )

    parser.allow_interspersed_args = False

    defaults = {
        "quiet": 0,
        "silent": False,
        "verbose": 0,
    }

    # Global options.
    parser.add_argument("method")
    parser.add_argument("-q", "--quiet",
                        default=defaults["quiet"], action="count",
                        help="decrease the logging verbosity")
    parser.add_argument("-s", "--silent",
                        default=defaults["silent"], action="store_true",
                        help="silence the logger")
    parser.add_argument("-v", "--verbose",
                        default=defaults["verbose"], action="count",
                        help="increase the logging verbosity")

    args = parser.parse_args()
    return args

class Sartoris(object):

    # Module level attribute for tagging datetime format
    DATE_TIME_TAG_FORMAT = '%Y%m%d-%H%M%S'

    # Name of deployment directory
    DEPLOY_DIR = '.git/deploy/'

    # Name of lock file
    LOCK_FILE_HANDLE = 'lock'

    __instance = None                           # class instance

    def __init__(self, *args, **kwargs):
        """ Initialize class instance """
        self.__class__.__instance = self
        self._configure()
        self._tag = None                    # Stores tag state

    def __new__(cls, *args, **kwargs):
        """ This class is Singleton, return only one instance """
        if not cls.__instance:
            cls.__instance = super(Sartoris, cls).__new__(cls, *args, **kwargs)
        return cls.__instance

    def _configure(self):
        """ Parse configuration from git config """
        sc = StackedConfig(StackedConfig.default_backends())
        self.config = {}
        proc = subprocess.Popen(['git', 'rev-parse', '--show-toplevel'])
        if proc.returncode != 0:
            exit_code = 20
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            sys.exit(exit_code)
        self.config['top_dir'] = proc.stdout.read()
        self.config['deploy_file'] = self.config['top_dir'] + '/.git'
        try:
            self.config['hook_dir'] = sc.get('deploy', 'hook-dir')
        except KeyError:
            exit_code = 21
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            sys.exit(exit_code)
        try:
            self.config['repo_name'] = sc.get('deploy', 'tag-prefix')
        except KeyError:
            exit_code = 22
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            sys.exit(exit_code)
        self.config['sync_dir'] = '{0}/sync'.format(hook_dir)

    def _check_lock(self):
        """ Returns boolean flag on lock file existence """
        return os.path.exists(self.DEPLOY_DIR + self.LOCK_FILE_HANDLE)

    def _create_lock(self):
        """ Create a lock file """
        with open(self.DEPLOY_DIR + self.LOCK_FILE_HANDLE,'rb'): pass

    def start(self, args):
        """
            * write a lock file
            * add a start tag
        """
        # @TODO use dulwich package implement git functionality rather
        #       than shell commands - http://www.samba.org/~jelmer/dulwich/

        # Create lock file - check if it already exists
        # @TODO catch exceptions for any os callable attributes
        if self._check_lock():
            raise SartorisError(message=exit_codes[2])

        log.debug(__name__ + '::Creating lock file.')
        self._create_lock()

        # Tag the repo at this point
        repo_name = self.config['repo_name']
        log.debug(__name__ + '::Adding `start` tag for repo.')

        timestamp = datetime.now().strftime(self.DATE_TIME_TAG_FORMAT)
        _tag = '{0}-start-{1}'.format(repo_name, timestamp)
        subprocess.call(['git', 'tag', '-a', _tag, '-m',
                         '"Tag for {0}"'.format(repo_name)])
        return 0

    def abort(self, args):
        """
            * reset state back to start tag
            * remove lock file
        """

        # Reset state - e.g. `git rev-list $TAG | head -n 1`
        # @TODO replace with dulwich
        cmd = "git rev-list {0} | head -n 1".format(self._tag)
        proc = subprocess.Popen(cmd.split(),
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE)
        if not proc.returncode:
            commit_sha = proc.stdout.readline().strip()
        else:
            raise SartorisError(message=exit_codes[5], exit_code=5)

        # 1. hard reset the index to the desired tree
        # 2. move the branch pointer back to the previous HEAD
        # 3. commit revert
        # @TODO replace with dulwich
        if subprocess.call("git reset --hard {0}".format(commit_sha).split()):
            raise SartorisError(message=exit_codes[5], exit_code=5)
        if subprocess.call("git reset --soft HEAD@{1}".split()):
            raise SartorisError(message=exit_codes[5], exit_code=5)
        if subprocess.call("git commit -m 'Revert to {0}'".format(commit_sha).
            split()):
            raise SartorisError(message=exit_codes[5], exit_code=5)

        # Remove lock file
        if os.listdir(self.DEPLOY_DIR).__contains__(self.LOCK_FILE_HANDLE):
            os.remove(self.LOCK_FILE_HANDLE)
        else:
            raise SartorisError(message=exit_codes[4])
        return 0

    def sync(self, args, no_deps=False, force=False):
        """
            * add a sync tag
            * write a .deploy file with the tag information
            * call a sync hook with the prefix (repo) and tag info
        """
        #TODO: do git calls in dulwich, rather than shelling out
        if 'lock' not in os.listdir('.git/deploy'):
            exit_code = 30
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            return exit_code
        repo_name = self.config['repo_name']
        _tag = "{0}-sync-{1}".format(repo_name,
                                     datetime.now().strftime(
                                         DATE_TIME_TAG_FORMAT))
        proc = subprocess.Popen(['/usr/bin/git tag', '-a', _tag])
        if proc.returncode != 0:
            exit_code = 31
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            return exit_code
        # Write .deploy file
        try:
            deploy_file = open(self.config['deploy_file'], 'w')
            deploy_file.write(json.dumps({'repo': repo_name, 'tag': _tag}))
            deploy_file.close()
        except OSError:
            exit_code = 32
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            return exit_code
        return self._sync()

    def _sync(self, tag, force):
        repo_name = self.config['repo_name']
        sync_script = '{0}/{1}.sync'.format(self.config["sync_dir"], repo_name)
        #TODO: use a pluggable sync system rather than shelling out
        if os.path.exists(sync_script):
            proc = subprocess.Popen([sync_script,
                                     '--repo="{0}"'.format(repo_name),
                                     '--tag="{0}"'.format(_tag),
                                     '--force="{0}"'.format(force)])
            log.info(proc.stdout.read())
            if proc.returncode != 0:
                exit_code = 40
                log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
                return exit_code
        os.unlink('.git/deploy')
        return 0

    def resync(self, args):
        """
            * write a lock file
            * call sync hook with the prefix (repo) and tag info
            * remove lock file
        """
        if self._check_lock():
            exit_code = 2
            log.error(__name__ + '::' + exit_codes[exit_code])
            return exit_code
        self._create_lock()
        repo_name = self.config['repo_name']
        try:
            deploy_file = open(self.config['deploy_file'], 'r')
            deploy_info = deploy_file.read()
            deploy_info = json.loads(deploy_info)
        except OSError:
            exit_code = 50
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            return exit_code
        return self._sync(repo_name, deploy_info["tag"], self.config(force))

    def revert(self, args):
        """
            * write a lock file
            * write previous deploy info into .deploy
            * call sync hook with the prefix (repo) and tag info
            * remove lock file
        """
        raise NotImplementedError()

    def show_tag(self, args):
        """
            * display current tagged release
        """
        raise NotImplementedError()

    def log_deploys(self, args):
        """
            * show last x deploys
        """
        raise NotImplementedError()

    def diff(self, args):
        """
            * show a git diff of the last deploy and it's previous deploy
        """
        raise NotImplementedError

def main(argv, out=None, err=None):
    """Main entry point.

    Returns a value that can be understood by :func:`sys.exit`.

    :param argv: a list of command line arguments, usually :data:`sys.argv`.
    :param out: stream to write messages; :data:`sys.stdout` if None.
    :param err: stream to write error messages; :data:`sys.stderr` if None.
    """
    if out is None:  # pragma: nocover
        out = sys.stdout
    if err is None:  # pragma: nocover
        err = sys.stderr
    args = parseargs(argv)
    level = logging.WARNING - ((args.verbose - args.quiet) * 10)
    if args.silent:
        level = logging.CRITICAL + 1

    format = "%(asctime)s %(levelname)-8s %(message)s"
    handler = logging.StreamHandler(err)
    handler.setFormatter(logging.Formatter(fmt=format,
                         datefmt='%b-%d %H:%M:%S'))
    log.addHandler(handler)
    log.setLevel(level)

    log.debug("Ready to run")

    # Inline call to functionality - if Sartoris does not possess this
    #  attribute flag with logger
    if not args.method:
        print args.help
        return 3

    if hasattr(Sartoris(), args.method) and callable(getattr(Sartoris(),
                                                     args.method)):
        try:
            getattr(Sartoris(), args.method)(args)
        except SartorisError as e:
            log.error(e.message)
            return e.exit_code
    else:
        log.error(__name__ + '::No function called %(method)s.' % {
            'method': args.method})

if __name__ == "__main__":  # pragma: nocover
    sys.exit(main(sys.argv))
<commit_msg>Add. Diff functionality. helper method _get_commit_sha_for_tag().<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""`This`_ is a tool to manage using git as a deployment management tool

.. _This: https://gerrit.wikimedia.org/r/gitweb?p=sartoris.git
"""
__license__ = """\
Copyright (c) 2012-2013 Wikimedia Foundation <info@wikimedia.org>

Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\
"""

import logging
import argparse
import os
import sys
import subprocess
from dulwich.config import StackedConfig
from datetime import datetime

exit_codes = {
    1: 'Operation failed.  Exiting.',
    2: 'A deployment has already been started.  Exiting.',
    3: 'Please enter valid arguments.',
    4: 'Missing lock file.',
    5: 'Could not reset.',
    6: 'Diff failed.',
    7: 'Missing tag(s).',
    20: 'Cannot find top level directory for the git repository. Exiting.',
    21: 'Missing system configuration item "hook-dir". Exiting.',
    22: 'Missing repo configuration item "tag-prefix". '
        'Please configure this using:'
        '\n\tgit config tag-prefix <repo>',
    30: 'No deploy started. Please run: git deploy start',
    31: 'Failed to write tag on sync. Exiting.',
    32: 'Failed to write the .deploy file. Exiting.',
    40: 'Failed to run sync script. Exiting.',
    50: 'Failed to read the .deploy file. Exiting.',
}

class SartorisError(Exception):
    """ Basic exception class for UserMetric types """
    def __init__(self, message="Git deploy error.",exit_code=1):
        Exception.__init__(self, message)
        self._exit_code = int(exit_code)

    @property
    def exit_code(self): return self._exit_code

# NullHandler was added in Python 3.1.
try:
    NullHandler = logging.NullHandler
except AttributeError:
    class NullHandler(logging.Handler):
        def emit(self, record):
            pass

# Add a do-nothing NullHandler to the module logger to prevent "No handlers
# could be found" errors. The calling code can still add other, more useful
# handlers, or otherwise configure logging.
log = logging.getLogger(__name__)
log.addHandler(NullHandler())

def parseargs(argv):
    """Parse command line arguments.

    Returns *args*, the list of arguments left over after processing.

    :param argv: a list of command line arguments, usually :data:`sys.argv`.
    """

    parser = argparse.ArgumentParser(
        description="This script performs ",
        epilog="",
        conflict_handler="resolve",
        usage="sartoris [-q --quiet] [-s --silent] [-v --verbose] [method]"
    )

    parser.allow_interspersed_args = False

    defaults = {
        "quiet": 0,
        "silent": False,
        "verbose": 0,
    }

    # Global options.
    parser.add_argument("method")
    parser.add_argument("-q", "--quiet",
                        default=defaults["quiet"], action="count",
                        help="decrease the logging verbosity")
    parser.add_argument("-s", "--silent",
                        default=defaults["silent"], action="store_true",
                        help="silence the logger")
    parser.add_argument("-v", "--verbose",
                        default=defaults["verbose"], action="count",
                        help="increase the logging verbosity")

    args = parser.parse_args()
    return args

class Sartoris(object):

    # Module level attribute for tagging datetime format
    DATE_TIME_TAG_FORMAT = '%Y%m%d-%H%M%S'

    # Name of deployment directory
    DEPLOY_DIR = '.git/deploy/'

    # Name of lock file
    LOCK_FILE_HANDLE = 'lock'

    __instance = None                           # class instance

    def __init__(self, *args, **kwargs):
        """ Initialize class instance """
        self.__class__.__instance = self
        self._configure()
        self._tag = None                    # Stores tag state

    def __new__(cls, *args, **kwargs):
        """ This class is Singleton, return only one instance """
        if not cls.__instance:
            cls.__instance = super(Sartoris, cls).__new__(cls, *args, **kwargs)
        return cls.__instance

    def _configure(self):
        """ Parse configuration from git config """
        sc = StackedConfig(StackedConfig.default_backends())
        self.config = {}
        proc = subprocess.Popen(['git', 'rev-parse', '--show-toplevel'])
        if proc.returncode != 0:
            exit_code = 20
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            sys.exit(exit_code)
        self.config['top_dir'] = proc.stdout.read()
        self.config['deploy_file'] = self.config['top_dir'] + '/.git'
        try:
            self.config['hook_dir'] = sc.get('deploy', 'hook-dir')
        except KeyError:
            exit_code = 21
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            sys.exit(exit_code)
        try:
            self.config['repo_name'] = sc.get('deploy', 'tag-prefix')
        except KeyError:
            exit_code = 22
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            sys.exit(exit_code)
        self.config['sync_dir'] = '{0}/sync'.format(hook_dir)

    def _check_lock(self):
        """ Returns boolean flag on lock file existence """
        return os.path.exists(self.DEPLOY_DIR + self.LOCK_FILE_HANDLE)

    def _create_lock(self):
        """ Create a lock file """
        with open(self.DEPLOY_DIR + self.LOCK_FILE_HANDLE,'rb'): pass

    def _get_commit_sha_for_tag(self, tag):
        """ Obtain the commit sha of an associated tag
                e.g. `git rev-list $TAG | head -n 1` """
        # @TODO replace with dulwich

        cmd = "git rev-list {0} | head -n 1".format(tag)
        proc = subprocess.Popen(cmd.split(),
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE)
        if not proc.returncode:
            return proc.stdout.readline().strip()
        else:
            raise SartorisError(message=exit_codes[8], exit_code=8)

    def start(self, args):
        """
            * write a lock file
            * add a start tag
        """
        # @TODO use dulwich package implement git functionality rather
        #       than shell commands - http://www.samba.org/~jelmer/dulwich/

        # Create lock file - check if it already exists
        # @TODO catch exceptions for any os callable attributes
        if self._check_lock():
            raise SartorisError(message=exit_codes[2])

        log.debug(__name__ + '::Creating lock file.')
        self._create_lock()

        # Tag the repo at this point
        repo_name = self.config['repo_name']
        log.debug(__name__ + '::Adding `start` tag for repo.')

        timestamp = datetime.now().strftime(self.DATE_TIME_TAG_FORMAT)
        _tag = '{0}-start-{1}'.format(repo_name, timestamp)
        subprocess.call(['git', 'tag', '-a', _tag, '-m',
                         '"Tag for {0}"'.format(repo_name)])
        return 0

    def abort(self, args):
        """
            * reset state back to start tag
            * remove lock file
        """

        # Get the commit hash of the current tag
        commit_sha = self._get_commit_sha_for_tag(self._tag)

        # 1. hard reset the index to the desired tree
        # 2. move the branch pointer back to the previous HEAD
        # 3. commit revert
        # @TODO replace with dulwich
        if subprocess.call("git reset --hard {0}".format(commit_sha).split()):
            raise SartorisError(message=exit_codes[5], exit_code=5)
        if subprocess.call("git reset --soft HEAD@{1}".split()):
            raise SartorisError(message=exit_codes[5], exit_code=5)
        if subprocess.call("git commit -m 'Revert to {0}'".format(commit_sha).
            split()):
            raise SartorisError(message=exit_codes[5], exit_code=5)

        # Remove lock file
        if os.listdir(self.DEPLOY_DIR).__contains__(self.LOCK_FILE_HANDLE):
            os.remove(self.LOCK_FILE_HANDLE)
        else:
            raise SartorisError(message=exit_codes[4])
        return 0

    def sync(self, args, no_deps=False, force=False):
        """
            * add a sync tag
            * write a .deploy file with the tag information
            * call a sync hook with the prefix (repo) and tag info
        """
        #TODO: do git calls in dulwich, rather than shelling out
        if 'lock' not in os.listdir('.git/deploy'):
            exit_code = 30
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            return exit_code
        repo_name = self.config['repo_name']
        _tag = "{0}-sync-{1}".format(repo_name,
                                     datetime.now().strftime(
                                         DATE_TIME_TAG_FORMAT))
        proc = subprocess.Popen(['/usr/bin/git tag', '-a', _tag])
        if proc.returncode != 0:
            exit_code = 31
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            return exit_code
        # Write .deploy file
        try:
            deploy_file = open(self.config['deploy_file'], 'w')
            deploy_file.write(json.dumps({'repo': repo_name, 'tag': _tag}))
            deploy_file.close()
        except OSError:
            exit_code = 32
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            return exit_code
        return self._sync()

    def _sync(self, tag, force):
        repo_name = self.config['repo_name']
        sync_script = '{0}/{1}.sync'.format(self.config["sync_dir"], repo_name)
        #TODO: use a pluggable sync system rather than shelling out
        if os.path.exists(sync_script):
            proc = subprocess.Popen([sync_script,
                                     '--repo="{0}"'.format(repo_name),
                                     '--tag="{0}"'.format(_tag),
                                     '--force="{0}"'.format(force)])
            log.info(proc.stdout.read())
            if proc.returncode != 0:
                exit_code = 40
                log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
                return exit_code
        os.unlink('.git/deploy')
        return 0

    def resync(self, args):
        """
            * write a lock file
            * call sync hook with the prefix (repo) and tag info
            * remove lock file
        """
        if self._check_lock():
            exit_code = 2
            log.error(__name__ + '::' + exit_codes[exit_code])
            return exit_code
        self._create_lock()
        repo_name = self.config['repo_name']
        try:
            deploy_file = open(self.config['deploy_file'], 'r')
            deploy_info = deploy_file.read()
            deploy_info = json.loads(deploy_info)
        except OSError:
            exit_code = 50
            log.error("{0}::{1}".format(__name__, exit_codes[exit_code]))
            return exit_code
        return self._sync(repo_name, deploy_info["tag"], self.config(force))

    def revert(self, args):
        """
            * write a lock file
            * write previous deploy info into .deploy
            * call sync hook with the prefix (repo) and tag info
            * remove lock file
        """
        raise NotImplementedError()

    def show_tag(self, args):
        """
            * display current tagged release
        """
        raise NotImplementedError()

    def log_deploys(self, args):
        """
            * show last x deploys
        """
        raise NotImplementedError()

    def diff(self, args):
        """
            * show a git diff of the last deploy and it's previous deploy
        """

        # Get the last two tags - assumes tagging on deployment only
        proc = subprocess.Popen("git tag | head -n 2".split())
        if not proc.returncode:
            tag_last = proc.stdout.readline().strip()
            tag_second_last = proc.stdout.readline().strip()
            if not tag_last or not tag_second_last:
                raise SartorisError(message=exit_codes[7], exit_code=7)
        else:
            raise SartorisError(message=exit_codes[6], exit_code=6)

        # Get the associated commit hashes for those tags
        sha_1 = self._get_commit_sha_for_tag(tag_last)
        sha_2 = self._get_commit_sha_for_tag(tag_second_last)

        # Produce the diff
        # @TODO replace with dulwich
        proc = "git diff {0} {1}".format(sha_2, sha_1).split()
        line = proc.stdout.readline()
        while line:
            print line
            line = proc.stdout.readline()
        return 0

def main(argv, out=None, err=None):
    """Main entry point.

    Returns a value that can be understood by :func:`sys.exit`.

    :param argv: a list of command line arguments, usually :data:`sys.argv`.
    :param out: stream to write messages; :data:`sys.stdout` if None.
    :param err: stream to write error messages; :data:`sys.stderr` if None.
    """
    if out is None:  # pragma: nocover
        out = sys.stdout
    if err is None:  # pragma: nocover
        err = sys.stderr
    args = parseargs(argv)
    level = logging.WARNING - ((args.verbose - args.quiet) * 10)
    if args.silent:
        level = logging.CRITICAL + 1

    format = "%(asctime)s %(levelname)-8s %(message)s"
    handler = logging.StreamHandler(err)
    handler.setFormatter(logging.Formatter(fmt=format,
                         datefmt='%b-%d %H:%M:%S'))
    log.addHandler(handler)
    log.setLevel(level)

    log.debug("Ready to run")

    # Inline call to functionality - if Sartoris does not possess this
    #  attribute flag with logger
    if not args.method:
        print args.help
        return 3

    if hasattr(Sartoris(), args.method) and callable(getattr(Sartoris(),
                                                     args.method)):
        try:
            getattr(Sartoris(), args.method)(args)
        except SartorisError as e:
            log.error(e.message)
            return e.exit_code
    else:
        log.error(__name__ + '::No function called %(method)s.' % {
            'method': args.method})

if __name__ == "__main__":  # pragma: nocover
    sys.exit(main(sys.argv))
<|endoftext|>