<commit_before># pyinfra
# File: tests/util.py
# Desc: utilities for fake pyinfra state/host objects

from datetime import datetime

import six
from mock import patch

from pyinfra.api import Config, Inventory
from pyinfra.api.attrs import AttrData

class FakeState(object):
    active = True
    deploy_dir = '/'
    in_op = True
    pipelining = False

    def __init__(self):
        self.inventory = Inventory(([], {}))
        self.config = Config()

    def get_temp_filename(*args):
        return '_tempfile_'

def parse_fact(fact):
    if isinstance(fact, six.string_types) and fact.startswith('datetime:'):
        return datetime.strptime(fact[9:], '%Y-%m-%dT%H:%M:%S')

    elif isinstance(fact, list):
        return [parse_fact(value) for value in fact]

    elif isinstance(fact, dict):
        return {
            key: parse_fact(value)
            for key, value in six.iteritems(fact)
        }

    return fact

class FakeFact(object):
    def __init__(self, data):
        self.data = parse_fact(data)

    def __getattr__(self, key):
        return getattr(self.data, key)

    def __getitem__(self, key):
        return self.data[key]

    def __call__(self, *args, **kwargs):
        return self.data[args[0]]

    def __iter__(self):
        return iter(self.data)

    def get(self, key, default=None):
        if key in self.data:
            return self.data[key]

        return default

class FakeFacts(object):
    def __init__(self, facts):
        self.facts = facts

    def __getattr__(self, key):
        if self.facts[key] is None:
            return None
        else:
            return FakeFact(self.facts[key])

class FakeHost(object):
    def __init__(self, name, facts, data):
        self.name = name
        self.fact = FakeFacts(facts)
        self.data = AttrData(data)

class FakeFile(object):
    _read = False
    _data = None

    def __init__(self, name, data=None):
        self._name = name
        self._data = data

    def read(self, *args, **kwargs):
        if self._read is False:
            self._read = True

            if self._data:
                return self._data
            else:
                return '_test_data_'

        return ''

    def readlines(self, *args, **kwargs):
        if self._read is False:
            self._read = True

            if self._data:
                return self._data.split()
            else:
                return ['_test_data_']

            return []

    def seek(self, *args, **kwargs):
        pass

    def close(self, *args, **kwargs):
        pass

    def __enter__(self, *args, **kwargs):
        return self

    def __exit__(self, *args, **kwargs):
        pass

class PatchFiles(object):
    def __init__(self, patch_files):
        files = []
        files_data = {}

        for filename_data in patch_files:
            if isinstance(filename_data, list):
                filename, data = filename_data
            else:
                filename = filename_data
                data = None

            filename = '{0}{1}'.format(FakeState.deploy_dir, filename)
            files.append(filename)

            if data:
                files_data[filename] = data

        self.files = files
        self.files_data = files_data

    def __enter__(self):
        self.patches = [
            patch(
                'pyinfra.modules.files.path.exists',
                lambda *args, **kwargs: True, create=True
            ),
            patch('pyinfra.modules.files.open', self.get_file, create=True),
            patch('pyinfra.api.util.open', self.get_file, create=True),
        ]

        for patched in self.patches:
            patched.start()

    def __exit__(self, type_, value, traceback):
        for patched in self.patches:
            patched.stop()

    def get_file(self, filename, *args):
        if filename in self.files:
            return FakeFile(filename, self.files_data.get(filename))

        raise IOError('Missing FakeFile: {0}'.format(filename))

def patch_files(files=None):
    return PatchFiles(files)

def create_host(name=None, facts=None, data=None):
    '''
    Creates a FakeHost object with attached fact data.
    '''

    real_facts = {}
    facts = facts or {}

    for name, fact_data in six.iteritems(facts):
            real_facts[name] = fact_data

    return FakeHost(name, facts=real_facts, data=data)
<commit_msg>Remove silly nested function -> class call.<commit_after># pyinfra
# File: tests/util.py
# Desc: utilities for fake pyinfra state/host objects

from datetime import datetime

import six
from mock import patch

from pyinfra.api import Config, Inventory
from pyinfra.api.attrs import AttrData

class FakeState(object):
    active = True
    deploy_dir = '/'
    in_op = True
    pipelining = False

    def __init__(self):
        self.inventory = Inventory(([], {}))
        self.config = Config()

    def get_temp_filename(*args):
        return '_tempfile_'

def parse_fact(fact):
    if isinstance(fact, six.string_types) and fact.startswith('datetime:'):
        return datetime.strptime(fact[9:], '%Y-%m-%dT%H:%M:%S')

    elif isinstance(fact, list):
        return [parse_fact(value) for value in fact]

    elif isinstance(fact, dict):
        return {
            key: parse_fact(value)
            for key, value in six.iteritems(fact)
        }

    return fact

class FakeFact(object):
    def __init__(self, data):
        self.data = parse_fact(data)

    def __getattr__(self, key):
        return getattr(self.data, key)

    def __getitem__(self, key):
        return self.data[key]

    def __call__(self, *args, **kwargs):
        return self.data[args[0]]

    def __iter__(self):
        return iter(self.data)

    def get(self, key, default=None):
        if key in self.data:
            return self.data[key]

        return default

class FakeFacts(object):
    def __init__(self, facts):
        self.facts = facts

    def __getattr__(self, key):
        if self.facts[key] is None:
            return None
        else:
            return FakeFact(self.facts[key])

class FakeHost(object):
    def __init__(self, name, facts, data):
        self.name = name
        self.fact = FakeFacts(facts)
        self.data = AttrData(data)

class FakeFile(object):
    _read = False
    _data = None

    def __init__(self, name, data=None):
        self._name = name
        self._data = data

    def read(self, *args, **kwargs):
        if self._read is False:
            self._read = True

            if self._data:
                return self._data
            else:
                return '_test_data_'

        return ''

    def readlines(self, *args, **kwargs):
        if self._read is False:
            self._read = True

            if self._data:
                return self._data.split()
            else:
                return ['_test_data_']

            return []

    def seek(self, *args, **kwargs):
        pass

    def close(self, *args, **kwargs):
        pass

    def __enter__(self, *args, **kwargs):
        return self

    def __exit__(self, *args, **kwargs):
        pass

class patch_files(object):
    def __init__(self, patch_files):
        files = []
        files_data = {}

        for filename_data in patch_files:
            if isinstance(filename_data, list):
                filename, data = filename_data
            else:
                filename = filename_data
                data = None

            filename = '{0}{1}'.format(FakeState.deploy_dir, filename)
            files.append(filename)

            if data:
                files_data[filename] = data

        self.files = files
        self.files_data = files_data

    def __enter__(self):
        self.patches = [
            patch(
                'pyinfra.modules.files.path.exists',
                lambda *args, **kwargs: True, create=True
            ),
            patch('pyinfra.modules.files.open', self.get_file, create=True),
            patch('pyinfra.api.util.open', self.get_file, create=True),
        ]

        for patched in self.patches:
            patched.start()

    def __exit__(self, type_, value, traceback):
        for patched in self.patches:
            patched.stop()

    def get_file(self, filename, *args):
        if filename in self.files:
            return FakeFile(filename, self.files_data.get(filename))

        raise IOError('Missing FakeFile: {0}'.format(filename))

def create_host(name=None, facts=None, data=None):
    '''
    Creates a FakeHost object with attached fact data.
    '''

    real_facts = {}
    facts = facts or {}

    for name, fact_data in six.iteritems(facts):
            real_facts[name] = fact_data

    return FakeHost(name, facts=real_facts, data=data)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
#
# This file is part of pypuppetdbquery.
# Copyright © 2016  Chris Boot <bootc@bootc.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import unittest

class CompatTestCase(unittest.TestCase):
    if not hasattr(unittest.TestCase, 'assertIsInstance'):
        def assertIsInstance(self, obj, cls, msg=None):
            if not isinstance(obj, cls):
                standardMsg = '%s is not an instance of %r' \
                    % (unittest.safe_repr(obj), cls)
                self.fail(self._formatMessage(msg, standardMsg))
<commit_msg>Also add assertIsNone() for Python 2.6<commit_after># -*- coding: utf-8 -*-
#
# This file is part of pypuppetdbquery.
# Copyright © 2016  Chris Boot <bootc@bootc.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import unittest

class CompatTestCase(unittest.TestCase):
    if not hasattr(unittest.TestCase, 'assertIsInstance'):
        def assertIsInstance(self, obj, cls, msg=None):
            if not isinstance(obj, cls):
                standardMsg = '%s is not an instance of %r' \
                    % (unittest.safe_repr(obj), cls)
                self.fail(self._formatMessage(msg, standardMsg))

    if not hasattr(unittest.TestCase, 'assertIsNone'):
        def assertIsNone(self, obj, msg=None):
            if obj is not None:
                standardMsg = '%s is not None' % (unittest.safe_repr(obj),)
                self.fail(self._formatMessage(msg, standardMsg))
<|endoftext|>


<commit_before>import io
import os.path
from six.moves.urllib.request import urlopen
from sqlalchemy import create_engine

from geopandas import GeoDataFrame, GeoSeries

try:
    from pandas import read_sql_table
except ImportError:
    PANDAS_NEW_SQL_API = False
else:
    PANDAS_NEW_SQL_API = True

# Compatibility layer for Python 2.6: try loading unittest2
import sys
if sys.version_info[:2] == (2, 6):
    try:
        import unittest2 as unittest
    except ImportError:
        import unittest

else:
    import unittest

try:
    import psycopg2
    from psycopg2 import OperationalError
except ImportError:
    class OperationalError(Exception):
        pass

def download_nybb():
    """ Returns the path to the NYC boroughs file. Downloads if necessary. """
    # Data from http://www.nyc.gov/html/dcp/download/bytes/nybb_14aav.zip
    # saved as geopandas/examples/nybb_14aav.zip.
    filename = 'nybb_14aav.zip'
    full_path_name = os.path.join('examples', filename)
    if not os.path.exists(full_path_name):
        with io.open(full_path_name, 'wb') as f:
            response = urlopen('http://www.nyc.gov/html/dcp/download/bytes/{0}'.format(filename))
            f.write(response.read())
    return full_path_name

def validate_boro_df(test, df):
    """ Tests a GeoDataFrame that has been read in from the nybb dataset."""
    test.assertTrue(isinstance(df, GeoDataFrame))
    # Make sure all the columns are there and the geometries
    # were properly loaded as MultiPolygons
    test.assertEqual(len(df), 5)
    columns = ('borocode', 'boroname', 'shape_leng', 'shape_area')
    for col in columns:
        test.assertTrue(col in df.columns, 'Column {0} missing'.format(col))
    test.assertTrue(all(df.geometry.type == 'MultiPolygon'))

def connect(dbname):
    try:
        if PANDAS_NEW_SQL_API:
            driver = 'psycopg2'
            conn_str = 'postgresql+{driver}://localhost/{dbname}'
            engine = create_engine(conn_str.format(driver=driver, dbname=dbname))
            return engine
        else:
            con = psycopg2.connect(dbname=dbname)
            return con
    except (NameError, OperationalError):
        return None

