<commit_before>"""Machinery for exps, cs, varlocs data -- common to nomials and programs"""
from collections import defaultdict
from functools import reduce as functools_reduce
from operator import add
import numpy as np
from ..small_classes import HashVector, Quantity
from ..keydict import KeySet, KeyDict
from ..small_scripts import mag
from ..repr_conventions import _repr

class NomialData(object):
    """Object for holding cs, exps, and other basic 'nomial' properties.

    cs: array (coefficient of each monomial term)
    exps: tuple of {VarKey: float} (exponents of each monomial term)
    varlocs: {VarKey: list} (terms each variable appears in)
    units: pint.UnitsContainer
    """
    # pylint: disable=too-many-instance-attributes
    def __init__(self, exps=None, cs=None, simplify=True):
        if simplify:
            exps, cs = simplify_exps_and_cs(exps, cs)
        self.exps, self.cs = exps, cs
        self.any_nonpositive_cs = any(mag(c) <= 0 for c in self.cs)

        varlocs = {}
        for i, exp in enumerate(exps):
            for var in exp:
                if var not in varlocs:
                    varlocs[var] = []
                varlocs[var].append(i)
        self.varlocs = varlocs
        self._varkeys, self._values = None, None
        if hasattr(self.cs, "units"):
            self.units = Quantity(1, self.cs.units)  #pylint: disable=no-member
        else:
            self.units = None

        self._hashvalue = None

    __repr__ = _repr

    def __hash__(self):
        if self._hashvalue is None:
            # confirm lengths before calling zip
            assert len(self.exps) == len(self.cs)
            # identical pint.Quantity object instances have different hashes
            self._hashvalue = hash(tuple(list(zip(self.exps, mag(self.cs))) +
                                         [str(self.units)]))
        return self._hashvalue

    @property
    def varkeys(self):
        "The NomialData's varkeys, created when necessary for a substitution."
        if not self._varkeys:
            self._varkeys = KeySet(self.varlocs)
        return self._varkeys

    @property
    def values(self):
        "The NomialData's values, created when necessary."
        if not self._values:
            self._values = KeyDict({k: k.descr["value"] for k in self.varlocs
                                    if "value" in k.descr})
        return self._values

    def init_from_nomials(self, nomials):
        """Way to initialize from nomials. Calls __init__.
        Used by subclass __init__ methods.
        """
        exps = functools_reduce(add, (tuple(s.exps) for s in nomials))
        cs = np.hstack((mag(s.cs) for s in nomials))
        # nomials are already simplified, so simplify=False
        NomialData.__init__(self, exps, cs, simplify=False)
        self.units = tuple(s.units for s in nomials)

    def diff(self, var):
        """Derivative of this with respect to a Variable

        Arguments
        ---------
        var (Variable):
            Variable to take derivative with respect to

        Returns
        -------
        NomialData
        """
        varset = self.varkeys[var]
        if len(varset) == 0:
            return NomialData(exps=[{}], cs=[0], simplify=False)
        elif len(varset) > 1:
            raise ValueError("multiple variables %s found for key %s"
                             % (list(varset), var))
        else:
            var, = varset
        exps, cs = [], []
        # var.units may be str if units disabled
        if not var.units and not isinstance(var.units, str):
            units = 1
        else:
            if hasattr(self.cs, "units"):
                cs_units = Quantity(1, self.cs.units)
            else:
                cs_units = 1
            units = cs_units/var.units
        for i, exp in enumerate(self.exps):
            exp = HashVector(exp)   # copy -- exp is mutated below
            e = exp.get(var, 0)
            if var in exp:
                exp[var] -= 1
            exps.append(exp)
            cs.append(e*mag(self.cs)[i])
        # don't simplify to keep length same as self
        return NomialData(exps=exps, cs=cs*units, simplify=False)

    def __eq__(self, other):
        """Equality test"""
        if not all(hasattr(other, a) for a in ("exps", "cs")):
            return NotImplemented
        return self.exps == other.exps and all(self.cs == other.cs)

def simplify_exps_and_cs(exps, cs, return_map=False):
    """Reduces the number of monomials, and casts them to a sorted form.

    Arguments
    ---------

    exps : list of Hashvectors
        The exponents of each monomial
    cs : array of floats or Quantities
        The coefficients of each monomial
    return_map : bool (optional)
        Whether to return the map of which monomials combined to form a
        simpler monomial, and their fractions of that monomial's final c.

    Returns
    -------
    exps : list of Hashvectors
        Exponents of simplified monomials.
    cs : array of floats or Quantities
        Coefficients of simplified monomials.
    mmap : list of HashVectors
        List for each new monomial of {originating indexes: fractions}
    """
    matches = defaultdict(float)
    if return_map:
        expmap = defaultdict(dict)
    if isinstance(cs, Quantity):
        units = cs.units
        cs = cs.magnitude
    elif isinstance(cs[0], Quantity):
        units = cs[0].units
        if len(cs) == 1:
            cs = [cs[0].magnitude]
        else:
            cs = [c.to(units).magnitude for c in cs]
    else:
        units = None
    for i, exp in enumerate(exps):
        exp = HashVector({var: x for (var, x) in exp.items() if x != 0})
        matches[exp] += cs[i]
        if return_map:
            expmap[exp][i] = cs[i]

    if len(matches) > 1:
        zeroed_terms = (exp for exp, c in matches.items() if mag(c) == 0)
        for exp in zeroed_terms:
            del matches[exp]

    exps_ = tuple(matches.keys())
    cs_ = list(matches.values())
    cs_ = Quantity(cs_, units) if units else np.array(cs_, dtype='float')

    if not return_map:
        return exps_, cs_
    else:
        mmap = [HashVector() for c in cs_]
        for i, item in enumerate(matches.items()):
            exp, c = item
            for j in expmap[exp]:
                mmap[i][j] = mag(expmap[exp][j]/c)
        return exps_, cs_, mmap
<commit_msg>fix diff logic<commit_after>"""Machinery for exps, cs, varlocs data -- common to nomials and programs"""
from collections import defaultdict
from functools import reduce as functools_reduce
from operator import add
import numpy as np
from ..small_classes import HashVector, Quantity
from ..keydict import KeySet, KeyDict
from ..small_scripts import mag
from ..repr_conventions import _repr

class NomialData(object):
    """Object for holding cs, exps, and other basic 'nomial' properties.

    cs: array (coefficient of each monomial term)
    exps: tuple of {VarKey: float} (exponents of each monomial term)
    varlocs: {VarKey: list} (terms each variable appears in)
    units: pint.UnitsContainer
    """
    # pylint: disable=too-many-instance-attributes
    def __init__(self, exps=None, cs=None, simplify=True):
        if simplify:
            exps, cs = simplify_exps_and_cs(exps, cs)
        self.exps, self.cs = exps, cs
        self.any_nonpositive_cs = any(mag(c) <= 0 for c in self.cs)

        varlocs = {}
        for i, exp in enumerate(exps):
            for var in exp:
                if var not in varlocs:
                    varlocs[var] = []
                varlocs[var].append(i)
        self.varlocs = varlocs
        self._varkeys, self._values = None, None
        if hasattr(self.cs, "units"):
            self.units = Quantity(1, self.cs.units)  #pylint: disable=no-member
        else:
            self.units = None

        self._hashvalue = None

    __repr__ = _repr

    def __hash__(self):
        if self._hashvalue is None:
            # confirm lengths before calling zip
            assert len(self.exps) == len(self.cs)
            # identical pint.Quantity object instances have different hashes
            self._hashvalue = hash(tuple(list(zip(self.exps, mag(self.cs))) +
                                         [str(self.units)]))
        return self._hashvalue

    @property
    def varkeys(self):
        "The NomialData's varkeys, created when necessary for a substitution."
        if not self._varkeys:
            self._varkeys = KeySet(self.varlocs)
        return self._varkeys

    @property
    def values(self):
        "The NomialData's values, created when necessary."
        if not self._values:
            self._values = KeyDict({k: k.descr["value"] for k in self.varlocs
                                    if "value" in k.descr})
        return self._values

    def init_from_nomials(self, nomials):
        """Way to initialize from nomials. Calls __init__.
        Used by subclass __init__ methods.
        """
        exps = functools_reduce(add, (tuple(s.exps) for s in nomials))
        cs = np.hstack((mag(s.cs) for s in nomials))
        # nomials are already simplified, so simplify=False
        NomialData.__init__(self, exps, cs, simplify=False)
        self.units = tuple(s.units for s in nomials)

    def diff(self, var):
        """Derivative of this with respect to a Variable

        Arguments
        ---------
        var (Variable):
            Variable to take derivative with respect to

        Returns
        -------
        NomialData
        """
        varset = self.varkeys[var]
        if len(varset) == 0:
            return NomialData(exps=[{}], cs=[0], simplify=False)
        elif len(varset) > 1:
            raise ValueError("multiple variables %s found for key %s"
                             % (list(varset), var))
        else:
            var, = varset
        exps, cs = [], []
        # var.units may be str if units disabled
        if not var.units:
            units = 1
        elif isinstance(var.units, str):
            if hasattr(self.cs, "units"):
                cs_units = Quantity(1, self.cs.units)
            else:
                cs_units = 1
            units = cs_units/var.units
        for i, exp in enumerate(self.exps):
            exp = HashVector(exp)   # copy -- exp is mutated below
            e = exp.get(var, 0)
            if var in exp:
                exp[var] -= 1
            exps.append(exp)
            cs.append(e*mag(self.cs)[i])
        # don't simplify to keep length same as self
        return NomialData(exps=exps, cs=cs*units, simplify=False)

    def __eq__(self, other):
        """Equality test"""
        if not all(hasattr(other, a) for a in ("exps", "cs")):
            return NotImplemented
        return self.exps == other.exps and all(self.cs == other.cs)

def simplify_exps_and_cs(exps, cs, return_map=False):
    """Reduces the number of monomials, and casts them to a sorted form.

    Arguments
    ---------

    exps : list of Hashvectors
        The exponents of each monomial
    cs : array of floats or Quantities
        The coefficients of each monomial
    return_map : bool (optional)
        Whether to return the map of which monomials combined to form a
        simpler monomial, and their fractions of that monomial's final c.

    Returns
    -------
    exps : list of Hashvectors
        Exponents of simplified monomials.
    cs : array of floats or Quantities
        Coefficients of simplified monomials.
    mmap : list of HashVectors
        List for each new monomial of {originating indexes: fractions}
    """
    matches = defaultdict(float)
    if return_map:
        expmap = defaultdict(dict)
    if isinstance(cs, Quantity):
        units = cs.units
        cs = cs.magnitude
    elif isinstance(cs[0], Quantity):
        units = cs[0].units
        if len(cs) == 1:
            cs = [cs[0].magnitude]
        else:
            cs = [c.to(units).magnitude for c in cs]
    else:
        units = None
    for i, exp in enumerate(exps):
        exp = HashVector({var: x for (var, x) in exp.items() if x != 0})
        matches[exp] += cs[i]
        if return_map:
            expmap[exp][i] = cs[i]

    if len(matches) > 1:
        zeroed_terms = (exp for exp, c in matches.items() if mag(c) == 0)
        for exp in zeroed_terms:
            del matches[exp]

    exps_ = tuple(matches.keys())
    cs_ = list(matches.values())
    cs_ = Quantity(cs_, units) if units else np.array(cs_, dtype='float')

    if not return_map:
        return exps_, cs_
    else:
        mmap = [HashVector() for c in cs_]
        for i, item in enumerate(matches.items()):
            exp, c = item
            for j in expmap[exp]:
                mmap[i][j] = mag(expmap[exp][j]/c)
        return exps_, cs_, mmap
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

# This file is part of GrowthRatesPy.
# https://github.com/yoavram/GrowthRatesPy

# Licensed under the MIT license:
# http://www.opensource.org/licenses/MIT-license
# Copyright (c) 2016, Yoav Ram <yoav@yoavram.com>
from __future__ import unicode_literals
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from builtins import open
from builtins import str
from future import standard_library
standard_library.install_aliases()
import subprocess
import os
import sys
import tempfile
try:
    from functools import lru_cache
except ImportError:
    from functools32 import lru_cache

import pandas as pd

@lru_cache(maxsize=1)
def find_growthrates():
    """Attempts to find `GrowthRates`.

    Tries to use `shutil.which` or  `distutils.spawn.find_executable`,
    otherwise tries to find it in `Program Files` folder.

    Returns
    -------
    str
        Path to `GrowthRates`; raises `ValueError` if couldn't find.

    """
    try:
        from shutil import which
    except ImportError:
        from distutils.spawn import find_executable as which

    for path in [
        which('GrowthRates'),
        which('GrowthRates.exe'),
        r'C:\Program Files\GrowthRates\GrowthRates.exe',
        r'C:\Program Files (x86)\GrowthRates\GrowthRates.exe',
        r'C:\Program Files\GrowthRates 2.1\GrowthRates.exe',
        r'C:\Program Files (x86)\GrowthRates 2.1\GrowthRates.exe',
        r'C:\Program Files\GrowthRates 2.1 Windows\GrowthRates.exe',
        r'C:\Program Files (x86)\GrowthRates 2.1 Windows\GrowthRates.exe',
        None
    ]:
        if path and os.path.exists(path):
            break
    if not path:
        raise ValueError("Didn't find GrowthRates executable.")
    return path

def growthrates(infile=None, data=None, blank_well=None):
    """Runs `GrowthRates` on input file with blank given well.

    Parameters
    ----------
    infile : str
        path to data file.
    data : pd.DataFrame
        Data to used instead of `infile`.
    blank_well : int
        well number for background OD.

    Returns
    -------
    pandas.DataFrame
        Table of GrowthRates results.
    """
    if not isinstance(blank_well, int) or blank_well <= 0 :
        raise ValueError("blank_well must be a non-negative integer")

    if data is not None:
        infile = tempfile.mktemp(suffix='.tsv')
        data.to_csv(infile, sep='\t')

    if not os.path.exists(infile):
        raise ValueError("Path to input file doesn't exist:", infile)

    gr_path = find_growthrates()

    arguments = [gr_path, '-i', infile]
    if blank_well is not None:
        arguments.extend(['-w', str(blank_well)])

    _execute_growthrates(arguments)

    base_filename = os.path.splitext(infile)[0]
    # results_filename = base_filename + '.results'
    summary_filename = base_filename + '.summary'
    return read_summary(summary_filename)

def _execute_growthrates(arguments):
    status_code = subprocess.call(arguments)
    if status_code != 0:
        raise RuntimeError("GrowthRates returns status code {:d}".format(status_code))

def read_results(results_filename):
    """Loads a GrowthRates results file.

    Parameters
    ----------
    results_filename : str
        path to results file.

    Returns
    -------
    pandas.DataFrame
        Table of GrowthRates results.
    """
    with open(results_filename) as f:
        for line in f:
            if line.startswith('*'):
                break
        return [line.strip() for line in f if line and line != '*' * 57]

def read_summary(summary_filename):
    """Loads a GrowthRates summary file.

    Parameters
    ----------
    summary_filename : str
        path to summary file.

    Returns
    -------
    pandas.DataFrame
        Table of GrowthRates summary.
    """
    with open(summary_filename) as f:
        return pd.read_csv(f, sep='\t', skipinitialspace=True, skiprows=1)

if __name__ == '__main__':
    summary = growthrates('../Tecan_210115.tsv', 96)
    assert summary.shape == (96, 5)
    sys.exit(0)
<commit_msg>when saving df to file, don't save the index<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

# This file is part of GrowthRatesPy.
# https://github.com/yoavram/GrowthRatesPy

# Licensed under the MIT license:
# http://www.opensource.org/licenses/MIT-license
# Copyright (c) 2016, Yoav Ram <yoav@yoavram.com>
from __future__ import unicode_literals
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from builtins import open
from builtins import str
from future import standard_library
standard_library.install_aliases()
import subprocess
import os
import sys
import tempfile
try:
    from functools import lru_cache
except ImportError:
    from functools32 import lru_cache

import pandas as pd

@lru_cache(maxsize=1)
def find_growthrates():
    """Attempts to find `GrowthRates`.

    Tries to use `shutil.which` or  `distutils.spawn.find_executable`,
    otherwise tries to find it in `Program Files` folder.

    Returns
    -------
    str
        Path to `GrowthRates`; raises `ValueError` if couldn't find.

    """
    try:
        from shutil import which
    except ImportError:
        from distutils.spawn import find_executable as which

    for path in [
        which('GrowthRates'),
        which('GrowthRates.exe'),
        r'C:\Program Files\GrowthRates\GrowthRates.exe',
        r'C:\Program Files (x86)\GrowthRates\GrowthRates.exe',
        r'C:\Program Files\GrowthRates 2.1\GrowthRates.exe',
        r'C:\Program Files (x86)\GrowthRates 2.1\GrowthRates.exe',
        r'C:\Program Files\GrowthRates 2.1 Windows\GrowthRates.exe',
        r'C:\Program Files (x86)\GrowthRates 2.1 Windows\GrowthRates.exe',
        None
    ]:
        if path and os.path.exists(path):
            break
    if not path:
        raise ValueError("Didn't find GrowthRates executable.")
    return path