def create_db(df):
    """
    Create a nybb table in the test_geopandas PostGIS database.
    Returns a boolean indicating whether the database table was successfully
    created

    """
    # Try to create the database, skip the db tests if something goes
    # wrong
    # If you'd like these tests to run, create a database called
    # 'test_geopandas' and enable postgis in it:
    # > createdb test_geopandas
    # > psql -c "CREATE EXTENSION postgis" -d test_geopandas
    con = connect('test_geopandas')
    if con is None:
        return False
    if PANDAS_NEW_SQL_API:
        try:
            con = con.connect()
        except OperationalError:
            raise unittest.case.SkipTest()
    else:
        con = con.cursor()

    try:
        con.execute("DROP TABLE IF EXISTS nybb;")

        sql = """CREATE TABLE nybb (
            geom        geometry,
            borocode    integer,
            boroname    varchar(40),
            shape_leng  float,
            shape_area  float
        );"""
        con.execute(sql)

        for i, row in df.iterrows():
            sql = """INSERT INTO nybb VALUES (
                ST_GeometryFromText(%s), %s, %s, %s, %s
            );"""
            con.execute(sql, (row['geometry'].wkt,
                                 row['BoroCode'],
                                 row['BoroName'],
                                 row['Shape_Leng'],
                                 row['Shape_Area']))
    finally:
        con.close()

    return True

def assert_seq_equal(left, right):
    """Poor man's version of assert_almost_equal which isn't working with Shapely
    objects right now"""
    assert len(left) == len(right), "Mismatched lengths: %d != %d" % (len(left), len(right))

    for elem_left, elem_right in zip(left, right):
        assert elem_left == elem_right, "%r != %r" % (left, right)

def geom_equals(this, that):
    """Test for geometric equality. Empty geometries are considered equal.

    Parameters
    ----------
    this, that : arrays of Geo objects (or anything that has an `is_empty`
                 attribute)
    """

    return (this.geom_equals(that) | (this.is_empty & that.is_empty)).all()

def geom_almost_equals(this, that):
    """Test for 'almost' geometric equality. Empty geometries considered equal.

    Parameters
    ----------
    this, that : arrays of Geo objects (or anything that has an `is_empty`
                 property)
    """

    return (this.geom_almost_equals(that) |
            (this.is_empty & that.is_empty)).all()

# TODO: Remove me when standardizing on pandas 0.13, which already includes
#       this test util.
def assert_isinstance(obj, klass_or_tuple):
    assert isinstance(obj, klass_or_tuple), "type: %r != %r" % (
                                           type(obj).__name__,
                                           getattr(klass_or_tuple, '__name__',
                                                   klass_or_tuple))

def assert_geoseries_equal(left, right, check_dtype=False,
                           check_index_type=False,
                           check_series_type=True,
                           check_less_precise=False,
                           check_geom_type=False,
                           check_crs=True):
    """Test util for checking that two GeoSeries are equal.

    Parameters
    ----------
    left, right : two GeoSeries
    check_dtype : bool, default False
        if True, check geo dtype [only included so it's a drop-in replacement
        for assert_series_equal]
    check_index_type : bool, default False
        check that index types are equal
    check_series_type : bool, default True
        check that both are same type (*and* are GeoSeries). If False,
        will attempt to convert both into GeoSeries.
    check_less_precise : bool, default False
        if True, use geom_almost_equals. if False, use geom_equals.
    check_geom_type : bool, default False
        if True, check that all the geom types are equal.
    check_crs: bool, default True
        if check_series_type is True, then also check that the
        crs matches
    """
    assert len(left) == len(right), "%d != %d" % (len(left), len(right))

    if check_index_type:
        assert_isinstance(left.index, type(right.index))

    if check_dtype:
        assert left.dtype == right.dtype, "dtype: %s != %s" % (left.dtype,
                                                               right.dtype)

    if check_series_type:
        assert isinstance(left, GeoSeries)
        assert_isinstance(left, type(right))

        if check_crs:
            assert(left.crs == right.crs)
    else:
        if not isinstance(left, GeoSeries):
            left = GeoSeries(left)
        if not isinstance(right, GeoSeries):
            right = GeoSeries(right, index=left.index)

    assert left.index.equals(right.index), "index: %s != %s" % (left.index,
                                                                right.index)

    if check_geom_type:
        assert (left.type == right.type).all(), "type: %s != %s" % (left.type,
                                                                    right.type)

    if check_less_precise:
        assert geom_almost_equals(left, right)
    else:
        assert geom_equals(left, right)
<commit_msg>TST: Don't raise in utility function<commit_after>import io
import os.path
from six.moves.urllib.request import urlopen
from sqlalchemy import create_engine

from geopandas import GeoDataFrame, GeoSeries

try:
    from pandas import read_sql_table
except ImportError:
    PANDAS_NEW_SQL_API = False
else:
    PANDAS_NEW_SQL_API = True

# Compatibility layer for Python 2.6: try loading unittest2
import sys
if sys.version_info[:2] == (2, 6):
    try:
        import unittest2 as unittest
    except ImportError:
        import unittest

else:
    import unittest

try:
    import psycopg2
    from psycopg2 import OperationalError
except ImportError:
    class OperationalError(Exception):
        pass

def download_nybb():
    """ Returns the path to the NYC boroughs file. Downloads if necessary. """
    # Data from http://www.nyc.gov/html/dcp/download/bytes/nybb_14aav.zip
    # saved as geopandas/examples/nybb_14aav.zip.
    filename = 'nybb_14aav.zip'
    full_path_name = os.path.join('examples', filename)
    if not os.path.exists(full_path_name):
        with io.open(full_path_name, 'wb') as f:
            response = urlopen('http://www.nyc.gov/html/dcp/download/bytes/{0}'.format(filename))
            f.write(response.read())
    return full_path_name

def validate_boro_df(test, df):
    """ Tests a GeoDataFrame that has been read in from the nybb dataset."""
    test.assertTrue(isinstance(df, GeoDataFrame))
    # Make sure all the columns are there and the geometries
    # were properly loaded as MultiPolygons
    test.assertEqual(len(df), 5)
    columns = ('borocode', 'boroname', 'shape_leng', 'shape_area')
    for col in columns:
        test.assertTrue(col in df.columns, 'Column {0} missing'.format(col))
    test.assertTrue(all(df.geometry.type == 'MultiPolygon'))

def connect(dbname):
    try:
        if PANDAS_NEW_SQL_API:
            driver = 'psycopg2'
            conn_str = 'postgresql+{driver}://localhost/{dbname}'
            engine = create_engine(conn_str.format(driver=driver, dbname=dbname))
            return engine
        else:
            con = psycopg2.connect(dbname=dbname)
            return con
    except (NameError, OperationalError):
        return None

def create_db(df):
    """
    Create a nybb table in the test_geopandas PostGIS database.
    Returns a boolean indicating whether the database table was successfully
    created

    """
    # Try to create the database, skip the db tests if something goes
    # wrong
    # If you'd like these tests to run, create a database called
    # 'test_geopandas' and enable postgis in it:
    # > createdb test_geopandas
    # > psql -c "CREATE EXTENSION postgis" -d test_geopandas
    con = connect('test_geopandas')
    if con is None:
        return False
    if PANDAS_NEW_SQL_API:
        try:
            con = con.connect()
        except OperationalError:
            return
    else:
        con = con.cursor()

    try:
        con.execute("DROP TABLE IF EXISTS nybb;")

        sql = """CREATE TABLE nybb (
            geom        geometry,
            borocode    integer,
            boroname    varchar(40),
            shape_leng  float,
            shape_area  float
        );"""
        con.execute(sql)

        for i, row in df.iterrows():
            sql = """INSERT INTO nybb VALUES (
                ST_GeometryFromText(%s), %s, %s, %s, %s
            );"""
            con.execute(sql, (row['geometry'].wkt,
                                 row['BoroCode'],
                                 row['BoroName'],
                                 row['Shape_Leng'],
                                 row['Shape_Area']))
    finally:
        con.close()

    return True

def assert_seq_equal(left, right):
    """Poor man's version of assert_almost_equal which isn't working with Shapely
    objects right now"""
    assert len(left) == len(right), "Mismatched lengths: %d != %d" % (len(left), len(right))

    for elem_left, elem_right in zip(left, right):
        assert elem_left == elem_right, "%r != %r" % (left, right)

def geom_equals(this, that):
    """Test for geometric equality. Empty geometries are considered equal.

    Parameters
    ----------
    this, that : arrays of Geo objects (or anything that has an `is_empty`
                 attribute)
    """

    return (this.geom_equals(that) | (this.is_empty & that.is_empty)).all()

def geom_almost_equals(this, that):
    """Test for 'almost' geometric equality. Empty geometries considered equal.

    Parameters
    ----------
    this, that : arrays of Geo objects (or anything that has an `is_empty`
                 property)
    """

    return (this.geom_almost_equals(that) |
            (this.is_empty & that.is_empty)).all()

# TODO: Remove me when standardizing on pandas 0.13, which already includes
#       this test util.
def assert_isinstance(obj, klass_or_tuple):
    assert isinstance(obj, klass_or_tuple), "type: %r != %r" % (
                                           type(obj).__name__,
                                           getattr(klass_or_tuple, '__name__',
                                                   klass_or_tuple))

def assert_geoseries_equal(left, right, check_dtype=False,
                           check_index_type=False,
                           check_series_type=True,
                           check_less_precise=False,
                           check_geom_type=False,
                           check_crs=True):
    """Test util for checking that two GeoSeries are equal.

    Parameters
    ----------
    left, right : two GeoSeries
    check_dtype : bool, default False
        if True, check geo dtype [only included so it's a drop-in replacement
        for assert_series_equal]
    check_index_type : bool, default False
        check that index types are equal
    check_series_type : bool, default True
        check that both are same type (*and* are GeoSeries). If False,
        will attempt to convert both into GeoSeries.
    check_less_precise : bool, default False
        if True, use geom_almost_equals. if False, use geom_equals.
    check_geom_type : bool, default False
        if True, check that all the geom types are equal.
    check_crs: bool, default True
        if check_series_type is True, then also check that the
        crs matches
    """
    assert len(left) == len(right), "%d != %d" % (len(left), len(right))

    if check_index_type:
        assert_isinstance(left.index, type(right.index))

    if check_dtype:
        assert left.dtype == right.dtype, "dtype: %s != %s" % (left.dtype,
                                                               right.dtype)

    if check_series_type:
        assert isinstance(left, GeoSeries)
        assert_isinstance(left, type(right))

        if check_crs:
            assert(left.crs == right.crs)
    else:
        if not isinstance(left, GeoSeries):
            left = GeoSeries(left)
        if not isinstance(right, GeoSeries):
            right = GeoSeries(right, index=left.index)

    assert left.index.equals(right.index), "index: %s != %s" % (left.index,
                                                                right.index)

    if check_geom_type:
        assert (left.type == right.type).all(), "type: %s != %s" % (left.type,
                                                                    right.type)

    if check_less_precise:
        assert geom_almost_equals(left, right)
    else:
        assert geom_equals(left, right)
<|endoftext|>