def growthrates(infile=None, data=None, blank_well=None):
    """Runs `GrowthRates` on input file with blank given well.

    Parameters
    ----------
    infile : str
        path to data file.
    data : pd.DataFrame
        Data to used instead of `infile`.
    blank_well : int
        well number for background OD.

    Returns
    -------
    pandas.DataFrame
        Table of GrowthRates results.
    """
    if not isinstance(blank_well, int) or blank_well <= 0 :
        raise ValueError("blank_well must be a non-negative integer")

    if data is not None:
        infile = tempfile.mktemp(suffix='.tsv')
        data.to_csv(infile, sep='\t', index=False)

    if not os.path.exists(infile):
        raise ValueError("Path to input file doesn't exist:", infile)

    gr_path = find_growthrates()

    arguments = [gr_path, '-i', infile]
    if blank_well is not None:
        arguments.extend(['-w', str(blank_well)])

    _execute_growthrates(arguments)

    base_filename = os.path.splitext(infile)[0]
    # results_filename = base_filename + '.results'
    summary_filename = base_filename + '.summary'
    return read_summary(summary_filename)

def _execute_growthrates(arguments):
    status_code = subprocess.call(arguments)
    if status_code != 0:
        raise RuntimeError("GrowthRates returns status code {:d}".format(status_code))

def read_results(results_filename):
    """Loads a GrowthRates results file.

    Parameters
    ----------
    results_filename : str
        path to results file.

    Returns
    -------
    pandas.DataFrame
        Table of GrowthRates results.
    """
    with open(results_filename) as f:
        for line in f:
            if line.startswith('*'):
                break
        return [line.strip() for line in f if line and line != '*' * 57]

def read_summary(summary_filename):
    """Loads a GrowthRates summary file.

    Parameters
    ----------
    summary_filename : str
        path to summary file.

    Returns
    -------
    pandas.DataFrame
        Table of GrowthRates summary.
    """
    with open(summary_filename) as f:
        return pd.read_csv(f, sep='\t', skipinitialspace=True, skiprows=1)

if __name__ == '__main__':
    summary = growthrates('../Tecan_210115.tsv', 96)
    assert summary.shape == (96, 5)
    sys.exit(0)
<|endoftext|>


<commit_before>from django.db import models
from django.utils.translation import ugettext_lazy as _

class SiteManager(models.Manager):
    def get_current(self):
        from django.conf import settings
        try:
            sid = settings.SITE_ID
        except AttributeError:
            from django.core.exceptions import ImproperlyConfigured
            raise ImproperlyConfigured("You're using the Django \"sites framework\" without having set the SITE_ID setting. Create a site in your database and set the SITE_ID setting to fix this error.")
        return self.get(pk=sid)

class Site(models.Model):
    domain = models.CharField(_('domain name'), max_length=100)
    name = models.CharField(_('display name'), max_length=50)
    objects = SiteManager()
    class Meta:
        db_table = 'django_site'
        verbose_name = _('site')
        verbose_name_plural = _('sites')
        ordering = ('domain',)
    class Admin:
        list_display = ('domain', 'name')
        search_fields = ('domain', 'name')

    def __unicode__(self):
        return self.domain

class RequestSite(object):
    """
    A class that shares the primary interface of Site (i.e., it has
    ``domain`` and ``name`` attributes) but gets its data from a Django
    HttpRequest object rather than from a database.

    The save() and delete() methods raise NotImplementedError.
    """
    def __init__(self, request):
        self.domain = self.name = request.META['SERVER_NAME']

    def __unicode__(self):
        return self.domain

    def save(self):
        raise NotImplementedError('RequestSite cannot be saved.')

    def delete(self):
        raise NotImplementedError('RequestSite cannot be deleted.')
<commit_msg>Fixed #4951 -- Improved host retrieval in sites.models.RequestSite. Thanks, Caleb.<commit_after>from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.http import get_host

class SiteManager(models.Manager):
    def get_current(self):
        from django.conf import settings
        try:
            sid = settings.SITE_ID
        except AttributeError:
            from django.core.exceptions import ImproperlyConfigured
            raise ImproperlyConfigured("You're using the Django \"sites framework\" without having set the SITE_ID setting. Create a site in your database and set the SITE_ID setting to fix this error.")
        return self.get(pk=sid)

class Site(models.Model):
    domain = models.CharField(_('domain name'), max_length=100)
    name = models.CharField(_('display name'), max_length=50)
    objects = SiteManager()
    class Meta:
        db_table = 'django_site'
        verbose_name = _('site')
        verbose_name_plural = _('sites')
        ordering = ('domain',)
    class Admin:
        list_display = ('domain', 'name')
        search_fields = ('domain', 'name')

    def __unicode__(self):
        return self.domain

class RequestSite(object):
    """
    A class that shares the primary interface of Site (i.e., it has
    ``domain`` and ``name`` attributes) but gets its data from a Django
    HttpRequest object rather than from a database.

    The save() and delete() methods raise NotImplementedError.
    """
    def __init__(self, request):
        self.domain = self.name = get_host(request)

    def __unicode__(self):
        return self.domain

    def save(self):
        raise NotImplementedError('RequestSite cannot be saved.')

    def delete(self):
        raise NotImplementedError('RequestSite cannot be deleted.')
<|endoftext|>


<commit_before>"""
Base classes for writing management commands (named commands which can
be executed through ``django-admin.py`` or ``manage.py``).

"""

import os
import sys
from optparse import make_option, OptionParser

import django
from django.core.exceptions import ImproperlyConfigured
from django.core.management.color import color_style
from django.utils.encoding import smart_str

class CommandError(Exception):
    """
    Exception class indicating a problem while executing a management
    command.

    If this exception is raised during the execution of a management
    command, it will be caught and turned into a nicely-printed error
    message to the appropriate output stream (i.e., stderr); as a
    result, raising this exception (with a sensible description of the
    error) is the preferred way to indicate that something has gone
    wrong in the execution of a command.

    """
    pass

def handle_default_options(options):
    """
    Include any default options that all commands should accept here
    so that ManagementUtility can handle them before searching for
    user commands.

    """
    if options.settings:
        os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
    if options.pythonpath:
        sys.path.insert(0, options.pythonpath)

class BaseCommand(object):
    """
    The base class from which all management commands ultimately
    derive.

    Use this class if you want access to all of the mechanisms which
    parse the command-line arguments and work out what code to call in
    response; if you don't need to change any of that behavior,
    consider using one of the subclasses defined in this file.

    If you are interested in overriding/customizing various aspects of
    the command-parsing and -execution behavior, the normal flow works
    as follows:

    1. ``django-admin.py`` or ``manage.py`` loads the command class
       and calls its ``run_from_argv()`` method.

    2. The ``run_from_argv()`` method calls ``create_parser()`` to get
       an ``OptionParser`` for the arguments, parses them, performs
       any environment changes requested by options like
       ``pythonpath``, and then calls the ``execute()`` method,
       passing the parsed arguments.

    3. The ``execute()`` method attempts to carry out the command by
       calling the ``handle()`` method with the parsed arguments; any
       output produced by ``handle()`` will be printed to standard
       output and, if the command is intended to produce a block of
       SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``.

    4. If ``handle()`` raised a ``CommandError``, ``execute()`` will
       instead print an error message to ``stderr``.

    Thus, the ``handle()`` method is typically the starting point for
    subclasses; many built-in commands and command types either place
    all of their logic in ``handle()``, or perform some additional
    parsing work in ``handle()`` and then delegate from it to more
    specialized methods as needed.

    Several attributes affect behavior at various steps along the way:

    ``args``
        A string listing the arguments accepted by the command,
        suitable for use in help messages; e.g., a command which takes
        a list of application names might set this to '<appname
        appname ...>'.

    ``can_import_settings``
        A boolean indicating whether the command needs to be able to
        import Django settings; if ``True``, ``execute()`` will verify
        that this is possible before proceeding. Default value is
        ``True``.

    ``help``
        A short description of the command, which will be printed in
        help messages.

    ``option_list``
        This is the list of ``optparse`` options which will be fed
        into the command's ``OptionParser`` for parsing arguments.

    ``output_transaction``
        A boolean indicating whether the command outputs SQL
        statements; if ``True``, the output will automatically be
        wrapped with ``BEGIN;`` and ``COMMIT;``. Default value is
        ``False``.

    ``requires_model_validation``
        A boolean; if ``True``, validation of installed models will be
        performed prior to executing the command. Default value is
        ``True``. To validate an individual application's models
        rather than all applications' models, call
        ``self.validate(app)`` from ``handle()``, where ``app`` is the
        application's Python module.

    """
    # Metadata about this command.
    option_list = (
        make_option('-v', '--verbosity', action='store', dest='verbosity', default='1',
            type='choice', choices=['0', '1', '2', '3'],
            help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
        make_option('--settings',
            help='The Python path to a settings module, e.g. "myproject.settings.main". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.'),
        make_option('--pythonpath',
            help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".'),
        make_option('--traceback', action='store_true',
            help='Print traceback on exception'),
    )
    help = ''
    args = ''

    # Configuration shortcuts that alter various logic.
    can_import_settings = True
    requires_model_validation = True
    output_transaction = False # Whether to wrap the output in a "BEGIN; COMMIT;"

    def __init__(self):
        self.style = color_style()

    def get_version(self):
        """
        Return the Django version, which should be correct for all
        built-in Django commands. User-supplied commands should
        override this method.

        """
        return django.get_version()

    def usage(self, subcommand):
        """
        Return a brief description of how to use this command, by
        default from the attribute ``self.help``.

        """
        usage = '%%prog %s [options] %s' % (subcommand, self.args)
        if self.help:
            return '%s\n\n%s' % (usage, self.help)
        else:
            return usage

    def create_parser(self, prog_name, subcommand):
        """
        Create and return the ``OptionParser`` which will be used to
        parse the arguments to this command.

        """
        return OptionParser(prog=prog_name,
                            usage=self.usage(subcommand),
                            version=self.get_version(),
                            option_list=self.option_list)

    def print_help(self, prog_name, subcommand):
        """
        Print the help message for this command, derived from
        ``self.usage()``.

        """
        parser = self.create_parser(prog_name, subcommand)
        parser.print_help()

    def run_from_argv(self, argv):
        """
        Set up any environment changes requested (e.g., Python path
        and Django settings), then run this command.

        """
        parser = self.create_parser(argv[0], argv[1])
        options, args = parser.parse_args(argv[2:])
        handle_default_options(options)
        self.execute(*args, **options.__dict__)

    def execute(self, *args, **options):
        """
        Try to execute this command, performing model validation if
        needed (as controlled by the attribute
        ``self.requires_model_validation``). If the command raises a
        ``CommandError``, intercept it and print it sensibly to
        stderr.

        """
        # Switch to English, because django-admin.py creates database content
        # like permissions, and those shouldn't contain any translations.
        # But only do this if we can assume we have a working settings file,
        # because django.utils.translation requires settings.
        if self.can_import_settings:
            try:
                from django.utils import translation
                translation.activate('en-us')
            except ImportError, e:
                # If settings should be available, but aren't,
                # raise the error and quit.
                sys.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e)))
                sys.exit(1)
        try:
            self.stdout = options.get('stdout', sys.stdout)
            self.stderr = options.get('stderr', sys.stderr)
            if self.requires_model_validation:
                self.validate()
            output = self.handle(*args, **options)
            if output:
                if self.output_transaction:
                    # This needs to be imported here, because it relies on
                    # settings.
                    from django.db import connections, DEFAULT_DB_ALIAS
                    connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
                    if connection.ops.start_transaction_sql():
                        self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()))
                self.stdout.write(output)
                if self.output_transaction:
                    self.stdout.write(self.style.SQL_KEYWORD("COMMIT;") + '\n')
        except CommandError, e:
            self.stderr.write(smart_str(self.style.ERROR('Error: %s\n' % e)))
            sys.exit(1)

    def validate(self, app=None, display_num_errors=False):
        """
        Validates the given app, raising CommandError for any errors.

        If app is None, then this will validate all installed apps.

        """
        from django.core.management.validation import get_validation_errors
        try:
            from cStringIO import StringIO
        except ImportError:
            from StringIO import StringIO
        s = StringIO()
        num_errors = get_validation_errors(s, app)
        if num_errors:
            s.seek(0)
            error_text = s.read()
            raise CommandError("One or more models did not validate:\n%s" % error_text)
        if display_num_errors:
            self.stdout.write("%s error%s found\n" % (num_errors, num_errors != 1 and 's' or ''))

    def handle(self, *args, **options):
        """
        The actual logic of the command. Subclasses must implement
        this method.

        """
        raise NotImplementedError()

class AppCommand(BaseCommand):
    """
    A management command which takes one or more installed application
    names as arguments, and does something with each of them.

    Rather than implementing ``handle()``, subclasses must implement
    ``handle_app()``, which will be called once for each application.

    """
    args = '<appname appname ...>'

    def handle(self, *app_labels, **options):
        from django.db import models
        if not app_labels:
            raise CommandError('Enter at least one appname.')
        try:
            app_list = [models.get_app(app_label) for app_label in app_labels]
        except (ImproperlyConfigured, ImportError), e:
            raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e)
        output = []
        for app in app_list:
            app_output = self.handle_app(app, **options)
            if app_output:
                output.append(app_output)
        return '\n'.join(output)

    def handle_app(self, app, **options):
        """
        Perform the command's actions for ``app``, which will be the
        Python module corresponding to an application name given on
        the command line.

        """
        raise NotImplementedError()

class LabelCommand(BaseCommand):
    """
    A management command which takes one or more arbitrary arguments
    (labels) on the command line, and does something with each of
    them.

    Rather than implementing ``handle()``, subclasses must implement
    ``handle_label()``, which will be called once for each label.

    If the arguments should be names of installed applications, use
    ``AppCommand`` instead.

    """
    args = '<label label ...>'
    label = 'label'

    def handle(self, *labels, **options):
        if not labels:
            raise CommandError('Enter at least one %s.' % self.label)

        output = []
        for label in labels:
            label_output = self.handle_label(label, **options)
            if label_output:
                output.append(label_output)
        return '\n'.join(output)

    def handle_label(self, label, **options):
        """
        Perform the command's actions for ``label``, which will be the
        string as given on the command line.

        """
        raise NotImplementedError()

class NoArgsCommand(BaseCommand):
    """
    A command which takes no arguments on the command line.

    Rather than implementing ``handle()``, subclasses must implement
    ``handle_noargs()``; ``handle()`` itself is overridden to ensure
    no arguments are passed to the command.

    Attempting to pass arguments will raise ``CommandError``.

    """
    args = ''

    def handle(self, *args, **options):
        if args:
            raise CommandError("Command doesn't accept any arguments")
        return self.handle_noargs(**options)

    def handle_noargs(self, **options):
        """
        Perform this command's actions.

        """
        raise NotImplementedError()

def copy_helper(style, app_or_project, name, directory, other_name=''):
    """
    Copies either a Django application layout template or a Django project
    layout template into the specified directory.

    """
    # style -- A color style object (see django.core.management.color).
    # app_or_project -- The string 'app' or 'project'.
    # name -- The name of the application or project.
    # directory -- The directory to which the layout template should be copied.
    # other_name -- When copying an application layout, this should be the name
    #               of the project.
    import re
    import shutil
    other = {'project': 'app', 'app': 'project'}[app_or_project]
    if not re.search(r'^[_a-zA-Z]\w*$', name): # If it's not a valid directory name.
        # Provide a smart error message, depending on the error.
        if not re.search(r'^[_a-zA-Z]', name):
            message = 'make sure the name begins with a letter or underscore'
        else:
            message = 'use only numbers, letters and underscores'
        raise CommandError("%r is not a valid %s name. Please %s." % (name, app_or_project, message))
    top_dir = os.path.join(directory, name)
    try:
        os.mkdir(top_dir)
    except OSError, e:
        raise CommandError(e)

    # Determine where the app or project templates are. Use
    # django.__path__[0] because we don't know into which directory
    # django has been installed.
    template_dir = os.path.join(django.__path__[0], 'conf', '%s_template' % app_or_project)

    for d, subdirs, files in os.walk(template_dir):
        relative_dir = d[len(template_dir)+1:].replace('%s_name' % app_or_project, name)
        if relative_dir:
            os.mkdir(os.path.join(top_dir, relative_dir))
        for subdir in subdirs[:]:
            if subdir.startswith('.'):
                subdirs.remove(subdir)
        for f in files:
            if not f.endswith('.py'):
                # Ignore .pyc, .pyo, .py.class etc, as they cause various
                # breakages.
                continue
            path_old = os.path.join(d, f)
            path_new = os.path.join(top_dir, relative_dir, f.replace('%s_name' % app_or_project, name))
            fp_old = open(path_old, 'r')
            fp_new = open(path_new, 'w')
            fp_new.write(fp_old.read().replace('{{ %s_name }}' % app_or_project, name).replace('{{ %s_name }}' % other, other_name))
            fp_old.close()
            fp_new.close()
            try:
                shutil.copymode(path_old, path_new)
                _make_writeable(path_new)
            except OSError:
                sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new))