<commit_before>tetrominos = {
  ('I', 0):
    (0,
    [[1],
     [1],
     [1],
     [1]]),
  ('I', 1):
    (0,
    [[1, 1, 1, 1]]),
  ('I', 2): ('!', 'Invalid'),
  ('I', 3): ('!', 'Invalid'),
#----------------------------
  ('J', 0):
    (1,
    [[0, 1],
     [0, 1],
     [1, 1]]),
  ('J', 1):
    (0,
    [[1, 0, 0],
     [1, 1, 1]]),
  ('J', 2):
    (0,
    [[1, 1],
     [1, 0],
     [1, 0]]),
  ('J', 3):
    (0,
    [[1, 1, 1],
     [0, 0, 1]]),
#----------------------------
  ('L', 0):
    (0,
    [[1, 0],
     [1, 0],
     [1, 1]]),
  ('L', 1):
    (0,
    [[1, 1, 1],
     [1, 0, 0]]),
  ('L', 2):
    (0,
    [[1, 1],
     [0, 1],
     [0, 1]]),
  ('L', 3):
    (2,
    [[0, 0, 1],
     [1, 1, 1]]),
#----------------------------
  ('O', 0):
    (0,
    [[1, 1],
     [1, 1]]),
  ('O', 1): ('!', 'Invalid'),
  ('O', 2): ('!', 'Invalid'),
  ('O', 3): ('!', 'Invalid'),
#----------------------------
  ('S', 0):
    (1,
    [[0, 1, 1],
     [1, 1, 0]]),
  ('S', 1):
    (0,
    [[1, 0],
     [1, 1],
     [0, 1]]),
  ('S', 2): ('!', 'Invalid'),
  ('S', 3): ('!', 'Invalid'),
#----------------------------
  ('T', 0):
    (0,
    [[1, 1, 1],
     [0, 1, 0]]),
  ('T', 1):
    (1,
    [[0, 1],
     [1, 1],
     [0, 1]]),
  ('T', 2):
    (1,
    [[0, 1, 0],
     [1, 1, 1]]),
  ('T', 3):
    (0,
    [[1, 0],
     [1, 1],
     [1, 0]]),
#----------------------------
  ('Z', 0):
    (0,
    [[1, 1, 0],
     [0, 1, 1]]),
  ('Z', 1):
    (1,
    [[0, 1],
     [1, 1],
     [1, 0]]),
  ('Z', 2): ('!', 'Invalid'),
  ('Z', 3): ('!', 'Invalid'),
}

def doubleIter(xmax, ymax):
  for x in range(xmax):
    for y in range(ymax):
      yield (x, y)

from threading import Lock, Thread
global uuidlock
uuidlock = Lock()
def getUUID():
  global uuid
  uuidlock.acquire()
  uuid += 1
  newId = uuid
  uuidlock.release()
  return newId

class PartialSolution(Thread):
  def __init__(self, root=None):
    if root != None:
      self.board, self.board_h, self.board_w, self.pieces, self.steps, self.parity, self.parityLimit, self.x, self.y, self.uuid = root
      return
    super(PartialSolution, self).__init__()

  def debug(self):
    print 'UUID:', self.uuid
    print 'Board:'
    self.printBoard()
    print 'Cost:', self.cost
    print 'Pieces:', self.pieces
    print 'Steps:', self.steps

  def clone(self):
    return PartialSolution(root=(
      self.board,
      self.board_h,
      self.board_w,
      list(self.pieces),
      list(self.steps),
      self.parity,
      self.parityLimit,
      self.x,
      self.y,
      self.uuid, # Will be changed later IF the new copy survives
      ))

  def isInvalid(self, x, y):
    if x < 0 or y < 0:
      return True
    if x >= self.board_h or y >= self.board_w:
      return True
    return self.getBoard(x, y)

  def getParity(self, x, y):
    evenOdd = (x+y) % 2
    return 2 * (evenOdd) - 1

  def getBoard(self, x, y):
    mask = 2 << (x*self.board_w + y)
    return self.board & mask == mask

  def setBoard(self, x, y):
    self.board |= 2 << (x*self.board_w + y)

  def setPrintableBoard(self, board, x, y, char):
    if board[x][y] == ' ':
      board[x][y] = char
    elif board[x][y] == '-' and char == '|':
      board[x][y] = '+'
    elif board[x][y] == '|' and char == '-':
      board[x][y] = '+'

  def printBoard(self):
    board2 = [[-1 for _ in range(self.board_w)] for __ in range(self.board_h)]
    x = y = 0
    for pieceNum, rotation in self.steps:
      pieceName, initialRotation = pieces[pieceNum]
      offset, piece = tetrominos[(pieceName, (initialRotation+rotation) % 4)]
      for x, y in doubleIter(self.board_h, self.board_w):
        if board2[x][y] == -1:
          break
      for i, j in doubleIter(len(piece), len(piece[0])):
        if piece[i][j] == 1:
          board2[x+i][y+j-offset] = pieceNum

    board3 = [[' ' for _ in range(self.board_w*10+1)] for __ in range(self.board_h*5+1)]
    # Fixing some corners
    board3[0][self.board_w*10] = '-'
    board3[self.board_h*5][0] = '|'
    board3[self.board_h*5][self.board_w*10] = '+'
    for x in range(len(board2)):
      for y in range(len(board2[x])):
        if x == 0: # Top row
          for j in range(10):
            self.setPrintableBoard(board3, x*5+0, y*10+j, '-')
        elif board2[x][y] != board2[x-1][y]: # Internal row
          for j in range(11):
            self.setPrintableBoard(board3, x*5+0, y*10+j, '-')
        if x == self.board_h-1: # Bottom row
          for j in range(10):
            self.setPrintableBoard(board3, x*5+5, y*10+j, '-')
        if y == 0: # Left column
          for i in range(5):
            self.setPrintableBoard(board3, x*5+i, y*10+0, '|')
        elif board2[x][y] != board2[x][y-1]: # Internal column
          for i in range(6):
            self.setPrintableBoard(board3, x*5+i, y*10+0, '|')
        if y == self.board_w-1: # Right column
          for i in range(5):
            self.setPrintableBoard(board3, x*5+i, y*10+10, '|')

    for line in board3:
      print ''.join(line)

  def run(self):
    while True:
      try:
        _cost, self = q.get(True, 1)
        self.cost = _cost
      except Empty:
        return
      global maxCost
      if self.cost > maxCost and maxCost != -1:
        continue
      if len(self.pieces) == len(self.steps):
        global solutions
        if len(solutions) == 0:
          maxCost = self.cost
        if len(solutions) == MAXSOLNS:
          return
        solutions.append(self)
        if len(solutions) == MAXSOLNS: # Allows the -1/0 logic below.
          return
        q.task_done()
        continue

      # Locating the first available space
      for self.x, self.y in doubleIter(self.board_h, self.board_w):
        if self.getBoard(self.x, self.y):
          continue
        break
      self.attempted_pieces = []
      for rotation in range(0, 4):
        for pieceNum in range(len(self.pieces)):
          if self.pieces[pieceNum] == None:
            continue
          pieceName, initialRotation = self.pieces[pieceNum]
          offset, piece = tetrominos[(pieceName, (initialRotation+rotation) % 4)]
          # Avoiding duplicates
          if (pieceName, (initialRotation+rotation) % 4) in self.attempted_pieces:
            continue
          self.attempted_pieces.append((pieceName, (initialRotation+rotation) % 4))
          if piece == 'Invalid': # For pieces like the O which cannot be rotated further
            continue
          newSolution = self.clone()
          # T pieces have a kind of parity. Consider the board to be a checkerboard,
          # then a T piece covers 3 black and 1 white squares, whereas all other pieces
          # cover 2 and 2. Thus, you must have an even number of T pieces AND
          # exactly half must be placed on white and half on black.
          if pieceName == 'T':
            parity = self.getParity(self.x, self.y)
            if abs(self.parity) == self.parityLimit and parity * self.parity > 0:
              continue # At parity limit and this placement only increases said limit
            newSolution.parity += parity

          invalidPlacement = False
          for i, j in doubleIter(len(piece), len(piece[0])):
            if piece[i][j] == 1:
              # Bounds check and Collision
              if self.isInvalid(self.x+i, self.y+j-offset):
                invalidPlacement = True
                break
              newSolution.setBoard(self.x+i, self.y+j-offset)
          # Checking for 1x1 holes in the next row is inefficient, as it happens very rarely (1-2%).
          # if self.x < self.board_h:
          #   for j in range(1, self.board_w-1):
          #     if (self.getBoard(self.x+1, j-1), self.getBoard(self.x+1, j), self.getBoard(self.x+1, j+1)) == (True, False, True):
          #       if self.isInvalid(self.x+2, j):
          #           invalidPlacement = True
          #           break
          if invalidPlacement:
            continue
          newSolution.uuid = getUUID()
          newSolution.pieces[pieceNum] = None
          newSolution.steps.append((pieceNum, rotation))
          q.put((self.cost + rotation, newSolution))
      q.task_done()

challenges = {
  # 'Name': ['Pieces', Height, Width],
  'Connector':  ['T0, T0, L1', 3, 4], # 3
  'A':          ['I1, L1, J1, Z0', 4, 4], # 1
  'Cube':       ['T0, T0, L1, Z0', 4, 4], # 4
  'Floor 1':    ['L1, Z0, L1, Z0', 4, 4], # 4
  'Recorder':   ['T0, T0, J1, S0, Z0', 5, 4], # 3
  'Fan':        ['T0, T0, L1, S0, Z0', 5, 4], # 4
  'B':          ['I1, T0, T0, L1, Z0', 5, 4], # 5
  'C':          ['T0, T0, J1, J1, L1, Z0', 6, 4], # 2
  'Platform':   ['I1, S0, T0, T0, L1, Z0', 6, 4], # 5
  'Floor 6':    ['O0, S0, S0, S0, S0, L0, L0, L0, L0', 6, 6], # 8
  'Floor 2':    ['O0, T0, T0, T0, T0, L1, L1, L1, L1', 6, 6], # 7
  'A star':     ['T0, T0, T0, T0, L1, J1, S0, S0, Z0, Z0', 5, 8], # 6
  'B star':     ['I1, I1, O0, T0, T0, T0, T0, L1, L1, J1', 5, 8], # 3
  'C star':     ['L1, J1, S0, Z0, T0, T0, I1, I1, O0, O0', 5, 8], # 2
  'Floor 3':    ['I1, I1, I1, I1, J1, J1, L1, L1, S0, Z0', 5, 8], # 3
  'Floor 4':    ['O0, O0, T0, T0, T0, T0, J1, L1, S0, S0, Z0, Z0', 8, 6], # 4
  'Floor 5':    ['I1, I1, O0, O0, O0, O0, T0, T0, T0, T0, J1, L1, S0, Z0', 7, 8], # 4
}

NUMTHREADS = 16
MAXSOLNS = 1 # Set to -1 for all solutions. Set to 0 to calculate only cost.
from Queue import PriorityQueue, Empty
from time import time

lock = Lock()
for title in challenges.keys():
  data = challenges[title]
  TCount = 0
  pieces = data[0].split(', ')
  for i in range(len(pieces)):
    if pieces[i][0] == 'T':
      TCount += 1
    pieces[i] = (pieces[i][0], int(pieces[i][1]))
  global maxCost
  maxCost = -1
  global solutions
  solutions = []
  global uuid
  uuid = 0
  print 'Challenge "{name}" using {num} pieces: {pieces}'.format(name=title, num = len(pieces), pieces=pieces)
  startTime = time()

  q = PriorityQueue()
  # cost, PartialSolution(root=(board, board_h, board_w, pieces, steps, parity, parityLimit, x, y, uuid))
  q.put((0, PartialSolution(root=(0L, data[1], data[2], list(pieces), [], 0, TCount/2, 0, 0, 0))))
  threads = []
  for i in range(NUMTHREADS):
    thread = PartialSolution()
    thread.daemon = True
    threads.append(thread)
    thread.start()
  for thread in threads:
    thread.join()
  print 'Took {time} seconds using {partials} partials.'.format(time=time()-startTime, partials = uuid)
  print 'Found {num} solution{s} with {cost} rotations:'.format(num=len(solutions), s=('s' if len(solutions) != 1 else ''), cost=maxCost)
  for solution in solutions:
    solution.printBoard()
    print
<commit_msg>Removed parity, improved board printout, added error checking<commit_after>tetrominos = {
  ('I', 0):
    (0,
    [[1],
     [1],
     [1],
     [1]]),
  ('I', 1):
    (0,
    [[1, 1, 1, 1]]),
  ('I', 2): ('!', 'Invalid'),
  ('I', 3): ('!', 'Invalid'),
#----------------------------
  ('J', 0):
    (1,
    [[0, 1],
     [0, 1],
     [1, 1]]),
  ('J', 1):
    (0,
    [[1, 0, 0],
     [1, 1, 1]]),
  ('J', 2):
    (0,
    [[1, 1],
     [1, 0],
     [1, 0]]),
  ('J', 3):
    (0,
    [[1, 1, 1],
     [0, 0, 1]]),
#----------------------------
  ('L', 0):
    (0,
    [[1, 0],
     [1, 0],
     [1, 1]]),
  ('L', 1):
    (0,
    [[1, 1, 1],
     [1, 0, 0]]),
  ('L', 2):
    (0,
    [[1, 1],
     [0, 1],
     [0, 1]]),
  ('L', 3):
    (2,
    [[0, 0, 1],
     [1, 1, 1]]),
#----------------------------
  ('O', 0):
    (0,
    [[1, 1],
     [1, 1]]),
  ('O', 1): ('!', 'Invalid'),
  ('O', 2): ('!', 'Invalid'),
  ('O', 3): ('!', 'Invalid'),
#----------------------------
  ('S', 0):
    (1,
    [[0, 1, 1],
     [1, 1, 0]]),
  ('S', 1):
    (0,
    [[1, 0],
     [1, 1],
     [0, 1]]),
  ('S', 2): ('!', 'Invalid'),
  ('S', 3): ('!', 'Invalid'),
#----------------------------
  ('T', 0):
    (0,
    [[1, 1, 1],
     [0, 1, 0]]),
  ('T', 1):
    (1,
    [[0, 1],
     [1, 1],
     [0, 1]]),
  ('T', 2):
    (1,
    [[0, 1, 0],
     [1, 1, 1]]),
  ('T', 3):
    (0,
    [[1, 0],
     [1, 1],
     [1, 0]]),
#----------------------------
  ('Z', 0):
    (0,
    [[1, 1, 0],
     [0, 1, 1]]),
  ('Z', 1):
    (1,
    [[0, 1],
     [1, 1],
     [1, 0]]),
  ('Z', 2): ('!', 'Invalid'),
  ('Z', 3): ('!', 'Invalid'),
}

def doubleIter(xmax, ymax):
  for x in range(xmax):
    for y in range(ymax):
      yield (x, y)

from threading import Lock, Thread
global uuidlock
uuidlock = Lock()
def getUUID():
  global uuid
  uuidlock.acquire()
  uuid += 1
  newId = uuid
  uuidlock.release()
  return newId

class PartialSolution(Thread):
  def __init__(self, root=None):
    if root != None:
      self.board, self.board_h, self.board_w, self.pieces, self.steps, self.x, self.y, self.uuid = root
      return
    super(PartialSolution, self).__init__()

  def debug(self):
    print 'UUID:', self.uuid
    print 'Board:'
    self.printBoard()
    print 'Cost:', self.cost
    print 'Pieces:', self.pieces
    print 'Steps:', self.steps

  def clone(self):
    return PartialSolution(root=(
      self.board,
      self.board_h,
      self.board_w,
      list(self.pieces),
      list(self.steps),
      self.x,
      self.y,
      self.uuid, # Will be changed later IF the new copy survives
      ))

  def isInvalid(self, x, y):
    if x < 0 or y < 0:
      return True
    if x >= self.board_h or y >= self.board_w:
      return True
    return self.getBoard(x, y)

  # Returns 1 or -1, depending on parity.
  # def getParity(self, x, y):
  #   evenOdd = (x+y) % 2
  #   return 2 * (evenOdd) - 1

  def getBoard(self, x, y):
    mask = 2 << (x*self.board_w + y)
    return self.board & mask == mask

  def setBoard(self, x, y):
    self.board |= 2 << (x*self.board_w + y)

  def setPrintableBoard(self, board, x, y, char):
    if board[x][y] == '/':
      board[x][y] = char
    elif board[x][y] == '-' and char == '|':
      board[x][y] = '+'
    elif board[x][y] == '|' and char == '-':
      board[x][y] = '+'

  def printBoard(self):
    # Board2 is reconstructed from the piece placement steps
    board2 = [[-1 for _ in range(self.board_w)] for __ in range(self.board_h)]
    x = y = 0
    for pieceNum, rotation in self.steps:
      pieceName, initialRotation = pieces[pieceNum]
      offset, piece = tetrominos[(pieceName, (initialRotation+rotation) % 4)]
      for x, y in doubleIter(self.board_h, self.board_w):
        if board2[x][y] == -1:
          break
      for i, j in doubleIter(len(piece), len(piece[0])):
        if piece[i][j] == 1:
          board2[x+i][y+j-offset] = pieceNum

    # Board3 is constructed from board 2. It's just prettier :)
    board3 = [['/' for _ in range(self.board_w*10+1)] for __ in range(self.board_h*5+1)]
    # Fixing some corners
    board3[0][self.board_w*10] = '-'
    board3[self.board_h*5][0] = '|'
    board3[self.board_h*5][self.board_w*10] = '+'
    for x, y in doubleIter(len(board2), len(board2[x])):
      if board2[x][y] != -1: # Piece internal
        for i, j in doubleIter(4, 9):
          self.setPrintableBoard(board3, x*5+i+1, y*10+j+1, ' ')
        if x > 0 and board2[x][y] == board2[x-1][y]: # Piece internal row
          for j in range(1, 10):
            self.setPrintableBoard(board3, x*5, y*10+j, ' ')
        if y > 0 and board2[x][y] == board2[x][y-1]: # Piece internal column
          for i in range(1, 5):
            self.setPrintableBoard(board3, x*5+i, y*10, ' ')
        if x > 0 and y > 0 and board2[x][y] == board2[x-1][y] and board2[x][y] == board2[x][y-1]: # O piece internal
          self.setPrintableBoard(board3, x*5, y*10, ' ')
      if x == 0: # Top row
        for j in range(10):
          self.setPrintableBoard(board3, x*5, y*10+j, '-')
      elif board2[x][y] != board2[x-1][y]: # Internal row
        for j in range(11):
          self.setPrintableBoard(board3, x*5, y*10+j, '-')
      if x == self.board_h-1: # Bottom row
        for j in range(10):
          self.setPrintableBoard(board3, (x+1)*5, y*10+j, '-')
      if y == 0: # Left column
        for i in range(5):
          self.setPrintableBoard(board3, x*5+i, y*10, '|')
      elif board2[x][y] != board2[x][y-1]: # Internal column
        for i in range(6):
          self.setPrintableBoard(board3, x*5+i, y*10, '|')
      if y == self.board_w-1: # Right column
        for i in range(5):
          self.setPrintableBoard(board3, x*5+i, (y+1)*10, '|')

    for line in board3:
      print ''.join(line)

  def run(self):
    while True:
      try:
        _cost, self = q.get(True, 1)
        self.cost = _cost
      except Empty:
        return
      global maxCost
      if self.cost > maxCost and maxCost != -1:
        continue
      # Completed solution, since all pieces are placed
      if len(self.pieces) == len(self.steps):
        global solutions
        if len(solutions) == 0:
          maxCost = self.cost
        if len(solutions) == MAXSOLNS:
          return
        solutions.append(self)
        if len(solutions) == MAXSOLNS: # Allows the -1/0 logic below.
          return
        q.task_done()
        continue

      # Locating the first available space
      for self.x, self.y in doubleIter(self.board_h, self.board_w):
        if self.getBoard(self.x, self.y):
          continue
        break
      self.attempted_pieces = []
      for rotation in range(0, 4):
        for pieceNum in range(len(self.pieces)):
          if self.pieces[pieceNum] == None:
            continue
          pieceName, initialRotation = self.pieces[pieceNum]
          offset, piece = tetrominos[(pieceName, (initialRotation+rotation) % 4)]
          if piece == 'Invalid': # For pieces like the O which cannot be rotated further
            continue
          # Avoiding duplicates
          if (pieceName, (initialRotation+rotation) % 4) in self.attempted_pieces:
            continue
          self.attempted_pieces.append((pieceName, (initialRotation+rotation) % 4))
          newSolution = self.clone()
          # T pieces have a kind of parity. Consider the board to be a checkerboard,
          # then a T piece covers 3 black and 1 white squares, whereas all other pieces
          # cover 2 and 2. Thus, you must have an even number of T pieces AND
          # exactly half must be placed on white and half on black.
          # I never saw a solution in which this branch triggered.
          # if pieceName == 'T':
          #   parity = self.getParity(self.x, self.y)
          #   if abs(self.parity) == self.parityLimit and parity * self.parity > 0:
          #     continue # At parity limit and this placement only increases said limit
          #   newSolution.parity += parity

          invalidPlacement = False
          for i, j in doubleIter(len(piece), len(piece[0])):
            if piece[i][j] == 1:
              # Bounds check and Collision
              if self.isInvalid(self.x+i, self.y+j-offset):
                invalidPlacement = True
                break
              newSolution.setBoard(self.x+i, self.y+j-offset)
          # Checking for 1x1 holes in the next row is inefficient, as it happens very rarely (1-2%).
          # if self.x < self.board_h:
          #   for j in range(1, self.board_w-1):
          #     if (self.getBoard(self.x+1, j-1), self.getBoard(self.x+1, j), self.getBoard(self.x+1, j+1)) == (True, False, True):
          #       if self.isInvalid(self.x+2, j):
          #           invalidPlacement = True
          #           break
          if invalidPlacement:
            continue
          newSolution.uuid = getUUID()
          newSolution.pieces[pieceNum] = None
          newSolution.steps.append((pieceNum, rotation))
          q.put((self.cost + rotation, newSolution))
      q.task_done()