def _make_writeable(filename):
    """
    Make sure that the file is writeable. Useful if our source is
    read-only.

    """
    import stat
    if sys.platform.startswith('java'):
        # On Jython there is no os.access()
        return
    if not os.access(filename, os.W_OK):
        st = os.stat(filename)
        new_permissions = stat.S_IMODE(st.st_mode) | stat.S_IWUSR
        os.chmod(filename, new_permissions)
<commit_msg>Used traceback.format_exc instead of just printing crappy "Import Error" when running management commands such as "runserver" or "shell"<commit_after>"""
Base classes for writing management commands (named commands which can
be executed through ``django-admin.py`` or ``manage.py``).

"""

import os
import sys
import traceback
from optparse import make_option, OptionParser

import django
from django.core.exceptions import ImproperlyConfigured
from django.core.management.color import color_style
from django.utils.encoding import smart_str

class CommandError(Exception):
    """
    Exception class indicating a problem while executing a management
    command.

    If this exception is raised during the execution of a management
    command, it will be caught and turned into a nicely-printed error
    message to the appropriate output stream (i.e., stderr); as a
    result, raising this exception (with a sensible description of the
    error) is the preferred way to indicate that something has gone
    wrong in the execution of a command.

    """
    pass

def handle_default_options(options):
    """
    Include any default options that all commands should accept here
    so that ManagementUtility can handle them before searching for
    user commands.

    """
    if options.settings:
        os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
    if options.pythonpath:
        sys.path.insert(0, options.pythonpath)

class BaseCommand(object):
    """
    The base class from which all management commands ultimately
    derive.

    Use this class if you want access to all of the mechanisms which
    parse the command-line arguments and work out what code to call in
    response; if you don't need to change any of that behavior,
    consider using one of the subclasses defined in this file.

    If you are interested in overriding/customizing various aspects of
    the command-parsing and -execution behavior, the normal flow works
    as follows:

    1. ``django-admin.py`` or ``manage.py`` loads the command class
       and calls its ``run_from_argv()`` method.

    2. The ``run_from_argv()`` method calls ``create_parser()`` to get
       an ``OptionParser`` for the arguments, parses them, performs
       any environment changes requested by options like
       ``pythonpath``, and then calls the ``execute()`` method,
       passing the parsed arguments.

    3. The ``execute()`` method attempts to carry out the command by
       calling the ``handle()`` method with the parsed arguments; any
       output produced by ``handle()`` will be printed to standard
       output and, if the command is intended to produce a block of
       SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``.

    4. If ``handle()`` raised a ``CommandError``, ``execute()`` will
       instead print an error message to ``stderr``.

    Thus, the ``handle()`` method is typically the starting point for
    subclasses; many built-in commands and command types either place
    all of their logic in ``handle()``, or perform some additional
    parsing work in ``handle()`` and then delegate from it to more
    specialized methods as needed.

    Several attributes affect behavior at various steps along the way:

    ``args``
        A string listing the arguments accepted by the command,
        suitable for use in help messages; e.g., a command which takes
        a list of application names might set this to '<appname
        appname ...>'.

    ``can_import_settings``
        A boolean indicating whether the command needs to be able to
        import Django settings; if ``True``, ``execute()`` will verify
        that this is possible before proceeding. Default value is
        ``True``.

    ``help``
        A short description of the command, which will be printed in
        help messages.

    ``option_list``
        This is the list of ``optparse`` options which will be fed
        into the command's ``OptionParser`` for parsing arguments.

    ``output_transaction``
        A boolean indicating whether the command outputs SQL
        statements; if ``True``, the output will automatically be
        wrapped with ``BEGIN;`` and ``COMMIT;``. Default value is
        ``False``.

    ``requires_model_validation``
        A boolean; if ``True``, validation of installed models will be
        performed prior to executing the command. Default value is
        ``True``. To validate an individual application's models
        rather than all applications' models, call
        ``self.validate(app)`` from ``handle()``, where ``app`` is the
        application's Python module.

    """
    # Metadata about this command.
    option_list = (
        make_option('-v', '--verbosity', action='store', dest='verbosity', default='1',
            type='choice', choices=['0', '1', '2', '3'],
            help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
        make_option('--settings',
            help='The Python path to a settings module, e.g. "myproject.settings.main". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.'),
        make_option('--pythonpath',
            help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".'),
        make_option('--traceback', action='store_true',
            help='Print traceback on exception'),
    )
    help = ''
    args = ''

    # Configuration shortcuts that alter various logic.
    can_import_settings = True
    requires_model_validation = True
    output_transaction = False # Whether to wrap the output in a "BEGIN; COMMIT;"

    def __init__(self):
        self.style = color_style()

    def get_version(self):
        """
        Return the Django version, which should be correct for all
        built-in Django commands. User-supplied commands should
        override this method.

        """
        return django.get_version()

    def usage(self, subcommand):
        """
        Return a brief description of how to use this command, by
        default from the attribute ``self.help``.

        """
        usage = '%%prog %s [options] %s' % (subcommand, self.args)
        if self.help:
            return '%s\n\n%s' % (usage, self.help)
        else:
            return usage

    def create_parser(self, prog_name, subcommand):
        """
        Create and return the ``OptionParser`` which will be used to
        parse the arguments to this command.

        """
        return OptionParser(prog=prog_name,
                            usage=self.usage(subcommand),
                            version=self.get_version(),
                            option_list=self.option_list)

    def print_help(self, prog_name, subcommand):
        """
        Print the help message for this command, derived from
        ``self.usage()``.

        """
        parser = self.create_parser(prog_name, subcommand)
        parser.print_help()

    def run_from_argv(self, argv):
        """
        Set up any environment changes requested (e.g., Python path
        and Django settings), then run this command.

        """
        parser = self.create_parser(argv[0], argv[1])
        options, args = parser.parse_args(argv[2:])
        handle_default_options(options)
        self.execute(*args, **options.__dict__)

    def execute(self, *args, **options):
        """
        Try to execute this command, performing model validation if
        needed (as controlled by the attribute
        ``self.requires_model_validation``). If the command raises a
        ``CommandError``, intercept it and print it sensibly to
        stderr.

        """
        # Switch to English, because django-admin.py creates database content
        # like permissions, and those shouldn't contain any translations.
        # But only do this if we can assume we have a working settings file,
        # because django.utils.translation requires settings.
        if self.can_import_settings:
            try:
                from django.utils import translation
                translation.activate('en-us')
            except ImportError, e:
                # If settings should be available, but aren't,
                # raise the error and quit.
                sys.stderr.write(smart_str(self.style.ERROR(traceback.format_exc())))
                sys.exit(1)
        try:
            self.stdout = options.get('stdout', sys.stdout)
            self.stderr = options.get('stderr', sys.stderr)
            if self.requires_model_validation:
                self.validate()
            output = self.handle(*args, **options)
            if output:
                if self.output_transaction:
                    # This needs to be imported here, because it relies on
                    # settings.
                    from django.db import connections, DEFAULT_DB_ALIAS
                    connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
                    if connection.ops.start_transaction_sql():
                        self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()))
                self.stdout.write(output)
                if self.output_transaction:
                    self.stdout.write(self.style.SQL_KEYWORD("COMMIT;") + '\n')
        except CommandError, e:
            self.stderr.write(smart_str(self.style.ERROR(traceback.format_exc())))
            sys.exit(1)

    def validate(self, app=None, display_num_errors=False):
        """
        Validates the given app, raising CommandError for any errors.

        If app is None, then this will validate all installed apps.

        """
        from django.core.management.validation import get_validation_errors
        try:
            from cStringIO import StringIO
        except ImportError:
            from StringIO import StringIO
        s = StringIO()
        num_errors = get_validation_errors(s, app)
        if num_errors:
            s.seek(0)
            error_text = s.read()
            raise CommandError("One or more models did not validate:\n%s" % error_text)
        if display_num_errors:
            self.stdout.write("%s error%s found\n" % (num_errors, num_errors != 1 and 's' or ''))

    def handle(self, *args, **options):
        """
        The actual logic of the command. Subclasses must implement
        this method.

        """
        raise NotImplementedError()

class AppCommand(BaseCommand):
    """
    A management command which takes one or more installed application
    names as arguments, and does something with each of them.

    Rather than implementing ``handle()``, subclasses must implement
    ``handle_app()``, which will be called once for each application.

    """
    args = '<appname appname ...>'

    def handle(self, *app_labels, **options):
        from django.db import models
        if not app_labels:
            raise CommandError('Enter at least one appname.')
        try:
            app_list = [models.get_app(app_label) for app_label in app_labels]
        except (ImproperlyConfigured, ImportError), e:
            raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e)
        output = []
        for app in app_list:
            app_output = self.handle_app(app, **options)
            if app_output:
                output.append(app_output)
        return '\n'.join(output)

    def handle_app(self, app, **options):
        """
        Perform the command's actions for ``app``, which will be the
        Python module corresponding to an application name given on
        the command line.

        """
        raise NotImplementedError()