challenges = {
  # 'Name': ['Pieces', Height, Width],
  'Connector':  ['T0, T0, L1', 3, 4], # 3
  'A':          ['I1, L1, J1, Z0', 4, 4], # 1
  'Cube':       ['T0, T0, L1, Z0', 4, 4], # 4
  'Floor 1':    ['L1, Z0, L1, Z0', 4, 4], # 4
  'Recorder':   ['T0, T0, J1, S0, Z0', 5, 4], # 3
  'Fan':        ['T0, T0, L1, S0, Z0', 5, 4], # 4
  'B':          ['I1, T0, T0, L1, Z0', 5, 4], # 5
  'C':          ['T0, T0, J1, J1, L1, Z0', 6, 4], # 2
  'Platform':   ['I1, S0, T0, T0, L1, Z0', 6, 4], # 5
  'Floor 6':    ['O0, S0, S0, S0, S0, L0, L0, L0, L0', 6, 6], # 8
  'Floor 2':    ['O0, T0, T0, T0, T0, L1, L1, L1, L1', 6, 6], # 7
  'A star':     ['T0, T0, T0, T0, L1, J1, S0, S0, Z0, Z0', 5, 8], # 6
  'B star':     ['I1, I1, O0, T0, T0, T0, T0, L1, L1, J1', 5, 8], # 3
  'C star':     ['L1, J1, S0, Z0, T0, T0, I1, I1, O0, O0', 5, 8], # 2
  'Floor 3':    ['I1, I1, I1, I1, J1, J1, L1, L1, S0, Z0', 5, 8], # 3
  'Floor 4':    ['O0, O0, T0, T0, T0, T0, J1, L1, S0, S0, Z0, Z0', 8, 6], # 4
  'Floor 5':    ['I1, I1, O0, O0, O0, O0, T0, T0, T0, T0, J1, L1, S0, Z0', 7, 8], # 4
}

NUMTHREADS = 16
MAXSOLNS = 1 # Set to -1 for all solutions. Set to 0 to calculate only cost.
from Queue import PriorityQueue, Empty
from time import time

lock = Lock()
for title in challenges.keys():
  data = challenges[title]
  pieces = data[0].split(', ')
  if len(pieces) * 4 != data[1]*data[2]:
    raise ValueError('Board size', data[1]*data[2], 'does not fit piece size', len(pieces)*4)
  TCount = 0
  for i in range(len(pieces)):
    if pieces[i][0] == 'T':
      TCount += 1
    pieces[i] = (pieces[i][0], int(pieces[i][1]))
  if TCount%2 != 0: # See parity comment above.
    raise ValueError('Solutions must have an even number of T pieces.')
  global maxCost
  maxCost = -1
  global solutions
  solutions = []
  global uuid
  uuid = 0
  print 'Challenge "{name}" using {num} pieces: {pieces}'.format(name=title, num = len(pieces), pieces=pieces)
  startTime = time()

  q = PriorityQueue()
  # cost, PartialSolution(root=(board, board_h, board_w, pieces, steps, x, y, uuid))
  q.put((0, PartialSolution(root=(0L, data[1], data[2], list(pieces), [], 0, 0, 0))))
  threads = []
  for i in range(NUMTHREADS):
    thread = PartialSolution()
    thread.daemon = True
    threads.append(thread)
    thread.start()
  for thread in threads:
    thread.join()
  print 'Took {time} seconds using {partials} partials.'.format(time=time()-startTime, partials = uuid)
  print 'Found {num} solution{s} with {cost} rotations:'.format(num=len(solutions), s=('s' if len(solutions) != 1 else ''), cost=maxCost)
  for solution in solutions:
    solution.printBoard()
    print
<|endoftext|>


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

import click
from PIL import Image
from ansi.colour.rgb import rgb8, rgb16, rgb256
from ansi.colour.fx import reset

_default_palette = ' ░▒▓█'

_resample_methods = {
    'nearest': Image.NEAREST,
    'bilinear': Image.BILINEAR,
    'bicubic': Image.BICUBIC,
    'antialias': Image.ANTIALIAS
}

@click.command()
@click.argument('image', type=click.File('rb'))
@click.argument('out', type=click.File('wt'), default='-',
                required=False)
@click.option('-w', '--width', type=click.INT,
              help="Width of output. If height is not given, "
                   "the image will be proportionally scaled.")
@click.option('-h', '--height', type=click.INT,
              help="Height of output. If width is not given, "
                    "the image will be proportionally scaled. "
                    "If neither width nor height are given, "
                    "the current terminal width is used instead.")
@click.option('--correct/--no-correct', default=True,
              help="Wether to account for the proportions of "
                   "monospaced characters. On by default.")
@click.option('-r', '--resample', default='antialias',
              type=click.Choice(['nearest', 'bilinear',
                                 'bicubic', 'antialias']),
              help="Filter to use for resampling. Default is antialias.")
@click.option('-p', '--palette', default=_default_palette,
              help="A custom palette for rendering images, "
                   "from from dark to bright.")
@click.option('-i', '--invert', is_flag=True,
              help="Inverts the palette.")
@click.option('-c', '--colour', type=click.Choice(['8', '16', '256']),
              help="Enables colour output. This does not disable "
                   "the normal character palette. Rather, the characters "
                   "get a foreground colour corresponding to "
                   "the colour in the original.")
@click.option('--debug', is_flag=True,
              help="Debug mode.")
def convert(image, out, width, height, correct,
            resample, palette, invert, colour, debug):
    """
    Converts INPUT to a text representation.
    OUT determines the output stream (stdout by default).

    Supports most image filetypes by default,
    except for JPEG. For that you need to install libjpeg.
    \b For more info see:
    http://pillow.readthedocs.org/installation.html#external-libraries
    """

    original = Image.open(image)

    if invert:
        palette = palette[::-1]

    if not width and not height:
        width, _ = click.get_terminal_size()

    size = calculate_size(original.size, (width, height))

    resized = original.resize(size, resample=_resample_methods[resample])

    if correct:
        corrected_size = (size[0], int(size[1] * 0.5))
        resized = resized.resize(corrected_size,
                                 resample=_resample_methods[resample])

    if not colour:
        adjusted = resized.convert(mode='L')
    else:
        adjusted = resized.convert(mode='RGB')

    for line in build_lines(adjusted, palette, colour):
        click.echo(line)

    if debug:
        click.echo("Original size {}\nRequest size {}\nResult size {}"
                   .format(original.size, (width, height), resized.size))

def calculate_size(original, target):
    """
    Proportionally scales image sizes.

    :param tuple original: the original size
    :param tuple target: the desired size
    :returns: the result size
    :rtype: tuple
    """

    original_width, original_height = original
    target_width, target_height = target

    ratio = original_height / original_width

    if target_width and not target_height:
        size = (target_width, int(target_width * ratio))
    elif target_height and not target_width:
        size = (int(target_height / ratio), target_height)
    elif target_width and target_height:
        size = (target_width, target_height)

    return size

def build_lines(image, palette, mode):
    """
    Generator function that iterates over an image and converts it to a
    text representation.

    :param :py:class:PIL.Image image: input image
    :param palette: character palette, ordered from dark to bright
    :type palette: str or list
    """

    width, height = image.size

    bw = image.convert(mode='L')

    for y in range(height):
        line = ''

        for x in range(width):
            value = bw.getpixel((x, y))
            char = value_to_char(value, palette)

            if mode:
                pixel = image.getpixel((x, y))

                if mode == '256':
                    char = rgb256(*pixel) + char + str(reset)
                elif mode == '16':
                    char = rgb16(*pixel) + char + str(reset)
                elif mode == '8':
                    char = rgb8(*pixel) + char + str(reset)
                else:
                    raise ValueError("Invalid colour mode. ('{}')\
                        Use either '8', '16' or '256'".format(mode))

            line += char

        yield line

def value_to_char(value, palette, value_range=(0, 256)):
    """
    Takes a grayscale value and maps it to a character in a palette

    :param int value: input colour value
    :param palette: character palette, ordered from dark to bright
    :type palette: str or list
    :param tuple value_range: minimum and maximum value
    :raises ValueError: if the input value does not fall within value_range
    """

    if value not in range(*value_range):
        raise ValueError("Input value not in expected range.")

    palette_range = (0, len(palette))
    mapped = int(_scale(value, value_range, palette_range))
    return palette[mapped]

def _scale(value, source, destination):
    """
    Linear map a value from a source to a destination range.

    :param int value: original value
    :param tuple source: source range
    :param tuple destination: destination range
    :rtype: float
    """

    return (
        ((value - source[0]) / (source[1]-source[0]))
        * (destination[1]-destination[0])
        + destination[0]
    )
<commit_msg>Change wording in help text<commit_after>#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import click
from PIL import Image
from ansi.colour.rgb import rgb8, rgb16, rgb256
from ansi.colour.fx import reset

_default_palette = ' ░▒▓█'

_resample_methods = {
    'nearest': Image.NEAREST,
    'bilinear': Image.BILINEAR,
    'bicubic': Image.BICUBIC,
    'antialias': Image.ANTIALIAS
}

@click.command()
@click.argument('image', type=click.File('rb'))
@click.argument('out', type=click.File('wt'), default='-',
                required=False)
@click.option('-w', '--width', type=click.INT,
              help="Width of output. If height is not given, "
                   "the image will be proportionally scaled.")
@click.option('-h', '--height', type=click.INT,
              help="Height of output. If width is not given, "
                    "the image will be proportionally scaled. "
                    "If neither width nor height are given, "
                    "the current terminal width is used instead.")
@click.option('--correct/--no-correct', default=True,
              help="Wether to account for the proportions of "
                   "monospaced characters. On by default.")
@click.option('-r', '--resample', default='antialias',
              type=click.Choice(['nearest', 'bilinear',
                                 'bicubic', 'antialias']),
              help="Filter to use for resampling. Default is antialias.")
@click.option('-p', '--palette', default=_default_palette,
              help="A custom palette for rendering images, "
                   "from from dark to bright.")
@click.option('-i', '--invert', is_flag=True,
              help="Inverts the palette.")
@click.option('-c', '--colour', type=click.Choice(['8', '16', '256']),
              help="Enables colour output. This does not disable "
                   "the normal character palette. Rather, the characters "
                   "get a foreground colour corresponding to "
                   "the colour in the original.")
@click.option('--debug', is_flag=True,
              help="Debug mode.")
def convert(image, out, width, height, correct,
            resample, palette, invert, colour, debug):
    """
    Converts INPUT to a text representation.
    OUT is an optional file to save to.

    Supports most image filetypes by default,
    except for JPEG. For that you need to install libjpeg.
    \b For more info see:
    http://pillow.readthedocs.org/installation.html#external-libraries
    """

    original = Image.open(image)

    if invert:
        palette = palette[::-1]

    if not width and not height:
        width, _ = click.get_terminal_size()

    size = calculate_size(original.size, (width, height))

    resized = original.resize(size, resample=_resample_methods[resample])

    if correct:
        corrected_size = (size[0], int(size[1] * 0.5))
        resized = resized.resize(corrected_size,
                                 resample=_resample_methods[resample])

    if not colour:
        adjusted = resized.convert(mode='L')
    else:
        adjusted = resized.convert(mode='RGB')

    for line in build_lines(adjusted, palette, colour):
        click.echo(line)

    if debug:
        click.echo("Original size {}\nRequest size {}\nResult size {}"
                   .format(original.size, (width, height), resized.size))

def calculate_size(original, target):
    """
    Proportionally scales image sizes.

    :param tuple original: the original size
    :param tuple target: the desired size
    :returns: the result size
    :rtype: tuple
    """

    original_width, original_height = original
    target_width, target_height = target

    ratio = original_height / original_width

    if target_width and not target_height:
        size = (target_width, int(target_width * ratio))
    elif target_height and not target_width:
        size = (int(target_height / ratio), target_height)
    elif target_width and target_height:
        size = (target_width, target_height)

    return size

def build_lines(image, palette, mode):
    """
    Generator function that iterates over an image and converts it to a
    text representation.

    :param :py:class:PIL.Image image: input image
    :param palette: character palette, ordered from dark to bright
    :type palette: str or list
    """

    width, height = image.size

    bw = image.convert(mode='L')

    for y in range(height):
        line = ''

        for x in range(width):
            value = bw.getpixel((x, y))
            char = value_to_char(value, palette)

            if mode:
                pixel = image.getpixel((x, y))

                if mode == '256':
                    char = rgb256(*pixel) + char + str(reset)
                elif mode == '16':
                    char = rgb16(*pixel) + char + str(reset)
                elif mode == '8':
                    char = rgb8(*pixel) + char + str(reset)
                else:
                    raise ValueError("Invalid colour mode. ('{}')\
                        Use either '8', '16' or '256'".format(mode))

            line += char

        yield line

def value_to_char(value, palette, value_range=(0, 256)):
    """
    Takes a grayscale value and maps it to a character in a palette

    :param int value: input colour value
    :param palette: character palette, ordered from dark to bright
    :type palette: str or list
    :param tuple value_range: minimum and maximum value
    :raises ValueError: if the input value does not fall within value_range
    """

    if value not in range(*value_range):
        raise ValueError("Input value not in expected range.")

    palette_range = (0, len(palette))
    mapped = int(_scale(value, value_range, palette_range))
    return palette[mapped]

def _scale(value, source, destination):
    """
    Linear map a value from a source to a destination range.

    :param int value: original value
    :param tuple source: source range
    :param tuple destination: destination range
    :rtype: float
    """

    return (
        ((value - source[0]) / (source[1]-source[0]))
        * (destination[1]-destination[0])
        + destination[0]
    )
<|endoftext|>


<commit_before>import os, sys, logging, paste
from webob.exc import HTTPFound, HTTPNotFound
from tg.util import Bunch

log = logging.getLogger(__name__)

import tg
from tg import request_local
from tg.i18n import _get_translator
from tg.request_local import Request, Response

from pylons.util import ContextObj, AttribSafeContextObj

class RequestLocals(object):
    pass

class TGApp(object):
    def __init__(self, config=None, **kwargs):
        """Initialize a base WSGI application

        The base WSGI application requires several keywords, the
        package name, and the globals object. If no helpers object is
        provided then h will be None.

        """
        self.config = config = config or tg.config._current_obj()
        self.globals = config.get('tg.app_globals')
        self.package_name = config['package'].__name__

        self.controller_classes = {}
        self.log_debug = False

        self.config.setdefault('lang', None)

        # Cache some options for use during requests
        self._session_key = 'beaker.session'
        self._cache_key = 'beaker.cache'

        self.strict_tmpl_context = self.config['tg.strict_tmpl_context']
        self.req_options = config.get('tg.request_options', config['pylons.request_options'])
        self.resp_options = config.get('tg.response_options', config['pylons.response_options'])

    def setup_pylons_compatibility(self, environ, controller):
        """Updates environ to be backward compatible with Pylons"""
        environ['pylons.controller'] = controller
        environ['pylons.pylons'] = environ['tg.locals']
        environ['pylons.routes_dict'] = environ['tg.routes_dict']

    def __call__(self, environ, start_response):
        # Cache the logging level for the request
        log_debug = self.log_debug = logging.DEBUG >= log.getEffectiveLevel()

        testmode = self.setup_app_env(environ, start_response)
        if testmode:
            if environ['PATH_INFO'] == '/_test_vars':
                paste.registry.restorer.save_registry_state(environ)
                start_response('200 OK', [('Content-type', 'text/plain')])
                return ['%s' % paste.registry.restorer.get_request_id(environ)]

        controller = self.resolve(environ, start_response)
        response = self.dispatch(controller, environ, start_response)

        if testmode and hasattr(response, 'wsgi_response'):
            environ['paste.testing_variables']['response'] = response

        try:
            if hasattr(response, 'wsgi_response'):
                # Transform Response objects from legacy Controller
                if log_debug:
                    log.debug("Transforming legacy Response object into WSGI "
                              "response")
                return response(environ, start_response)
            elif response is not None:
                return response

            raise Exception("No content returned by controller (Did you "
                            "remember to 'return' it?) in: %r" %
                            controller.__name__)
        finally:
            # Help Python collect ram a bit faster by removing the reference
            # cycle that the pylons object causes
            if 'pylons.pylons' in environ:
                del environ['pylons.pylons']

    def setup_app_env(self, environ, start_response):
        """Setup and register all the Pylons objects with the registry

        After creating all the global objects for use in the request,
        :meth:`~PylonsApp.register_globals` is called to register them
        in the environment.

        """
        if self.log_debug:
            log.debug("Setting up Pylons stacked object globals")

        # Setup the basic global objects
        req_options = self.req_options
        req = Request(environ, charset=req_options['charset'],
                      unicode_errors=req_options['errors'],
                      decode_param_names=req_options['decode_param_names'])
        req.language = req_options['language']

        resp_options = self.resp_options
        response = Response(
            content_type=resp_options['content_type'],
            charset=resp_options['charset'])
        response.headers.update(resp_options['headers'])

        # Setup the translator object
        lang = self.config['lang']
        translator = _get_translator(lang, pylons_config=self.config)

        if self.strict_tmpl_context:
            tmpl_context = ContextObj()
        else:
            tmpl_context = AttribSafeContextObj()

        locals = RequestLocals()
        locals.response = response
        locals.request = req
        locals.app_globals = self.globals
        locals.config = self.config
        locals.tmpl_context = tmpl_context
        locals.translator = translator
        locals.session = environ[self._session_key]
        locals.cache = environ[self._cache_key]
        locals.url = environ['routes.url']
        environ['tg.locals'] = locals

        #Register Global objects
        registry = environ['paste.registry']
        registry.register(request_local.response, response)
        registry.register(request_local.request, req)
        registry.register(request_local.app_globals, self.globals)
        registry.register(request_local.config, self.config)
        registry.register(request_local.tmpl_context, tmpl_context)
        registry.register(request_local.translator, translator)
        registry.register(request_local.session, environ[self._session_key])
        registry.register(request_local.cache, environ[self._cache_key])
        registry.register(request_local.url, environ['routes.url'])

        if 'paste.testing_variables' in environ:
            if self.log_debug:
                log.debug("Setting up paste testing environment variables")
            testenv = environ['paste.testing_variables']
            testenv['req'] = req
            testenv['response'] = response
            testenv['tmpl_context'] = tmpl_context
            testenv['app_globals'] = testenv['g'] = self.globals
            testenv['config'] = self.config
            testenv['session'] = environ[self._session_key]
            testenv['cache'] = environ[self._cache_key]
            return True

        return False

    def resolve(self, environ, start_response):
        """Uses dispatching information found in
        ``environ['wsgiorg.routing_args']`` to retrieve a controller
        name and return the controller instance from the appropriate
        controller module.

        Override this to change how the controller name is found and
        returned.

        """
        match = environ['wsgiorg.routing_args'][1]
        environ['tg.routes_dict'] = match
        controller = match.get('controller')
        if not controller:
            return

        if self.log_debug:
            log.debug("Resolved URL to controller: %r", controller)

        return self.find_controller(controller)

    def class_name_from_module_name(self, module_name):
        words = module_name.replace('-', '_').split('_')
        return ''.join(w.title() for w in words)

    def find_controller(self, controller):
        """Locates a controller by attempting to import it then grab
        the SomeController instance from the imported module.

        Override this to change how the controller object is found once
        the URL has been resolved.

        """
        # Check to see if we've cached the class instance for this name
        if controller in self.controller_classes:
            return self.controller_classes[controller]

        root_module_path = self.config['paths']['root']
        base_controller_path = self.config['paths']['controllers']

        #remove the part of the path we expect to be the root part (plus one '/')
        assert base_controller_path.startswith(root_module_path)
        controller_path = base_controller_path[len(root_module_path)+1:]

        #attach the package
        full_module_name = '.'.join([self.package_name] +
            controller_path.split(os.sep) + controller.split('/'))

        # Hide the traceback here if the import fails (bad syntax and such)
        __traceback_hide__ = 'before_and_this'

        __import__(full_module_name)
        module_name = controller.split('/')[-1]
        class_name = self.class_name_from_module_name(module_name) + 'Controller'
        if self.log_debug:
            log.debug("Found controller, module: '%s', class: '%s'",
                      full_module_name, class_name)
        mycontroller = getattr(sys.modules[full_module_name], class_name)
        self.controller_classes[controller] = mycontroller
        return mycontroller

    def dispatch(self, controller, environ, start_response):
        """Dispatches to a controller, will instantiate the controller
        if necessary.

        Override this to change how the controller dispatch is handled.

        """
        log_debug = self.log_debug
        if not controller:
            if log_debug:
                log.debug("No controller found, returning 404 HTTP Not Found")
            return HTTPNotFound()(environ, start_response)

        # If it's a class, instantiate it
        if hasattr(controller, '__bases__'):
            if log_debug:
                log.debug("Controller appears to be a class, instantiating")
            controller = controller()
            controller._pylons_log_debug = log_debug

        #Setup pylons compatibility before calling controller
        self.setup_pylons_compatibility(environ, controller)

        # Controller is assumed to handle a WSGI call
        if log_debug:
            log.debug("Calling controller class with WSGI interface")
        return controller(environ, start_response)<commit_msg>Avoid accessing multiple times the environ as objects are already available in request locals<commit_after>import os, sys, logging, paste
from webob.exc import HTTPFound, HTTPNotFound
from tg.util import Bunch

log = logging.getLogger(__name__)

import tg
from tg import request_local
from tg.i18n import _get_translator
from tg.request_local import Request, Response

from pylons.util import ContextObj, AttribSafeContextObj

class RequestLocals(object):
    pass