class LabelCommand(BaseCommand):
    """
    A management command which takes one or more arbitrary arguments
    (labels) on the command line, and does something with each of
    them.

    Rather than implementing ``handle()``, subclasses must implement
    ``handle_label()``, which will be called once for each label.

    If the arguments should be names of installed applications, use
    ``AppCommand`` instead.

    """
    args = '<label label ...>'
    label = 'label'

    def handle(self, *labels, **options):
        if not labels:
            raise CommandError('Enter at least one %s.' % self.label)

        output = []
        for label in labels:
            label_output = self.handle_label(label, **options)
            if label_output:
                output.append(label_output)
        return '\n'.join(output)

    def handle_label(self, label, **options):
        """
        Perform the command's actions for ``label``, which will be the
        string as given on the command line.

        """
        raise NotImplementedError()

class NoArgsCommand(BaseCommand):
    """
    A command which takes no arguments on the command line.

    Rather than implementing ``handle()``, subclasses must implement
    ``handle_noargs()``; ``handle()`` itself is overridden to ensure
    no arguments are passed to the command.

    Attempting to pass arguments will raise ``CommandError``.

    """
    args = ''

    def handle(self, *args, **options):
        if args:
            raise CommandError("Command doesn't accept any arguments")
        return self.handle_noargs(**options)

    def handle_noargs(self, **options):
        """
        Perform this command's actions.

        """
        raise NotImplementedError()

def copy_helper(style, app_or_project, name, directory, other_name=''):
    """
    Copies either a Django application layout template or a Django project
    layout template into the specified directory.

    """
    # style -- A color style object (see django.core.management.color).
    # app_or_project -- The string 'app' or 'project'.
    # name -- The name of the application or project.
    # directory -- The directory to which the layout template should be copied.
    # other_name -- When copying an application layout, this should be the name
    #               of the project.
    import re
    import shutil
    other = {'project': 'app', 'app': 'project'}[app_or_project]
    if not re.search(r'^[_a-zA-Z]\w*$', name): # If it's not a valid directory name.
        # Provide a smart error message, depending on the error.
        if not re.search(r'^[_a-zA-Z]', name):
            message = 'make sure the name begins with a letter or underscore'
        else:
            message = 'use only numbers, letters and underscores'
        raise CommandError("%r is not a valid %s name. Please %s." % (name, app_or_project, message))
    top_dir = os.path.join(directory, name)
    try:
        os.mkdir(top_dir)
    except OSError, e:
        raise CommandError(e)

    # Determine where the app or project templates are. Use
    # django.__path__[0] because we don't know into which directory
    # django has been installed.
    template_dir = os.path.join(django.__path__[0], 'conf', '%s_template' % app_or_project)

    for d, subdirs, files in os.walk(template_dir):
        relative_dir = d[len(template_dir)+1:].replace('%s_name' % app_or_project, name)
        if relative_dir:
            os.mkdir(os.path.join(top_dir, relative_dir))
        for subdir in subdirs[:]:
            if subdir.startswith('.'):
                subdirs.remove(subdir)
        for f in files:
            if not f.endswith('.py'):
                # Ignore .pyc, .pyo, .py.class etc, as they cause various
                # breakages.
                continue
            path_old = os.path.join(d, f)
            path_new = os.path.join(top_dir, relative_dir, f.replace('%s_name' % app_or_project, name))
            fp_old = open(path_old, 'r')
            fp_new = open(path_new, 'w')
            fp_new.write(fp_old.read().replace('{{ %s_name }}' % app_or_project, name).replace('{{ %s_name }}' % other, other_name))
            fp_old.close()
            fp_new.close()
            try:
                shutil.copymode(path_old, path_new)
                _make_writeable(path_new)
            except OSError:
                sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new))

def _make_writeable(filename):
    """
    Make sure that the file is writeable. Useful if our source is
    read-only.

    """
    import stat
    if sys.platform.startswith('java'):
        # On Jython there is no os.access()
        return
    if not os.access(filename, os.W_OK):
        st = os.stat(filename)
        new_permissions = stat.S_IMODE(st.st_mode) | stat.S_IWUSR
        os.chmod(filename, new_permissions)
<|endoftext|>


<commit_before>import random

from app.table import Table
from collections import namedtuple

PULMONIC = Table('app/data/pulmonic.csv')
VOWELS = Table('app/data/vowels.csv')

# A Rule is a namedtuple with the following fields:
#     name: a string representing the process' name
#     target: a string representing the process' target
#     replacement: a string representing the process' replacement
#     changes: a dictionary of sound changes
#     environments: a list of environments to apply the rule to
Rule = namedtuple('Rule', ['name', 'target', 'replacement', 'changes', 'environments'])

def combine_lists(list_1, list_2):
    '''Combines two lists into a dictionary, ensuring that any pair that contains a
    '' is removed.
    '''
    pairs = [pair for pair in zip(list_1, list_2) if pair[0] != '' and pair[1] != '']
    return dict(pairs)

##############################################################################
## CONSONANTS
##############################################################################
def palatalization():
    changes = {}

    for plosive in PULMONIC['plosive']:
        if plosive != '':
            changes[plosive] = plosive + 'ʲ'

    return Rule('palatalization', 'plosive', 'palatalized plosive',
                 changes,
                 ['.(?:i|y|e|ø|j)', '(?:i|y|e|ø|j).'])

def velarization():
    return Rule('velarization', 'clear l', 'dark l',
                {'l': 'ɬ'},
                ['V.', '{back}.'])

def labialization():
    changes = {}
    for phoneme in PULMONIC['plosive']:
        if phoneme != '':
            changes[phoneme] = phoneme + 'ʷ'

    return Rule('labialization', 'plosive', 'labialized plosive',
                changes,
                ['{rounded}.', '.{rounded}'])

def dentalization():
    changes = {}
    for phoneme in ['d', 't', 'n', 'r', 'ɾ']:
        changes[phoneme] = phoneme + '\u032A'

    return Rule('dentalization', 'alveolar consonant',
                'dentalized alveolar consonant',
                changes,
                ['.(?:θ|ð)'])

def rhotacism():
    return Rule('rhotacism', '[n] and [z]', '[r]',
                {'n': 'r', 'z':'r'},
                ['V.'])

def lambdacism():
    return Rule('lambdacism', '[r]', '[l]',
                {'r': 'l'},
                ['V.', '^.'])

def t_glottalization():
    return Rule('t-glottalization', '[t]', '[ʔ]',
                {'t': 'ʔ'},
                ['.C'])

##############################################################################
## CONSONANTS - LENITION
##############################################################################
def flapping():
    return Rule('flapping', '[t] and [d]', 'flap',
                {'t': 'ɾ', 'd': 'ɾ'},
                ['^.', 'V.V'])

def consonant_nasalization():
    changes = combine_lists(PULMONIC['plosive'], PULMONIC['nasal'])

    return Rule('nasalization', 'plosive', 'nasal',
                changes,
                ['V.V'])

##############################################################################
## CONSONANTS - LENITION - SONORIZATION
##############################################################################
def voicing():
    return Rule('voicing', 'unvoiced plosive', 'voiced plosive',
                {'p': 'b', 't': 'd', 'ʈ': 'ɖ', 'c': 'ɟ', 'k': 'g', 'q': 'ɢ'},
                ['^.', 'V.V', '.$'])

def lateral_vocalization():
    return Rule('vocalization', 'lateral approximant', 'semivowel',
                {'l': 'j', 'ɫ': 'w'},
                ['^.', 'V.V', '.$'])

def palatal_vocalization():
    return Rule('vocalization', 'palatal approximant', 'front vowel',
                {'j': 'i'},
                ['^.', 'V.V', '.$'])

def approximation():
    changes = {}

    for fricative, approximant in zip(PULMONIC['fricative'], PULMONIC['approximant']):
        if fricative != '':
            if approximant == '':
                changes[fricative] = fricative + '\u031E'
            else:
                changes[fricative] = approximant

    return Rule('approximation', 'continuant', 'approximant',
                changes,
                ['^.', 'V.V', '.$'])

##############################################################################
## CONSONANTS - LENITION - OPENING
##############################################################################
def degemination():
    changes = {}
    for phoneme in PULMONIC['plosive']:
        if phoneme != '':
            changes[phoneme + phoneme] = phoneme
            changes[phoneme + phoneme + 'ʰ'] = phoneme + 'ʰ'

    return Rule('degemination', 'geminated plosive', 'plosive',
                changes,
                ['^.', 'V.V'])

def spirantization():
    changes = combine_lists(PULMONIC['plosive'], PULMONIC['fricative'])

    return Rule('spirantization', 'plosive', 'fricative',
                changes,
                ['^.', 'V.V'])

def debuccalization():
    fricatives = PULMONIC['fricative']
    fricatives.remove('h')
    changes = combine_lists(fricatives, ['h'] * len(fricatives))
    return Rule('dubuccalization', 'fricative', 'placeless approximant',
                changes,
                ['^.', 'V.V', '.$'])

def affrication():
    changes = {}

    for plosive, affricate in zip(PULMONIC['plosive'], PULMONIC['nonsibilantaffricate']):
        if plosive != '' and affricate != '':
            changes[plosive] = affricate
            changes[plosive + 'ʰ'] = affricate

    changes['t'] = 'ts'
    changes['tʰ'] = 'ts'

    return Rule('affrication', 'plosive', 'affricate',
                changes,
                ['V.V'])

def deaffrication():
    changes = combine_lists(PULMONIC['nonsibilantaffricate'], PULMONIC['fricative'])
    changes['ts'] = 's'

    return Rule('deaffrication', 'affricate', 'fricative',
                changes,
                ['^.', 'V.V', '.$'])

def approximant_elision():
    changes = {}

    for approximant in PULMONIC['approximant']:
        if approximant != '':
            changes[approximant] = ''

    changes['h'] = ''
    changes['ɦ'] = ''

    return Rule('elision', 'approximant', 'nothing',
                changes,
                ['^.', 'V.V', '.$'])

##############################################################################
## CONSONANTS - FORTITION
##############################################################################
def gemination():
    changes = {}
    for phoneme in PULMONIC['plosive']:
        if phoneme != '':
            changes[phoneme] = phoneme + phoneme

    return Rule('gemination', 'plosive', 'reduplicated plosive',
                changes,
                ['V.V'])

def despirantization():
    changes = combine_lists(PULMONIC['fricative'], PULMONIC['plosive'])

    return Rule('despirantization', 'fricative', 'plosive',
                changes,
                ['^.', 'V.V'])

def consonant_denasalization():
    changes = combine_lists(PULMONIC['nasal'], PULMONIC['plosive'])

    return Rule('denasalization', 'nasal', 'plosive',
                changes,
                ['V.V'])

def devoicing():
    return Rule('devoicing', 'voiced plosive', 'unvoiced plosive',
                {'b': 'p', 'd': 't', 'ɖ': 'ʈ', 'ɟ': 'c', 'g': 'k', 'ɢ': 'q'},
                ['.$'])

##############################################################################
## VOWELS
##############################################################################
def vowel_nasalization():
    changes = {}

    for vowel in VOWELS.members():
        changes[vowel] = vowel + '\u0303'

    return Rule('nasalization', 'vowel', 'nasal vowel',
                changes,
                ['{nasal}.'])

def raising():
    changes = combine_lists(VOWELS['closemid'], VOWELS['close'])
    changes.update(combine_lists(VOWELS['openmid'], VOWELS['closemid']))
    changes.update(combine_lists(VOWELS['open'], VOWELS['openmid']))

    return Rule('raising', 'vowel', 'raised vowel',
                changes,
                ['C.C', 'C.', '.C'])

def lowering():
    changes = combine_lists(VOWELS['close'], VOWELS['closemid'])
    changes.update(combine_lists(VOWELS['closemid'], VOWELS['openmid']))
    changes.update(combine_lists(VOWELS['openmid'], VOWELS['open']))

    return Rule('lowering', 'vowel', 'lowered vowel',
                changes,
                ['C.C', 'C.', '.C'])

def fronting():
    changes = {}
    changes.update(combine_lists(VOWELS['uback'], VOWELS['ucentral']))
    changes.update(combine_lists(VOWELS['rback'], VOWELS['rcentral']))
    changes.update(combine_lists(VOWELS['ucentral'], VOWELS['ufront']))
    changes.update(combine_lists(VOWELS['rcentral'], VOWELS['rfront']))
    changes.update(combine_lists(VOWELS['rcentralback'], VOWELS['rfrontcentral']))

    return Rule('fronting', 'vowel', 'fronted vowel',
                changes,
                ['^.', '.$', 'C.C', 'C.', '.C', '.(?:i|j)'])

def backing():
    changes = {}
    changes.update(combine_lists(VOWELS['ucentral'], VOWELS['uback']))
    changes.update(combine_lists(VOWELS['rcentral'], VOWELS['rback']))
    changes.update(combine_lists(VOWELS['ufront'], VOWELS['ucentral']))
    changes.update(combine_lists(VOWELS['rfront'], VOWELS['rcentral']))
    changes.update(combine_lists(VOWELS['rfrontcentral'], VOWELS['rcentralback']))

    return Rule('backing', 'vowel', 'backed vowel',
                changes,
                ['^.', '.$', 'C.C', 'C.', '.C'])

def rounding():
    changes = {}
    changes.update(combine_lists(VOWELS['ufront'], VOWELS['rfront']))
    changes.update(combine_lists(VOWELS['ufrontcentral'], VOWELS['rfrontcentral']))
    changes.update(combine_lists(VOWELS['ucentral'], VOWELS['rcentral']))
    changes.update(combine_lists(VOWELS['ucentralback'], VOWELS['rcentralback']))
    changes.update(combine_lists(VOWELS['uback'], VOWELS['rback']))

    return Rule('rounding', 'vowel', 'rounded vowel',
                changes,
                ['^.', '.C'])

rules = [voicing(),
         devoicing(),
         palatalization(),
         velarization(),
         labialization(),
         dentalization(),
         rhotacism(),
         lambdacism(),
         degemination(),
         spirantization(),
         debuccalization(),
         gemination(),
         lateral_vocalization(),
         palatal_vocalization(),
         approximation(),
         flapping(),
         despirantization(),
         affrication(),
         deaffrication(),
         t_glottalization(),
         consonant_nasalization(),
         consonant_denasalization(),
         approximant_elision(),
         raising(),
         lowering(),
         fronting(),
         backing(),
         rounding(),
         vowel_nasalization()]
<commit_msg>Unrounding rule<commit_after>import random

from app.table import Table
from collections import namedtuple

PULMONIC = Table('app/data/pulmonic.csv')
VOWELS = Table('app/data/vowels.csv')

# A Rule is a namedtuple with the following fields:
#     name: a string representing the process' name
#     target: a string representing the process' target
#     replacement: a string representing the process' replacement
#     changes: a dictionary of sound changes
#     environments: a list of environments to apply the rule to
Rule = namedtuple('Rule', ['name', 'target', 'replacement', 'changes', 'environments'])

def combine_lists(list_1, list_2):
    '''Combines two lists into a dictionary, ensuring that any pair that contains a
    '' is removed.
    '''
    pairs = [pair for pair in zip(list_1, list_2) if pair[0] != '' and pair[1] != '']
    return dict(pairs)

##############################################################################
## CONSONANTS
##############################################################################
def palatalization():
    changes = {}

    for plosive in PULMONIC['plosive']:
        if plosive != '':
            changes[plosive] = plosive + 'ʲ'

    return Rule('palatalization', 'plosive', 'palatalized plosive',
                 changes,
                 ['.(?:i|y|e|ø|j)', '(?:i|y|e|ø|j).'])

def velarization():
    return Rule('velarization', 'clear l', 'dark l',
                {'l': 'ɬ'},
                ['V.', '{back}.'])

def labialization():
    changes = {}
    for phoneme in PULMONIC['plosive']:
        if phoneme != '':
            changes[phoneme] = phoneme + 'ʷ'

    return Rule('labialization', 'plosive', 'labialized plosive',
                changes,
                ['{rounded}.', '.{rounded}'])

def dentalization():
    changes = {}
    for phoneme in ['d', 't', 'n', 'r', 'ɾ']:
        changes[phoneme] = phoneme + '\u032A'

    return Rule('dentalization', 'alveolar consonant',
                'dentalized alveolar consonant',
                changes,
                ['.(?:θ|ð)'])

def rhotacism():
    return Rule('rhotacism', '[n] and [z]', '[r]',
                {'n': 'r', 'z':'r'},
                ['V.'])

def lambdacism():
    return Rule('lambdacism', '[r]', '[l]',
                {'r': 'l'},
                ['V.', '^.'])

def t_glottalization():
    return Rule('t-glottalization', '[t]', '[ʔ]',
                {'t': 'ʔ'},
                ['.C'])