class TGApp(object):
    def __init__(self, config=None, **kwargs):
        """Initialize a base WSGI application

        The base WSGI application requires several keywords, the
        package name, and the globals object. If no helpers object is
        provided then h will be None.

        """
        self.config = config = config or tg.config._current_obj()
        self.globals = config.get('tg.app_globals')
        self.package_name = config['package'].__name__

        self.controller_classes = {}
        self.log_debug = False

        self.config.setdefault('lang', None)

        # Cache some options for use during requests
        self.strict_tmpl_context = self.config['tg.strict_tmpl_context']
        self.req_options = config.get('tg.request_options', config['pylons.request_options'])
        self.resp_options = config.get('tg.response_options', config['pylons.response_options'])

    def setup_pylons_compatibility(self, environ, controller):
        """Updates environ to be backward compatible with Pylons"""
        environ['pylons.controller'] = controller
        environ['pylons.pylons'] = environ['tg.locals']
        environ['pylons.routes_dict'] = environ['tg.routes_dict']

    def __call__(self, environ, start_response):
        # Cache the logging level for the request
        log_debug = self.log_debug = logging.DEBUG >= log.getEffectiveLevel()

        testmode = self.setup_app_env(environ, start_response)
        if testmode:
            if environ['PATH_INFO'] == '/_test_vars':
                paste.registry.restorer.save_registry_state(environ)
                start_response('200 OK', [('Content-type', 'text/plain')])
                return ['%s' % paste.registry.restorer.get_request_id(environ)]

        controller = self.resolve(environ, start_response)
        response = self.dispatch(controller, environ, start_response)

        if testmode and hasattr(response, 'wsgi_response'):
            environ['paste.testing_variables']['response'] = response

        try:
            if hasattr(response, 'wsgi_response'):
                # Transform Response objects from legacy Controller
                if log_debug:
                    log.debug("Transforming legacy Response object into WSGI "
                              "response")
                return response(environ, start_response)
            elif response is not None:
                return response

            raise Exception("No content returned by controller (Did you "
                            "remember to 'return' it?) in: %r" %
                            controller.__name__)
        finally:
            # Help Python collect ram a bit faster by removing the reference
            # cycle that the pylons object causes
            if 'pylons.pylons' in environ:
                del environ['pylons.pylons']

    def setup_app_env(self, environ, start_response):
        """Setup and register all the Pylons objects with the registry

        After creating all the global objects for use in the request,
        :meth:`~PylonsApp.register_globals` is called to register them
        in the environment.

        """
        if self.log_debug:
            log.debug("Setting up Pylons stacked object globals")

        # Setup the basic global objects
        req_options = self.req_options
        req = Request(environ, charset=req_options['charset'],
                      unicode_errors=req_options['errors'],
                      decode_param_names=req_options['decode_param_names'])
        req.language = req_options['language']

        resp_options = self.resp_options
        response = Response(
            content_type=resp_options['content_type'],
            charset=resp_options['charset'])
        response.headers.update(resp_options['headers'])

        # Setup the translator object
        lang = self.config['lang']
        translator = _get_translator(lang, pylons_config=self.config)

        if self.strict_tmpl_context:
            tmpl_context = ContextObj()
        else:
            tmpl_context = AttribSafeContextObj()

        locals = RequestLocals()
        locals.response = response
        locals.request = req
        locals.app_globals = self.globals
        locals.config = self.config
        locals.tmpl_context = tmpl_context
        locals.translator = translator
        locals.session = environ['beaker.session']
        locals.cache = environ['beaker.cache']
        locals.url = environ['routes.url']
        environ['tg.locals'] = locals

        #Register Global objects
        registry = environ['paste.registry']
        registry.register(request_local.response, response)
        registry.register(request_local.request, req)
        registry.register(request_local.app_globals, self.globals)
        registry.register(request_local.config, self.config)
        registry.register(request_local.tmpl_context, tmpl_context)
        registry.register(request_local.translator, translator)
        registry.register(request_local.session, locals.session)
        registry.register(request_local.cache, locals.cache)
        registry.register(request_local.url, locals.url)

        if 'paste.testing_variables' in environ:
            if self.log_debug:
                log.debug("Setting up paste testing environment variables")
            testenv = environ['paste.testing_variables']
            testenv['req'] = req
            testenv['response'] = response
            testenv['tmpl_context'] = tmpl_context
            testenv['app_globals'] = testenv['g'] = self.globals
            testenv['config'] = self.config
            testenv['session'] = locals.session
            testenv['cache'] = locals.cache
            return True

        return False

    def resolve(self, environ, start_response):
        """Uses dispatching information found in
        ``environ['wsgiorg.routing_args']`` to retrieve a controller
        name and return the controller instance from the appropriate
        controller module.

        Override this to change how the controller name is found and
        returned.

        """
        match = environ['wsgiorg.routing_args'][1]
        environ['tg.routes_dict'] = match
        controller = match.get('controller')
        if not controller:
            return

        if self.log_debug:
            log.debug("Resolved URL to controller: %r", controller)

        return self.find_controller(controller)

    def class_name_from_module_name(self, module_name):
        words = module_name.replace('-', '_').split('_')
        return ''.join(w.title() for w in words)

    def find_controller(self, controller):
        """Locates a controller by attempting to import it then grab
        the SomeController instance from the imported module.

        Override this to change how the controller object is found once
        the URL has been resolved.

        """
        # Check to see if we've cached the class instance for this name
        if controller in self.controller_classes:
            return self.controller_classes[controller]

        root_module_path = self.config['paths']['root']
        base_controller_path = self.config['paths']['controllers']

        #remove the part of the path we expect to be the root part (plus one '/')
        assert base_controller_path.startswith(root_module_path)
        controller_path = base_controller_path[len(root_module_path)+1:]

        #attach the package
        full_module_name = '.'.join([self.package_name] +
            controller_path.split(os.sep) + controller.split('/'))

        # Hide the traceback here if the import fails (bad syntax and such)
        __traceback_hide__ = 'before_and_this'

        __import__(full_module_name)
        module_name = controller.split('/')[-1]
        class_name = self.class_name_from_module_name(module_name) + 'Controller'
        if self.log_debug:
            log.debug("Found controller, module: '%s', class: '%s'",
                      full_module_name, class_name)
        mycontroller = getattr(sys.modules[full_module_name], class_name)
        self.controller_classes[controller] = mycontroller
        return mycontroller

    def dispatch(self, controller, environ, start_response):
        """Dispatches to a controller, will instantiate the controller
        if necessary.

        Override this to change how the controller dispatch is handled.

        """
        log_debug = self.log_debug
        if not controller:
            if log_debug:
                log.debug("No controller found, returning 404 HTTP Not Found")
            return HTTPNotFound()(environ, start_response)

        # If it's a class, instantiate it
        if hasattr(controller, '__bases__'):
            if log_debug:
                log.debug("Controller appears to be a class, instantiating")
            controller = controller()
            controller._pylons_log_debug = log_debug

        #Setup pylons compatibility before calling controller
        self.setup_pylons_compatibility(environ, controller)

        # Controller is assumed to handle a WSGI call
        if log_debug:
            log.debug("Calling controller class with WSGI interface")
        return controller(environ, start_response)<|endoftext|>


<commit_before>""" An agent does actions in a game.
"""

class Base:
    """ Agent interface.
    """

    def optional(self, visible, enemy):
        """ Returns boolean
        """
        raise NotImplementedError()

    def select(self, visible, enemies):
        """ Returns index of selected enemy.
        """
        raise NotImplementedError()

    def battle(self, visible, enemy):
        """ Generates battle actions.
        """
        pass
<commit_msg>File-based agent.<commit_after>""" An agent does actions in a game.
"""

class Base:
    """ Agent interface.
    """

    def optional_enemy(self, visible, enemy):
        """ Returns boolean
        """
        raise NotImplementedError()

    def select_enemy(self, visible, enemies):
        """ Returns index of selected enemy.
        """
        raise NotImplementedError()

    def select_card(self, visible, cards):
        """ Returns index of selected card.
        """
        raise NotImplementedError()

    def battle(self, visible, enemy):
        """ Generates battle actions.
        """
        raise NotImplementedError()

class FileAgent(Base):
    """ An agent by given input file and output file.
    """

    def __init__(self, in_file, out_file):
        super().__init__()
        self._i_file = in_file
        self._o_file = out_file

    def _input(self):
        """ Get trimmed input line.
        """
        line = self._i_file.readline()
        return line.trim() if line is not None else None

    def _print(self, string):
        """ Print string in a line.
        """
        self._o_file.write("{}\n".format(string))

    def optional_enemy(self, visible, enemy):
        self._print("Optional enemy [Y/N]:")
        # TODO Print enemy information.
        string = self._input()
        if string == "Y":
            return True
        elif string == "N":
            return False
        else:
            self._print("Not a valid option.")
            return None

    def _select(self, visible, items):
        """ Select an item.
        """
        # TODO Print item information.
        try:
            idx = int(self._input())
            items[idx]
            return idx
        except ValueError:
            self._print("Not a valid index.")
            return None
        except IndexError:
            self._print("Index out of range.")
            return None

    def select_enemy(self, visible, enemies):
        self._print("Select enemy <index>:")
        return self._select(visible, enemies)

    def select_card(self, visible, cards):
        self._print("Select card <index>:")
        return self._select(visible, cards)

    def battle(self, visible, enemy):
        while True:
            self._print("Select action (draw/use/end):")
            action = self._input()
            if action == "draw":
                self._draw() # TODO
                self._print("Draw a card.")
            elif action == "use":
                idx = self.select_card(visible, visible.cards)
                visible.cards[idx].use()
                self._print("Use a card.")
            elif action == "end":
                self._print("End the battle.")
                break
            else:
                self._print("Invalid action \"{}\"".format(action))
<|endoftext|>


<commit_before>from django.test import TestCase

# Create your tests here.
<commit_msg>Started tests.<commit_after>from django.test import TestCase
from django.test.client import Client

from backend.models import Dictionary, Project

# Create your tests here.
class DictionaryIndexTestCase(TestCase):
    def set_up(self):
        Project.objects.create(name="project_1")
        Dictionary.objects.create(name="dictionary_1", project="project_1")

    def test_index_contains_dictionary(self):
        client = Client()
        response = client.get('dictionary_index')

        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.context['dictionaries']), 1)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

import os
import sys
import pkg_resources
import csv
import json
from collections import namedtuple
from zipfile import ZipFile

from tiingo.restclient import RestClient
import requests

VERSION = pkg_resources.get_distribution("tiingo").version

# These methods enable python 2 + 3 compatibility.
def get_zipfile_from_response(response):
    if sys.version_info < (3, 0):  # python 2
        from StringIO import StringIO as Buffer
    else:  # python 3
        from io import BytesIO as Buffer
    buffered = Buffer(response.content)
    return ZipFile(buffered)

def get_buffer_from_zipfile(zipfile, filename):
    if sys.version_info < (3, 0):  # python 2
        from StringIO import StringIO
        return StringIO(zipfile.read(filename))
    else:  # python 3
        # Source:
        # https://stackoverflow.com/questions/5627954/py3k-how-do-you-read-a-file-inside-a-zip-file-as-text-not-bytes
        from io import (TextIOWrapper, BytesIO)
        return TextIOWrapper(BytesIO(zipfile.read(filename)))

class TiingoClient(RestClient):
    """Class for managing interactions with the Tiingo REST API

        Supply API Key via Environment Variable TIINGO_API_KEY
        or via the Config Object
    """

    def __init__(self, *args, **kwargs):
        super(TiingoClient, self).__init__(*args, **kwargs)
        self._base_url = "https://api.tiingo.com"

        try:
            api_key = self._config['api_key']
        except KeyError:
            api_key = os.environ.get('TIINGO_API_KEY')
        assert(api_key)

        self._headers = {
            'Authorization': "Token {}".format(api_key),
            'Content-Type': 'application/json',
            'User-Agent': 'tiingo-python-client {}'.format(VERSION)
        }

    def __repr__(self):
        return '<TiingoClient(url="{}")>'.format(self._base_url)

    # TICKER PRICE ENDPOINTS
    # https://api.tiingo.com/docs/tiingo/daily
    def list_stock_tickers(self):
        """Return a list of dicts of metadata tickers for all supported Stocks
            as well as metadata about each ticker. This includes supported
            date range, the exchange the ticker is traded on, and the currency
            the stock is traded on.
           Tickers for unrelated products are omitted.
           https://apimedia.tiingo.com/docs/tiingo/daily/supported_tickers.zip
           """
        listing_file_url = "https://apimedia.tiingo.com/docs/tiingo/daily/supported_tickers.zip"
        response = requests.get(listing_file_url)
        zipdata = get_zipfile_from_response(response)
        raw_csv = get_buffer_from_zipfile(zipdata, 'supported_tickers.csv')
        reader = csv.DictReader(raw_csv)

        return [row for row in reader
                if row.get('assetType') == 'Stock']

    def get_ticker_metadata(self, ticker, fmt='json'):
        """Return metadata for 1 ticker
           Use TiingoClient.list_tickers() to get available options

            Args:
                ticker (str) : Unique identifier for stock
        """
        url = "tiingo/daily/{}".format(ticker)
        response = self._request('GET', url)
        data = response.json()
        if fmt == 'json':
            return data
        elif fmt == 'object':
            return json.loads(json.dumps(data),
                              object_hook=lambda d:
                              namedtuple('Ticker', d.keys())(*d.values()))

    def get_ticker_price(self, ticker,
                         startDate=None, endDate=None,
                         fmt='json', frequency='daily'):
        """By default, return latest EOD Composite Price for a stock ticker.
           On average, each feed contains 3 data sources.

            Supported tickers + Available Day Ranges are here:
            https://apimedia.tiingo.com/docs/tiingo/daily/supported_tickers.zip

            Args:
                ticker (string): Unique identifier for stock ticker
                startDate (string): Start of ticker range in YYYY-MM-DD format
                endDate (string): End of ticker range in YYYY-MM-DD format
                fmt (string): 'csv' or 'json'
                frequency (string): Resample frequency
        """
        url = "tiingo/daily/{}/prices".format(ticker)
        params = {
            'format': fmt,
            'frequency': frequency
        }

        if startDate:
            params['startDate'] = startDate
        if endDate:
            params['endDate'] = endDate

        # TODO: evaluate whether to stream CSV to cache on disk, or
        # load as array in memory, or just pass plain text
        response = self._request('GET', url, params=params)
        if fmt == "json":
            return response.json()
        else:
            return response.content.decode("utf-8")

    # NEWS FEEDS
    # tiingo/news
    def get_news(self, tickers=[], tags=[], sources=[], startDate=None,
                 endDate=None, limit=100, offset=0, sortBy="publishedDate",
                 fmt='json'):
        """Return list of news articles matching given search terms
            https://api.tiingo.com/docs/tiingo/news

            # Dates are in YYYY-MM-DD Format.

            Args:
                tickers [string] : List of unique Stock Tickers to search
                tags [string] : List of topics tagged by Tiingo Algorithms
                sources [string]: List of base urls to include as news sources
                startDate, endDate [date]: Boundaries of news search window
                limit (int): Max results returned. Default 100, max 1000
                offset (int): Search results offset, used for paginating
                sortBy (string): "publishedDate" OR (#TODO: UPDATE THIS)
        """
        url = "tiingo/news"
        params = {
            'limit': limit,
            'offset': offset,
            'sortBy': sortBy,
            'tickers': tickers,
            'sources': sources,
            'tags': tags,
            'startDate': startDate,
            'endDate': endDate
        }
        response = self._request('GET', url, params=params)
        data = response.json()
        if fmt == 'json':
            return data
        elif fmt == 'object':
            obj_arr = []
            for el in data:
                # inspired by https://stackoverflow.com/a/15882054
                arr_el = json.loads(json.dumps(el),
                                    object_hook=lambda d:
                                    namedtuple('NewsArticle', d.keys())(*d.values()))
                obj_arr.append(arr_el)
            return obj_arr

    def get_bulk_news(self, file_id=None, fmt='json'):
        """Only available to institutional clients.
            If ID is NOT provided, return array of available file_ids.
            If ID is provided, provides URL which you can use to download your
            file, as well as some metadata about that file.
        """
        if file_id:
            url = "tiingo/news/bulk_download/{}".format(file_id)
        else:
            url = "tiingo/news/bulk_download"

        response = self._request('GET', url)
        data = response.json()
        if fmt == 'json':
            return data
        elif fmt == 'object':
            return json.loads(json.dumps(data),
                              object_hook=lambda d: namedtuple('BulkDownload', d.keys())(*d.values()))
<commit_msg>Refactor with a common method for dict to object converstion<commit_after># -*- coding: utf-8 -*-

import os
import sys
import pkg_resources
import csv
import json
from collections import namedtuple
from zipfile import ZipFile

from tiingo.restclient import RestClient
import requests

VERSION = pkg_resources.get_distribution("tiingo").version

# These methods enable python 2 + 3 compatibility.
def get_zipfile_from_response(response):
    if sys.version_info < (3, 0):  # python 2
        from StringIO import StringIO as Buffer
    else:  # python 3
        from io import BytesIO as Buffer
    buffered = Buffer(response.content)
    return ZipFile(buffered)

def get_buffer_from_zipfile(zipfile, filename):
    if sys.version_info < (3, 0):  # python 2
        from StringIO import StringIO
        return StringIO(zipfile.read(filename))
    else:  # python 3
        # Source:
        # https://stackoverflow.com/questions/5627954/py3k-how-do-you-read-a-file-inside-a-zip-file-as-text-not-bytes
        from io import (TextIOWrapper, BytesIO)
        return TextIOWrapper(BytesIO(zipfile.read(filename)))

def dict_to_object(item, object_name):
    """Converts a python dict to a namedtuple, saving memory."""
    fields = item.keys()
    values = item.values()
    return json.loads(json.dumps(item),
                      object_hook=lambda d:
                      namedtuple(object_name, fields)(*values))

class TiingoClient(RestClient):
    """Class for managing interactions with the Tiingo REST API

        Supply API Key via Environment Variable TIINGO_API_KEY
        or via the Config Object
    """

    def __init__(self, *args, **kwargs):
        super(TiingoClient, self).__init__(*args, **kwargs)
        self._base_url = "https://api.tiingo.com"

        try:
            api_key = self._config['api_key']
        except KeyError:
            api_key = os.environ.get('TIINGO_API_KEY')
        assert(api_key)

        self._headers = {
            'Authorization': "Token {}".format(api_key),
            'Content-Type': 'application/json',
            'User-Agent': 'tiingo-python-client {}'.format(VERSION)
        }

    def __repr__(self):
        return '<TiingoClient(url="{}")>'.format(self._base_url)

    # TICKER PRICE ENDPOINTS
    # https://api.tiingo.com/docs/tiingo/daily
    def list_stock_tickers(self):
        """Return a list of dicts of metadata tickers for all supported Stocks
            as well as metadata about each ticker. This includes supported
            date range, the exchange the ticker is traded on, and the currency
            the stock is traded on.
           Tickers for unrelated products are omitted.
           https://apimedia.tiingo.com/docs/tiingo/daily/supported_tickers.zip
           """
        listing_file_url = "https://apimedia.tiingo.com/docs/tiingo/daily/supported_tickers.zip"
        response = requests.get(listing_file_url)
        zipdata = get_zipfile_from_response(response)
        raw_csv = get_buffer_from_zipfile(zipdata, 'supported_tickers.csv')
        reader = csv.DictReader(raw_csv)

        return [row for row in reader
                if row.get('assetType') == 'Stock']

    def get_ticker_metadata(self, ticker, fmt='json'):
        """Return metadata for 1 ticker
           Use TiingoClient.list_tickers() to get available options

            Args:
                ticker (str) : Unique identifier for stock
        """
        url = "tiingo/daily/{}".format(ticker)
        response = self._request('GET', url)
        data = response.json()
        if fmt == 'json':
            return data
        elif fmt == 'object':
            return dict_to_object(data, "Ticker")

    def get_ticker_price(self, ticker,
                         startDate=None, endDate=None,
                         fmt='json', frequency='daily'):
        """By default, return latest EOD Composite Price for a stock ticker.
           On average, each feed contains 3 data sources.

            Supported tickers + Available Day Ranges are here:
            https://apimedia.tiingo.com/docs/tiingo/daily/supported_tickers.zip

            Args:
                ticker (string): Unique identifier for stock ticker
                startDate (string): Start of ticker range in YYYY-MM-DD format
                endDate (string): End of ticker range in YYYY-MM-DD format
                fmt (string): 'csv' or 'json'
                frequency (string): Resample frequency
        """
        url = "tiingo/daily/{}/prices".format(ticker)
        params = {
            'format': fmt,
            'frequency': frequency
        }

        if startDate:
            params['startDate'] = startDate
        if endDate:
            params['endDate'] = endDate

        # TODO: evaluate whether to stream CSV to cache on disk, or
        # load as array in memory, or just pass plain text
        response = self._request('GET', url, params=params)
        if fmt == "json":
            return response.json()
        else:
            return response.content.decode("utf-8")

    # NEWS FEEDS
    # tiingo/news
    def get_news(self, tickers=[], tags=[], sources=[], startDate=None,
                 endDate=None, limit=100, offset=0, sortBy="publishedDate",
                 fmt='json'):
        """Return list of news articles matching given search terms
            https://api.tiingo.com/docs/tiingo/news

            # Dates are in YYYY-MM-DD Format.

            Args:
                tickers [string] : List of unique Stock Tickers to search
                tags [string] : List of topics tagged by Tiingo Algorithms
                sources [string]: List of base urls to include as news sources
                startDate, endDate [date]: Boundaries of news search window
                limit (int): Max results returned. Default 100, max 1000
                offset (int): Search results offset, used for paginating
                sortBy (string): "publishedDate" OR (#TODO: UPDATE THIS)
        """
        url = "tiingo/news"
        params = {
            'limit': limit,
            'offset': offset,
            'sortBy': sortBy,
            'tickers': tickers,
            'sources': sources,
            'tags': tags,
            'startDate': startDate,
            'endDate': endDate
        }
        response = self._request('GET', url, params=params)
        data = response.json()
        if fmt == 'json':
            return data
        elif fmt == 'object':
            obj_arr = []
            for el in data:
                # inspired by https://stackoverflow.com/a/15882054
                arr_el = dict_to_object(el, "NewsArticle")
                obj_arr.append(arr_el)
            return obj_arr

    def get_bulk_news(self, file_id=None, fmt='json'):
        """Only available to institutional clients.
            If ID is NOT provided, return array of available file_ids.
            If ID is provided, provides URL which you can use to download your
            file, as well as some metadata about that file.
        """
        if file_id:
            url = "tiingo/news/bulk_download/{}".format(file_id)
        else:
            url = "tiingo/news/bulk_download"

        response = self._request('GET', url)
        data = response.json()
        if fmt == 'json':
            return data
        elif fmt == 'object':
            return dict_to_object(data, "BulkNews")
<|endoftext|>