##############################################################################
## CONSONANTS - LENITION
##############################################################################
def flapping():
    return Rule('flapping', '[t] and [d]', 'flap',
                {'t': 'ɾ', 'd': 'ɾ'},
                ['^.', 'V.V'])

def consonant_nasalization():
    changes = combine_lists(PULMONIC['plosive'], PULMONIC['nasal'])

    return Rule('nasalization', 'plosive', 'nasal',
                changes,
                ['V.V'])

##############################################################################
## CONSONANTS - LENITION - SONORIZATION
##############################################################################
def voicing():
    return Rule('voicing', 'unvoiced plosive', 'voiced plosive',
                {'p': 'b', 't': 'd', 'ʈ': 'ɖ', 'c': 'ɟ', 'k': 'g', 'q': 'ɢ'},
                ['^.', 'V.V', '.$'])

def lateral_vocalization():
    return Rule('vocalization', 'lateral approximant', 'semivowel',
                {'l': 'j', 'ɫ': 'w'},
                ['^.', 'V.V', '.$'])

def palatal_vocalization():
    return Rule('vocalization', 'palatal approximant', 'front vowel',
                {'j': 'i'},
                ['^.', 'V.V', '.$'])

def approximation():
    changes = {}

    for fricative, approximant in zip(PULMONIC['fricative'], PULMONIC['approximant']):
        if fricative != '':
            if approximant == '':
                changes[fricative] = fricative + '\u031E'
            else:
                changes[fricative] = approximant

    return Rule('approximation', 'continuant', 'approximant',
                changes,
                ['^.', 'V.V', '.$'])

##############################################################################
## CONSONANTS - LENITION - OPENING
##############################################################################
def degemination():
    changes = {}
    for phoneme in PULMONIC['plosive']:
        if phoneme != '':
            changes[phoneme + phoneme] = phoneme
            changes[phoneme + phoneme + 'ʰ'] = phoneme + 'ʰ'

    return Rule('degemination', 'geminated plosive', 'plosive',
                changes,
                ['^.', 'V.V'])

def spirantization():
    changes = combine_lists(PULMONIC['plosive'], PULMONIC['fricative'])

    return Rule('spirantization', 'plosive', 'fricative',
                changes,
                ['^.', 'V.V'])

def debuccalization():
    fricatives = PULMONIC['fricative']
    fricatives.remove('h')
    changes = combine_lists(fricatives, ['h'] * len(fricatives))
    return Rule('dubuccalization', 'fricative', 'placeless approximant',
                changes,
                ['^.', 'V.V', '.$'])

def affrication():
    changes = {}

    for plosive, affricate in zip(PULMONIC['plosive'], PULMONIC['nonsibilantaffricate']):
        if plosive != '' and affricate != '':
            changes[plosive] = affricate
            changes[plosive + 'ʰ'] = affricate

    changes['t'] = 'ts'
    changes['tʰ'] = 'ts'

    return Rule('affrication', 'plosive', 'affricate',
                changes,
                ['V.V'])

def deaffrication():
    changes = combine_lists(PULMONIC['nonsibilantaffricate'], PULMONIC['fricative'])
    changes['ts'] = 's'

    return Rule('deaffrication', 'affricate', 'fricative',
                changes,
                ['^.', 'V.V', '.$'])

def approximant_elision():
    changes = {}

    for approximant in PULMONIC['approximant']:
        if approximant != '':
            changes[approximant] = ''

    changes['h'] = ''
    changes['ɦ'] = ''

    return Rule('elision', 'approximant', 'nothing',
                changes,
                ['^.', 'V.V', '.$'])

##############################################################################
## CONSONANTS - FORTITION
##############################################################################
def gemination():
    changes = {}
    for phoneme in PULMONIC['plosive']:
        if phoneme != '':
            changes[phoneme] = phoneme + phoneme

    return Rule('gemination', 'plosive', 'reduplicated plosive',
                changes,
                ['V.V'])

def despirantization():
    changes = combine_lists(PULMONIC['fricative'], PULMONIC['plosive'])

    return Rule('despirantization', 'fricative', 'plosive',
                changes,
                ['^.', 'V.V'])

def consonant_denasalization():
    changes = combine_lists(PULMONIC['nasal'], PULMONIC['plosive'])

    return Rule('denasalization', 'nasal', 'plosive',
                changes,
                ['V.V'])

def devoicing():
    return Rule('devoicing', 'voiced plosive', 'unvoiced plosive',
                {'b': 'p', 'd': 't', 'ɖ': 'ʈ', 'ɟ': 'c', 'g': 'k', 'ɢ': 'q'},
                ['.$'])

##############################################################################
## VOWELS
##############################################################################
def vowel_nasalization():
    changes = {}

    for vowel in VOWELS.members():
        changes[vowel] = vowel + '\u0303'

    return Rule('nasalization', 'vowel', 'nasal vowel',
                changes,
                ['{nasal}.'])

def raising():
    changes = combine_lists(VOWELS['closemid'], VOWELS['close'])
    changes.update(combine_lists(VOWELS['openmid'], VOWELS['closemid']))
    changes.update(combine_lists(VOWELS['open'], VOWELS['openmid']))

    return Rule('raising', 'vowel', 'raised vowel',
                changes,
                ['C.C', 'C.', '.C'])

def lowering():
    changes = combine_lists(VOWELS['close'], VOWELS['closemid'])
    changes.update(combine_lists(VOWELS['closemid'], VOWELS['openmid']))
    changes.update(combine_lists(VOWELS['openmid'], VOWELS['open']))

    return Rule('lowering', 'vowel', 'lowered vowel',
                changes,
                ['C.C', 'C.', '.C'])

def fronting():
    changes = {}
    changes.update(combine_lists(VOWELS['uback'], VOWELS['ucentral']))
    changes.update(combine_lists(VOWELS['rback'], VOWELS['rcentral']))
    changes.update(combine_lists(VOWELS['ucentral'], VOWELS['ufront']))
    changes.update(combine_lists(VOWELS['rcentral'], VOWELS['rfront']))
    changes.update(combine_lists(VOWELS['rcentralback'], VOWELS['rfrontcentral']))

    return Rule('fronting', 'vowel', 'fronted vowel',
                changes,
                ['^.', '.$', 'C.C', 'C.', '.C', '.(?:i|j)'])

def backing():
    changes = {}
    changes.update(combine_lists(VOWELS['ucentral'], VOWELS['uback']))
    changes.update(combine_lists(VOWELS['rcentral'], VOWELS['rback']))
    changes.update(combine_lists(VOWELS['ufront'], VOWELS['ucentral']))
    changes.update(combine_lists(VOWELS['rfront'], VOWELS['rcentral']))
    changes.update(combine_lists(VOWELS['rfrontcentral'], VOWELS['rcentralback']))

    return Rule('backing', 'vowel', 'backed vowel',
                changes,
                ['^.', '.$', 'C.C', 'C.', '.C'])

def rounding():
    changes = {}
    changes.update(combine_lists(VOWELS['ufront'], VOWELS['rfront']))
    changes.update(combine_lists(VOWELS['ufrontcentral'], VOWELS['rfrontcentral']))
    changes.update(combine_lists(VOWELS['ucentral'], VOWELS['rcentral']))
    changes.update(combine_lists(VOWELS['ucentralback'], VOWELS['rcentralback']))
    changes.update(combine_lists(VOWELS['uback'], VOWELS['rback']))

    return Rule('rounding', 'vowel', 'rounded vowel',
                changes,
                ['^.', '.C'])

def unrounding():
    changes = {}
    changes.update(combine_lists(VOWELS['rfront'], VOWELS['ufront']))
    changes.update(combine_lists(VOWELS['rfrontcentral'], VOWELS['ufrontcentral']))
    changes.update(combine_lists(VOWELS['rcentral'], VOWELS['ucentral']))
    changes.update(combine_lists(VOWELS['rcentralback'], VOWELS['ucentralback']))
    changes.update(combine_lists(VOWELS['rback'], VOWELS['uback']))

    return Rule('unrounding', 'vowel', 'unrounded vowel',
                changes,
                ['^.', '.$', 'C.'])

rules = [voicing(),
         devoicing(),
         palatalization(),
         velarization(),
         labialization(),
         dentalization(),
         rhotacism(),
         lambdacism(),
         degemination(),
         spirantization(),
         debuccalization(),
         gemination(),
         lateral_vocalization(),
         palatal_vocalization(),
         approximation(),
         flapping(),
         despirantization(),
         affrication(),
         deaffrication(),
         t_glottalization(),
         consonant_nasalization(),
         consonant_denasalization(),
         approximant_elision(),
         raising(),
         lowering(),
         fronting(),
         backing(),
         rounding(),
         unrounding(),
         vowel_nasalization()]
<|endoftext|>