<commit_before>from __future__ import absolute_import

import collections
import numpy as np
from warnings import warn
from jinja2 import Environment, PackageLoader

from . import defaults
from .basemaps import Basemaps
from .source import SourceType
from .. import utils

# TODO: refactor

class Map(object):
    """Map

    Args:
        layers (list of Layer-types): List of layers. One or more of
          :py:class:`Layer <cartoframes.viz.Layer>`.
        basemap (str):
          - if a `str`, name of a CARTO vector basemap. One of `positron`,
            `voyager`, or `darkmatter` from the :obj:`BaseMaps` class
          - if a `dict`, Mapbox or other style as the value of the `style` key.
            If a Mapbox style, the access token is the value of the `token`
            key.
        bounds (dict or list): a dict with `east`,`north`,`west`,`south`
          properties, or a list of floats in the following order: [west,
          south, east, north]. If not provided the bounds will be automatically
          calculated to fit all features.
        size (tuple of int): a (width, height) pair for the size of the map.
          Default is (1024, 632)

    Example:

        .. code::

            from cartoframes.auth import Context, set_default_context
            from cartoframes.viz import Map, Layer

            context = Context(
                base_url='https://your_user_name.carto.com',
                api_key='your api key'
            )
            set_default_context(context)

            Map(Layer('table in your account'))

        CARTO basemap style.

        .. code::

            from cartoframes.auth import Context, set_default_context
            from cartoframes.viz import Map, Layer, basemaps

            context = Context(
                base_url='https://your_user_name.carto.com',
                api_key='your api key'
            )
            set_default_context(context)

            Map(
                Layer('table in your account'),
                basemaps.darkmatter
            )

        Custom basemap style. Here we use the Mapbox streets style, which
        requires an access token.

        .. code::

            from cartoframes.auth import Context, set_default_context
            from cartoframes.viz import Map, Layer, basemaps

            context = Context(
                base_url='https://your_user_name.carto.com',
                api_key='your api key'
            )
            set_default_context(context)

            basemap = {
                'style': 'mapbox://styles/mapbox/streets-v9',
                'token: '<your mapbox token>'
            }

            Map(
                Layer('table in your account'),
                basemap
            )

        Color basemap style.

        .. code::

            from cartoframes.auth import Context, set_default_context
            from cartoframes.viz import Map, Layer, basemaps

            context = Context(
                base_url='https://your_user_name.carto.com',
                api_key='your api key'
            )
            set_default_context(context)

            Map(
                Layer('table in your account'),
                basemap='yellow'  # None, False, 'white', 'rgb(255, 255, 0)'
            )

        Custom bounds.

        .. code::

            from cartoframes.auth import Context, set_default_context
            from cartoframes.viz import Map, Layer

            context = Context(
                base_url='https://your_user_name.carto.com',
                api_key='your api key'
            )
            set_default_context(context)

            bounds = {
                'west': -10,
                'east': 10,
                'north': -10,
                'south': 10
            }

            Map(
                Layer('table in your account'),
                bounds=bounds
            )
    """

    def __init__(self,
                 layers=None,
                 basemap=Basemaps.darkmatter,
                 bounds=None,
                 size=None,
                 viewport=None,
                 template=None,
                 default_legend=None,
                 **kwargs):

        self.layers = _init_layers(layers)
        self.basemap = basemap
        self.size = size
        self.viewport = viewport
        self.template = template
        self.sources = _get_map_layers(self.layers)
        self.bounds = _get_bounds(bounds, self.layers)
        self._carto_vl_path = kwargs.get('_carto_vl_path', defaults.CARTO_VL_PATH)
        self._airship_path = kwargs.get('_airship_path', None)
        self._htmlMap = HTMLMap()

        if default_legend is None and all(layer.legend is None for layer in self.layers):
            self.default_legend = True
        else:
            self.default_legend = default_legend

        self._htmlMap.set_content(
            size=self.size,
            sources=self.sources,
            bounds=self.bounds,
            viewport=self.viewport,
            basemap=self.basemap,
            default_legend=self.default_legend,
            _carto_vl_path=self._carto_vl_path,
            _airship_path=self._airship_path)

    def _repr_html_(self):
        return self._htmlMap.html

    def publish(self):
        pass

def _get_bounds(bounds, layers):
    return (
        _format_bounds(bounds)
        if bounds
        else _get_super_bounds(layers)
    )

def _init_layers(layers):
    if layers is None:
        return None
    if not isinstance(layers, collections.Iterable):
        return [layers]
    else:
        # Reverse layers
        return layers[::-1]

def _get_map_layers(layers):
    if layers is None:
        return None
    return list(map(_set_map_layer, layers))

def _set_map_layer(layer):
    return {
        'credentials': layer.source.credentials,
        'interactivity': layer.interactivity,
        'legend': layer.legend,
        'query': layer.source.query,
        'type': layer.source.type,
        'viz': layer.viz
    }

def _format_bounds(bounds):
    if isinstance(bounds, dict):
        return _dict_bounds(bounds)

    return _list_bounds(bounds)

def _list_bounds(bounds):
    if len(bounds) != 4:
        raise ValueError('bounds list must have exactly four values in the '
                         'order: [west, south, east, north]')

    return _dict_bounds({
        'west': bounds[0],
        'south': bounds[1],
        'east': bounds[2],
        'north': bounds[3]
    })

def _dict_bounds(bounds):
    if 'west' not in bounds or 'east' not in bounds or 'north' not in bounds\
            or 'south' not in bounds:
        raise ValueError('bounds must have east, west, north and '
                         'south properties')

    return '[[{west}, {south}], [{east}, {north}]]'.format(**bounds)

def _get_super_bounds(layers):
    """"""
    # TODO: refactor this method:
    # Compute the bounds in the source class
    if layers:
        hosted_layers = [
            layer for layer in layers
            if layer.source.type != SourceType.GEOJSON
        ]
        local_layers = [
            layer for layer in layers
            if layer.source.type == SourceType.GEOJSON
        ]
    else:
        hosted_layers = []
        local_layers = []

    hosted_bounds = dict.fromkeys(['west', 'south', 'east', 'north'])
    local_bounds = dict.fromkeys(['west', 'south', 'east', 'north'])

    if hosted_layers:
        hosted_bounds = _get_bounds_hosted(hosted_layers)

    if local_layers:
        local_bounds = _get_bounds_local(local_layers)

    bounds = _combine_bounds(hosted_bounds, local_bounds)

    return _format_bounds(bounds)

def _get_bounds_local(layers):
    """Aggregates bounding boxes of all local layers

        return: dict of bounding box of all bounds in layers
    """
    if not layers:
        return {'west': None, 'south': None, 'east': None, 'north': None}

    bounds = layers[0].bounds

    for layer in layers[1:]:
        bounds = np.concatenate(
            (
                np.minimum(
                    bounds[:2],
                    layer.bounds[:2]
                ),
                np.maximum(
                    bounds[2:],
                    layer.bounds[2:]
                )
            )
        )

    if bounds is None:
        return {'west': None, 'south': None, 'east': None, 'north': None}

    return dict(zip(['west', 'south', 'east', 'north'], bounds))

def _get_bounds_hosted(layers):
    """Aggregates bounding boxes of all hosted layers

        return: dict of bounding box of all bounds in layers
    """
    if not layers:
        return {'west': None, 'south': None, 'east': None, 'north': None}

    context = layers[0].source.context
    bounds = context and context._get_bounds([layers[0]])

    for layer in layers[1:]:
        context = layer.source.context
        next_bounds = context and context._get_bounds([layer])
        bounds = _combine_bounds(bounds, next_bounds)

    return bounds

def _combine_bounds(bbox1, bbox2):
    """Takes two bounding boxes dicts and gives a new bbox that encompasses
    them both"""
    WORLD = {'west': -180, 'south': -85.1, 'east': 180, 'north': 85.1}
    ALL_KEYS = set(WORLD.keys())

    # if neither are defined, use the world
    if not bbox1 and not bbox2:
        return WORLD
    # if all nones, use the world
    if _dict_all_nones(bbox1) and _dict_all_nones(bbox2):
        return WORLD

    # if one is none return the other
    if not bbox1 or not bbox2:
        return bbox1 or bbox2

    assert ALL_KEYS == set(bbox1.keys()) and ALL_KEYS == set(bbox2.keys()),\
        'Input bounding boxes must have the same dictionary keys'
    # create dict with cardinal directions and None-valued keys
    outbbox = dict.fromkeys(['west', 'south', 'east', 'north'])

    # set values and/or defaults
    for coord in ('north', 'east'):
        outbbox[coord] = np.nanmax([
            _conv2nan(bbox1[coord]),
            _conv2nan(bbox2[coord])
        ])
    for coord in ('south', 'west'):
        outbbox[coord] = np.nanmin([
            _conv2nan(bbox1[coord]),
            _conv2nan(bbox2[coord])
        ])

    return outbbox

def _dict_all_nones(bbox_dict):
    """Returns True if all dict values are None"""
    return bbox_dict and all(v is None for v in bbox_dict.values())

def _conv2nan(val):
    """convert Nones to np.nans"""
    return np.nan if val is None else val

class HTMLMap(object):
    def __init__(self):
        self.width = None
        self.height = None
        self.srcdoc = None

        self._env = Environment(
            loader=PackageLoader('cartoframes', 'assets/templates'),
            autoescape=True
        )

        self._env.filters['quot'] = _quote_filter
        self._env.filters['iframe_size'] = _iframe_size_filter
        self._env.filters['clear_none'] = _clear_none_filter

        self.html = None
        self._template = self._env.get_template('viz/basic.html.j2')

    def set_content(
        self, size, sources, bounds, viewport=None, basemap=None,
            default_legend=None,
            _carto_vl_path=defaults.CARTO_VL_PATH, _airship_path=None):

        self.html = self._parse_html_content(
            size, sources, bounds, viewport, basemap, default_legend,
            _carto_vl_path, _airship_path)

    def _parse_html_content(
        self, size, sources, bounds, viewport, basemap=None, default_legend=None,
            _carto_vl_path=defaults.CARTO_VL_PATH, _airship_path=None):

        token = ''
        basecolor = ''

        if basemap is None:
            # No basemap
            basecolor = 'white'
            basemap = ''
        elif isinstance(basemap, bool):
            if basemap is True:
                # Default basemap
                basemap = Basemaps.darkmatter
            else:
                # No basemap
                basecolor = 'white'
                basemap = ''
        elif isinstance(basemap, str):
            if basemap not in [Basemaps.voyager, Basemaps.positron, Basemaps.darkmatter]:
                # Basemap is a color
                basecolor = basemap
                basemap = ''
        elif isinstance(basemap, dict):
            token = basemap.get('token', '')
            if 'style' in basemap:
                basemap = basemap.get('style')
                if not token and basemap.get('style').startswith('mapbox://'):
                    warn('A Mapbox style usually needs a token')
            else:
                raise ValueError(
                    'If basemap is a dict, it must have a `style` key'
                )

        if (_airship_path is None):
            airship_components_path = defaults.AIRSHIP_COMPONENTS_PATH
            airship_bridge_path = defaults.AIRSHIP_BRIDGE_PATH
            airship_styles_path = defaults.AIRSHIP_STYLES_PATH
            airship_icons_path = defaults.AIRSHIP_ICONS_PATH
        else:
            airship_components_path = _airship_path + defaults.AIRSHIP_SCRIPT
            airship_bridge_path = _airship_path + defaults.AIRSHIP_BRIDGE_SCRIPT
            airship_styles_path = _airship_path + defaults.AIRSHIP_STYLE
            airship_icons_path = _airship_path + defaults.AIRSHIP_ICONS_STYLE

        camera = None
        if viewport is not None:
            camera = {
                'center': _get_center(viewport),
                'zoom': viewport.get('zoom'),
                'bearing': viewport.get('bearing'),
                'pitch': viewport.get('pitch')
            }

        has_legends = any(source['legend'] is not None for source in sources) or default_legend

        return self._template.render(
            width=size[0] if size is not None else None,
            height=size[1] if size is not None else None,
            sources=sources,
            basemap=basemap,
            basecolor=basecolor,
            mapboxtoken=token,
            bounds=bounds,
            camera=camera,
            has_legends=has_legends,
            default_legend=default_legend,
            carto_vl_path=_carto_vl_path,
            airship_components_path=airship_components_path,
            airship_bridge_path=airship_bridge_path,
            airship_styles_path=airship_styles_path,
            airship_icons_path=airship_icons_path
        )

    def _repr_html_(self):
        return self.html

def _quote_filter(value):
    return utils.safe_quotes(value.unescape())

def _iframe_size_filter(value):
    if isinstance(value, str):
        return value

    return '%spx;' % value

def _clear_none_filter(value):
    return dict(filter(lambda item: item[1] is not None, value.items()))

def _get_center(center):
    if 'lng' not in center or 'lat' not in center:
        return None

    return [center.get('lng'), center.get('lat')]
<commit_msg>Fix layers=None case<commit_after>from __future__ import absolute_import

import collections
import numpy as np
from warnings import warn
from jinja2 import Environment, PackageLoader

from . import defaults
from .basemaps import Basemaps
from .source import SourceType
from .. import utils

# TODO: refactor

class Map(object):
    """Map

    Args:
        layers (list of Layer-types): List of layers. One or more of
          :py:class:`Layer <cartoframes.viz.Layer>`.
        basemap (str):
          - if a `str`, name of a CARTO vector basemap. One of `positron`,
            `voyager`, or `darkmatter` from the :obj:`BaseMaps` class
          - if a `dict`, Mapbox or other style as the value of the `style` key.
            If a Mapbox style, the access token is the value of the `token`
            key.
        bounds (dict or list): a dict with `east`,`north`,`west`,`south`
          properties, or a list of floats in the following order: [west,
          south, east, north]. If not provided the bounds will be automatically
          calculated to fit all features.
        size (tuple of int): a (width, height) pair for the size of the map.
          Default is (1024, 632)

    Example:

        .. code::

            from cartoframes.auth import Context, set_default_context
            from cartoframes.viz import Map, Layer

            context = Context(
                base_url='https://your_user_name.carto.com',
                api_key='your api key'
            )
            set_default_context(context)

            Map(Layer('table in your account'))

        CARTO basemap style.

        .. code::

            from cartoframes.auth import Context, set_default_context
            from cartoframes.viz import Map, Layer, basemaps

            context = Context(
                base_url='https://your_user_name.carto.com',
                api_key='your api key'
            )
            set_default_context(context)

            Map(
                Layer('table in your account'),
                basemaps.darkmatter
            )

        Custom basemap style. Here we use the Mapbox streets style, which
        requires an access token.

        .. code::

            from cartoframes.auth import Context, set_default_context
            from cartoframes.viz import Map, Layer, basemaps

            context = Context(
                base_url='https://your_user_name.carto.com',
                api_key='your api key'
            )
            set_default_context(context)

            basemap = {
                'style': 'mapbox://styles/mapbox/streets-v9',
                'token: '<your mapbox token>'
            }

            Map(
                Layer('table in your account'),
                basemap
            )

        Color basemap style.

        .. code::

            from cartoframes.auth import Context, set_default_context
            from cartoframes.viz import Map, Layer, basemaps

            context = Context(
                base_url='https://your_user_name.carto.com',
                api_key='your api key'
            )
            set_default_context(context)

            Map(
                Layer('table in your account'),
                basemap='yellow'  # None, False, 'white', 'rgb(255, 255, 0)'
            )

        Custom bounds.

        .. code::

            from cartoframes.auth import Context, set_default_context
            from cartoframes.viz import Map, Layer

            context = Context(
                base_url='https://your_user_name.carto.com',
                api_key='your api key'
            )
            set_default_context(context)

            bounds = {
                'west': -10,
                'east': 10,
                'north': -10,
                'south': 10
            }

            Map(
                Layer('table in your account'),
                bounds=bounds
            )
    """

    def __init__(self,
                 layers=None,
                 basemap=Basemaps.darkmatter,
                 bounds=None,
                 size=None,
                 viewport=None,
                 template=None,
                 default_legend=None,
                 **kwargs):

        self.layers = _init_layers(layers)
        self.basemap = basemap
        self.size = size
        self.viewport = viewport
        self.template = template
        self.sources = _get_map_layers(self.layers)
        self.bounds = _get_bounds(bounds, self.layers)
        self._carto_vl_path = kwargs.get('_carto_vl_path', defaults.CARTO_VL_PATH)
        self._airship_path = kwargs.get('_airship_path', None)
        self._htmlMap = HTMLMap()

        if default_legend is None and all(layer.legend is None for layer in self.layers):
            self.default_legend = True
        else:
            self.default_legend = default_legend

        self._htmlMap.set_content(
            size=self.size,
            sources=self.sources,
            bounds=self.bounds,
            viewport=self.viewport,
            basemap=self.basemap,
            default_legend=self.default_legend,
            _carto_vl_path=self._carto_vl_path,
            _airship_path=self._airship_path)

    def _repr_html_(self):
        return self._htmlMap.html

    def publish(self):
        pass

def _get_bounds(bounds, layers):
    return (
        _format_bounds(bounds)
        if bounds
        else _get_super_bounds(layers)
    )

def _init_layers(layers):
    if layers is None:
        return []
    if not isinstance(layers, collections.Iterable):
        return [layers]
    else:
        # Reverse layers
        return layers[::-1]

def _get_map_layers(layers):
    if layers is None:
        return None
    return list(map(_set_map_layer, layers))

def _set_map_layer(layer):
    return {
        'credentials': layer.source.credentials,
        'interactivity': layer.interactivity,
        'legend': layer.legend,
        'query': layer.source.query,
        'type': layer.source.type,
        'viz': layer.viz
    }

def _format_bounds(bounds):
    if isinstance(bounds, dict):
        return _dict_bounds(bounds)

    return _list_bounds(bounds)

def _list_bounds(bounds):
    if len(bounds) != 4:
        raise ValueError('bounds list must have exactly four values in the '
                         'order: [west, south, east, north]')

    return _dict_bounds({
        'west': bounds[0],
        'south': bounds[1],
        'east': bounds[2],
        'north': bounds[3]
    })

def _dict_bounds(bounds):
    if 'west' not in bounds or 'east' not in bounds or 'north' not in bounds\
            or 'south' not in bounds:
        raise ValueError('bounds must have east, west, north and '
                         'south properties')

    return '[[{west}, {south}], [{east}, {north}]]'.format(**bounds)

def _get_super_bounds(layers):
    """"""
    # TODO: refactor this method:
    # Compute the bounds in the source class
    if layers:
        hosted_layers = [
            layer for layer in layers
            if layer.source.type != SourceType.GEOJSON
        ]
        local_layers = [
            layer for layer in layers
            if layer.source.type == SourceType.GEOJSON
        ]
    else:
        hosted_layers = []
        local_layers = []

    hosted_bounds = dict.fromkeys(['west', 'south', 'east', 'north'])
    local_bounds = dict.fromkeys(['west', 'south', 'east', 'north'])

    if hosted_layers:
        hosted_bounds = _get_bounds_hosted(hosted_layers)

    if local_layers:
        local_bounds = _get_bounds_local(local_layers)

    bounds = _combine_bounds(hosted_bounds, local_bounds)

    return _format_bounds(bounds)

def _get_bounds_local(layers):
    """Aggregates bounding boxes of all local layers

        return: dict of bounding box of all bounds in layers
    """
    if not layers:
        return {'west': None, 'south': None, 'east': None, 'north': None}

    bounds = layers[0].bounds

    for layer in layers[1:]:
        bounds = np.concatenate(
            (
                np.minimum(
                    bounds[:2],
                    layer.bounds[:2]
                ),
                np.maximum(
                    bounds[2:],
                    layer.bounds[2:]
                )
            )
        )

    if bounds is None:
        return {'west': None, 'south': None, 'east': None, 'north': None}

    return dict(zip(['west', 'south', 'east', 'north'], bounds))

def _get_bounds_hosted(layers):
    """Aggregates bounding boxes of all hosted layers

        return: dict of bounding box of all bounds in layers
    """
    if not layers:
        return {'west': None, 'south': None, 'east': None, 'north': None}

    context = layers[0].source.context
    bounds = context and context._get_bounds([layers[0]])

    for layer in layers[1:]:
        context = layer.source.context
        next_bounds = context and context._get_bounds([layer])
        bounds = _combine_bounds(bounds, next_bounds)

    return bounds

def _combine_bounds(bbox1, bbox2):
    """Takes two bounding boxes dicts and gives a new bbox that encompasses
    them both"""
    WORLD = {'west': -180, 'south': -85.1, 'east': 180, 'north': 85.1}
    ALL_KEYS = set(WORLD.keys())

    # if neither are defined, use the world
    if not bbox1 and not bbox2:
        return WORLD
    # if all nones, use the world
    if _dict_all_nones(bbox1) and _dict_all_nones(bbox2):
        return WORLD

    # if one is none return the other
    if not bbox1 or not bbox2:
        return bbox1 or bbox2

    assert ALL_KEYS == set(bbox1.keys()) and ALL_KEYS == set(bbox2.keys()),\
        'Input bounding boxes must have the same dictionary keys'
    # create dict with cardinal directions and None-valued keys
    outbbox = dict.fromkeys(['west', 'south', 'east', 'north'])

    # set values and/or defaults
    for coord in ('north', 'east'):
        outbbox[coord] = np.nanmax([
            _conv2nan(bbox1[coord]),
            _conv2nan(bbox2[coord])
        ])
    for coord in ('south', 'west'):
        outbbox[coord] = np.nanmin([
            _conv2nan(bbox1[coord]),
            _conv2nan(bbox2[coord])
        ])

    return outbbox

def _dict_all_nones(bbox_dict):
    """Returns True if all dict values are None"""
    return bbox_dict and all(v is None for v in bbox_dict.values())

def _conv2nan(val):
    """convert Nones to np.nans"""
    return np.nan if val is None else val

class HTMLMap(object):
    def __init__(self):
        self.width = None
        self.height = None
        self.srcdoc = None

        self._env = Environment(
            loader=PackageLoader('cartoframes', 'assets/templates'),
            autoescape=True
        )

        self._env.filters['quot'] = _quote_filter
        self._env.filters['iframe_size'] = _iframe_size_filter
        self._env.filters['clear_none'] = _clear_none_filter

        self.html = None
        self._template = self._env.get_template('viz/basic.html.j2')

    def set_content(
        self, size, sources, bounds, viewport=None, basemap=None,
            default_legend=None,
            _carto_vl_path=defaults.CARTO_VL_PATH, _airship_path=None):

        self.html = self._parse_html_content(
            size, sources, bounds, viewport, basemap, default_legend,
            _carto_vl_path, _airship_path)

    def _parse_html_content(
        self, size, sources, bounds, viewport, basemap=None, default_legend=None,
            _carto_vl_path=defaults.CARTO_VL_PATH, _airship_path=None):

        token = ''
        basecolor = ''

        if basemap is None:
            # No basemap
            basecolor = 'white'
            basemap = ''
        elif isinstance(basemap, bool):
            if basemap is True:
                # Default basemap
                basemap = Basemaps.darkmatter
            else:
                # No basemap
                basecolor = 'white'
                basemap = ''
        elif isinstance(basemap, str):
            if basemap not in [Basemaps.voyager, Basemaps.positron, Basemaps.darkmatter]:
                # Basemap is a color
                basecolor = basemap
                basemap = ''
        elif isinstance(basemap, dict):
            token = basemap.get('token', '')
            if 'style' in basemap:
                basemap = basemap.get('style')
                if not token and basemap.get('style').startswith('mapbox://'):
                    warn('A Mapbox style usually needs a token')
            else:
                raise ValueError(
                    'If basemap is a dict, it must have a `style` key'
                )

        if (_airship_path is None):
            airship_components_path = defaults.AIRSHIP_COMPONENTS_PATH
            airship_bridge_path = defaults.AIRSHIP_BRIDGE_PATH
            airship_styles_path = defaults.AIRSHIP_STYLES_PATH
            airship_icons_path = defaults.AIRSHIP_ICONS_PATH
        else:
            airship_components_path = _airship_path + defaults.AIRSHIP_SCRIPT
            airship_bridge_path = _airship_path + defaults.AIRSHIP_BRIDGE_SCRIPT
            airship_styles_path = _airship_path + defaults.AIRSHIP_STYLE
            airship_icons_path = _airship_path + defaults.AIRSHIP_ICONS_STYLE

        camera = None
        if viewport is not None:
            camera = {
                'center': _get_center(viewport),
                'zoom': viewport.get('zoom'),
                'bearing': viewport.get('bearing'),
                'pitch': viewport.get('pitch')
            }

        has_legends = any(source['legend'] is not None for source in sources) or default_legend

        return self._template.render(
            width=size[0] if size is not None else None,
            height=size[1] if size is not None else None,
            sources=sources,
            basemap=basemap,
            basecolor=basecolor,
            mapboxtoken=token,
            bounds=bounds,
            camera=camera,
            has_legends=has_legends,
            default_legend=default_legend,
            carto_vl_path=_carto_vl_path,
            airship_components_path=airship_components_path,
            airship_bridge_path=airship_bridge_path,
            airship_styles_path=airship_styles_path,
            airship_icons_path=airship_icons_path
        )

    def _repr_html_(self):
        return self.html

def _quote_filter(value):
    return utils.safe_quotes(value.unescape())

def _iframe_size_filter(value):
    if isinstance(value, str):
        return value

    return '%spx;' % value

def _clear_none_filter(value):
    return dict(filter(lambda item: item[1] is not None, value.items()))

def _get_center(center):
    if 'lng' not in center or 'lat' not in center:
        return None

    return [center.get('lng'), center.get('lat')]
<|endoftext|>


<commit_before># Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Provides the web interface for displaying an overview of jobs."""
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import

import logging
import json
import webapp2

from dashboard.pinpoint.models import job as job_module
from dashboard.common import utils
from google.appengine.datastore import datastore_query

_BATCH_FETCH_TIMEOUT = 200
_MAX_JOBS_TO_FETCH = 100
_MAX_JOBS_TO_COUNT = 1000
_DEFAULT_FILTERED_JOBS = 40

class Error(Exception):
  pass

class InvalidInput(Error):
  pass

class Jobs(webapp2.RequestHandler):
  """Shows an overview of recent anomalies for perf sheriffing."""

  def get(self):
    try:
      self.response.out.write(
          json.dumps(
              _GetJobs(
                  self.request.get_all('o'), self.request.get_all('filter'))))
    except InvalidInput as e:
      self.response.set_status(400)
      logging.exception(e)
      self.response.out.write(json.dumps({'error': e.message}))

def _GetJobs(options, query_filter):
  query = job_module.Job.query()

  # Query filters should a string as described in https://google.aip.dev/160
  # We implement a simple parser for the query_filter provided, to allow us to
  # support a simple expression language expressed in the AIP.
  # FIXME: Implement a validating parser.
  def _ParseExpressions():
    # Yield tokens as we parse them.
    # We only support 'AND' as a keyword and ignore any 'OR's.
    for q in query_filter:
      parts = q.split(' ')
      for p in parts:
        if p == 'AND':
          continue
        yield p

  has_filter = False
  has_batch_filter = False
  for f in _ParseExpressions():
    if f.startswith('user='):
      has_filter = True
      query = query.filter(job_module.Job.user == f[len('user='):])
    elif f.startswith('configuration='):
      has_filter = True
      query = query.filter(
          job_module.Job.configuration == f[len('configuration='):])
    elif f.startswith('comparison_mode='):
      has_filter = True
      query = query.filter(
          job_module.Job.comparison_mode == f[len('comparison_mode='):])
    elif f.startswith('batch_id='):
      has_batch_filter = True
      batch_id = f[len('batch_id='):]
      if not batch_id:
        raise InvalidInput('batch_id when specified must not be empty')
      query = query.filter(job_module.Job.batch_id == batch_id)

  if has_filter and has_batch_filter:
    raise InvalidInput('batch ids are mutually exclusive with job filters')

  query = query.order(-job_module.Job.created)
  limit = None
  timeout_qo = datastore_query.QueryOptions()
  if has_batch_filter:
    timeout_qo = datastore_query.QueryOptions(deadline=_BATCH_FETCH_TIMEOUT)
  else:
    limit = _MAX_JOBS_TO_FETCH if not has_filter else _DEFAULT_FILTERED_JOBS

  job_future = query.fetch_async(limit=limit, options=timeout_qo)
  count_future = query.count_async(limit=_MAX_JOBS_TO_COUNT, options=timeout_qo)

  result = {
      'jobs': [],
      'count': count_future.get_result(),
      'max_count': _MAX_JOBS_TO_COUNT
  }

  jobs = job_future.get_result()
  service_account_email = utils.ServiceAccountEmail()
  logging.debug('service account email = %s', service_account_email)

  def _FixupEmails(j):
    user = j.get('user')
    if user and user == service_account_email:
      j['user'] = 'chromeperf (automation)'
    return j

  for job in jobs:
    result['jobs'].append(_FixupEmails(job.AsDict(options)))

  return result
<commit_msg>Pinpoint UI - display more jobs on the main page<commit_after># Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Provides the web interface for displaying an overview of jobs."""
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import

import logging
import json
import webapp2

from dashboard.pinpoint.models import job as job_module
from dashboard.common import utils
from google.appengine.datastore import datastore_query

_BATCH_FETCH_TIMEOUT = 200
_MAX_JOBS_TO_FETCH = 1000
_MAX_JOBS_TO_COUNT = 1000
_DEFAULT_FILTERED_JOBS = 40

class Error(Exception):
  pass

class InvalidInput(Error):
  pass

class Jobs(webapp2.RequestHandler):
  """Shows an overview of recent anomalies for perf sheriffing."""

  def get(self):
    try:
      self.response.out.write(
          json.dumps(
              _GetJobs(
                  self.request.get_all('o'), self.request.get_all('filter'))))
    except InvalidInput as e:
      self.response.set_status(400)
      logging.exception(e)
      self.response.out.write(json.dumps({'error': e.message}))

def _GetJobs(options, query_filter):
  query = job_module.Job.query()

  # Query filters should a string as described in https://google.aip.dev/160
  # We implement a simple parser for the query_filter provided, to allow us to
  # support a simple expression language expressed in the AIP.
  # FIXME: Implement a validating parser.
  def _ParseExpressions():
    # Yield tokens as we parse them.
    # We only support 'AND' as a keyword and ignore any 'OR's.
    for q in query_filter:
      parts = q.split(' ')
      for p in parts:
        if p == 'AND':
          continue
        yield p

  has_filter = False
  has_batch_filter = False
  for f in _ParseExpressions():
    if f.startswith('user='):
      has_filter = True
      query = query.filter(job_module.Job.user == f[len('user='):])
    elif f.startswith('configuration='):
      has_filter = True
      query = query.filter(
          job_module.Job.configuration == f[len('configuration='):])
    elif f.startswith('comparison_mode='):
      has_filter = True
      query = query.filter(
          job_module.Job.comparison_mode == f[len('comparison_mode='):])
    elif f.startswith('batch_id='):
      has_batch_filter = True
      batch_id = f[len('batch_id='):]
      if not batch_id:
        raise InvalidInput('batch_id when specified must not be empty')
      query = query.filter(job_module.Job.batch_id == batch_id)

  if has_filter and has_batch_filter:
    raise InvalidInput('batch ids are mutually exclusive with job filters')

  query = query.order(-job_module.Job.created)
  limit = None
  timeout_qo = datastore_query.QueryOptions()
  if has_batch_filter:
    timeout_qo = datastore_query.QueryOptions(deadline=_BATCH_FETCH_TIMEOUT)
  else:
    limit = _MAX_JOBS_TO_FETCH if not has_filter else _DEFAULT_FILTERED_JOBS

  job_future = query.fetch_async(limit=limit, options=timeout_qo)
  count_future = query.count_async(limit=_MAX_JOBS_TO_COUNT, options=timeout_qo)

  result = {
      'jobs': [],
      'count': count_future.get_result(),
      'max_count': _MAX_JOBS_TO_COUNT
  }

  jobs = job_future.get_result()
  service_account_email = utils.ServiceAccountEmail()
  logging.debug('service account email = %s', service_account_email)

  def _FixupEmails(j):
    user = j.get('user')
    if user and user == service_account_email:
      j['user'] = 'chromeperf (automation)'
    return j

  for job in jobs:
    result['jobs'].append(_FixupEmails(job.AsDict(options)))

  return result
<|endoftext|>


<commit_before>"""
Context-dependent integration task, based on

  Context-dependent computation by recurrent dynamics in prefrontal cortex.
  V Mante, D Sussillo, KV Shinoy, & WT Newsome, Nature 2013.

  http://dx.doi.org/10.1038/nature12742

"""
from __future__ import division

import numpy as np

from pyrl import tasktools

# Inputs
inputs = tasktools.to_map('MOTION', 'COLOR',
                          'MOTION-LEFT', 'MOTION-RIGHT',
                          'COLOR-LEFT', 'COLOR-RIGHT')

# Actions
actions = tasktools.to_map('FIXATE', 'CHOOSE-LEFT', 'CHOOSE-RIGHT')

# Trial conditions
contexts     = ['m', 'c']
left_rights  = [-1, 1]
cohs         = [5, 15, 50]
n_conditions = len(contexts) * (len(left_rights)*len(cohs))**2

# Training
n_gradient   = 2*n_conditions
n_validation = 50*n_conditions

# Input noise
sigma = np.sqrt(2*100*0.02)

# Rewards
R_ABORTED = -1
R_CORRECT = +1

# Epoch durations
fixation   = 750
stimulus   = 750
delay_min  = 300
delay_mean = 300
delay_max  = 1200
decision   = 500
tmax       = fixation + stimulus + delay_min + delay_max + decision

def get_condition(rng, dt, context={}):
    #-------------------------------------------------------------------------------------
    # Epochs
    #-------------------------------------------------------------------------------------

    delay = context.get('delay')
    if delay is None:
        delay = delay_min + tasktools.truncated_exponential(rng, dt, delay_mean,
                                                            xmax=delay_max)

    durations = {
        'fixation':  (0, fixation),
        'stimulus':  (fixation, fixation + stimulus),
        'delay':     (fixation + stimulus, fixation + stimulus + delay),
        'decision':  (fixation + stimulus + delay, tmax),
        'tmax':      tmax
        }
    time, epochs = tasktools.get_epochs_idx(dt, durations)

    #-------------------------------------------------------------------------------------
    # Trial
    #-------------------------------------------------------------------------------------

    context_ = context.get('context')
    if context_ is None:
        context_ = rng.choice(contexts)

    left_right_m = context.get('left_right_m')
    if left_right_m is None:
        left_right_m = rng.choice(left_rights)

    left_right_c = context.get('left_right_c')
    if left_right_c is None:
        left_right_c = rng.choice(left_rights)

    coh_m = context.get('coh_m')
    if coh_m is None:
        coh_m = rng.choice(cohs)

    coh_c = context.get('coh_c')
    if coh_c is None:
        coh_c = rng.choice(cohs)

    return {
        'durations':    durations,
        'time':         time,
        'epochs':       epochs,
        'context':      context_,
        'left_right_m': left_right_m,
        'left_right_c': left_right_c,
        'coh_m':        coh_m,
        'coh_c':        coh_c
        }

# Input scaling
def scale(coh):
    return (1 + coh/100)/2

def get_step(rng, dt, trial, t, a):
    #-------------------------------------------------------------------------------------
    # Reward
    #-------------------------------------------------------------------------------------

    epochs = trial['epochs']
    status = {'continue': True}
    reward = 0
    if t-1 not in epochs['decision']:
        if a != actions['FIXATE']:
            status['continue'] = False
            reward = R_ABORTED
    elif t-1 in epochs['decision']:
        if a == actions['CHOOSE-LEFT']:
            status['continue'] = False
            status['choice']   = 'L'
            status['t_choice'] = t-1
            if trial['context'] == 'm':
                status['correct'] = (trial['left_right_m'] < 0)
            else:
                status['correct'] = (trial['left_right_c'] < 0)
            if status['correct']:
                reward = R_CORRECT
        elif a == actions['CHOOSE-RIGHT']:
            status['continue'] = False
            status['choice']   = 'R'
            status['t_choice'] = t-1
            if trial['context'] == 'm':
                status['correct'] = (trial['left_right_m'] > 0)
            else:
                status['correct'] = (trial['left_right_c'] > 0)
            if status['correct']:
                reward = R_CORRECT

    #-------------------------------------------------------------------------------------
    # Inputs
    #-------------------------------------------------------------------------------------

    if trial['context'] == 'm':
        context = inputs['MOTION']
    else:
        context = inputs['COLOR']

    if trial['left_right_m'] < 0:
        high_m = inputs['MOTION-LEFT']
        low_m  = inputs['MOTION-RIGHT']
    else:
        high_m = inputs['MOTION-RIGHT']
        low_m  = inputs['MOTION-LEFT']

    if trial['left_right_c'] < 0:
        high_c = inputs['COLOR-LEFT']
        low_c  = inputs['COLOR-RIGHT']
    else:
        high_c = inputs['COLOR-RIGHT']
        low_c  = inputs['COLOR-LEFT']

    u = np.zeros(len(inputs))
    if t in epochs['fixation'] or t in epochs['stimulus'] or t in epochs['delay']:
        u[context] = 1
    if t in epochs['stimulus']:
        u[high_m] = scale(+trial['coh_m']) + rng.normal(scale=sigma)/np.sqrt(dt)
        u[low_m]  = scale(-trial['coh_m']) + rng.normal(scale=sigma)/np.sqrt(dt)
        u[high_c] = scale(+trial['coh_c']) + rng.normal(scale=sigma)/np.sqrt(dt)
        u[low_c]  = scale(-trial['coh_c']) + rng.normal(scale=sigma)/np.sqrt(dt)

    #-------------------------------------------------------------------------------------

    return u, reward, status

def terminate(perf):
    p_decision, p_correct = tasktools.correct_2AFC(perf)

    return p_decision >= 0.99 and p_correct >= 0.85
<commit_msg>Initial commit<commit_after>"""
Context-dependent integration task, based on

  Context-dependent computation by recurrent dynamics in prefrontal cortex.
  V Mante, D Sussillo, KV Shinoy, & WT Newsome, Nature 2013.

  http://dx.doi.org/10.1038/nature12742

"""
from __future__ import division

import numpy as np

from pyrl import tasktools

# Inputs
inputs = tasktools.to_map('MOTION', 'COLOR',
                          'MOTION-LEFT', 'MOTION-RIGHT',
                          'COLOR-LEFT', 'COLOR-RIGHT')

# Actions
actions = tasktools.to_map('FIXATE', 'CHOOSE-LEFT', 'CHOOSE-RIGHT')

# Trial conditions
contexts     = ['m', 'c']
left_rights  = [-1, 1]
cohs         = [5, 15, 50]
n_conditions = len(contexts) * (len(left_rights)*len(cohs))**2

# Training
n_gradient   = n_conditions
n_validation = 50*n_conditions

# Input noise
sigma = np.sqrt(2*100*0.02)

# Rewards
R_ABORTED = -1
R_CORRECT = +1

# Epoch durations
fixation   = 750
stimulus   = 750
delay_min  = 300
delay_mean = 300
delay_max  = 1200
decision   = 500
tmax       = fixation + stimulus + delay_min + delay_max + decision

def get_condition(rng, dt, context={}):
    #-------------------------------------------------------------------------------------
    # Epochs
    #-------------------------------------------------------------------------------------

    delay = context.get('delay')
    if delay is None:
        delay = delay_min + tasktools.truncated_exponential(rng, dt, delay_mean,
                                                            xmax=delay_max)

    durations = {
        'fixation':  (0, fixation),
        'stimulus':  (fixation, fixation + stimulus),
        'delay':     (fixation + stimulus, fixation + stimulus + delay),
        'decision':  (fixation + stimulus + delay, tmax),
        'tmax':      tmax
        }
    time, epochs = tasktools.get_epochs_idx(dt, durations)

    #-------------------------------------------------------------------------------------
    # Trial
    #-------------------------------------------------------------------------------------

    context_ = context.get('context')
    if context_ is None:
        context_ = rng.choice(contexts)

    left_right_m = context.get('left_right_m')
    if left_right_m is None:
        left_right_m = rng.choice(left_rights)

    left_right_c = context.get('left_right_c')
    if left_right_c is None:
        left_right_c = rng.choice(left_rights)

    coh_m = context.get('coh_m')
    if coh_m is None:
        coh_m = rng.choice(cohs)

    coh_c = context.get('coh_c')
    if coh_c is None:
        coh_c = rng.choice(cohs)

    return {
        'durations':    durations,
        'time':         time,
        'epochs':       epochs,
        'context':      context_,
        'left_right_m': left_right_m,
        'left_right_c': left_right_c,
        'coh_m':        coh_m,
        'coh_c':        coh_c
        }

# Input scaling
def scale(coh):
    return (1 + coh/100)/2

def get_step(rng, dt, trial, t, a):
    #-------------------------------------------------------------------------------------
    # Reward
    #-------------------------------------------------------------------------------------

    epochs = trial['epochs']
    status = {'continue': True}
    reward = 0
    if t-1 not in epochs['decision']:
        if a != actions['FIXATE']:
            status['continue'] = False
            reward = R_ABORTED
    elif t-1 in epochs['decision']:
        if a == actions['CHOOSE-LEFT']:
            status['continue'] = False
            status['choice']   = 'L'
            status['t_choice'] = t-1
            if trial['context'] == 'm':
                status['correct'] = (trial['left_right_m'] < 0)
            else:
                status['correct'] = (trial['left_right_c'] < 0)
            if status['correct']:
                reward = R_CORRECT
        elif a == actions['CHOOSE-RIGHT']:
            status['continue'] = False
            status['choice']   = 'R'
            status['t_choice'] = t-1
            if trial['context'] == 'm':
                status['correct'] = (trial['left_right_m'] > 0)
            else:
                status['correct'] = (trial['left_right_c'] > 0)
            if status['correct']:
                reward = R_CORRECT

    #-------------------------------------------------------------------------------------
    # Inputs
    #-------------------------------------------------------------------------------------

    if trial['context'] == 'm':
        context = inputs['MOTION']
    else:
        context = inputs['COLOR']

    if trial['left_right_m'] < 0:
        high_m = inputs['MOTION-LEFT']
        low_m  = inputs['MOTION-RIGHT']
    else:
        high_m = inputs['MOTION-RIGHT']
        low_m  = inputs['MOTION-LEFT']

    if trial['left_right_c'] < 0:
        high_c = inputs['COLOR-LEFT']
        low_c  = inputs['COLOR-RIGHT']
    else:
        high_c = inputs['COLOR-RIGHT']
        low_c  = inputs['COLOR-LEFT']

    u = np.zeros(len(inputs))
    if t in epochs['fixation'] or t in epochs['stimulus'] or t in epochs['delay']:
        u[context] = 1
    if t in epochs['stimulus']:
        u[high_m] = scale(+trial['coh_m']) + rng.normal(scale=sigma)/np.sqrt(dt)
        u[low_m]  = scale(-trial['coh_m']) + rng.normal(scale=sigma)/np.sqrt(dt)
        u[high_c] = scale(+trial['coh_c']) + rng.normal(scale=sigma)/np.sqrt(dt)
        u[low_c]  = scale(-trial['coh_c']) + rng.normal(scale=sigma)/np.sqrt(dt)

    #-------------------------------------------------------------------------------------

    return u, reward, status

def terminate(perf):
    p_decision, p_correct = tasktools.correct_2AFC(perf)

    return p_decision >= 0.99 and p_correct >= 0.85
<|endoftext|>


<commit_before>import collections
import json
import sys
from typing import Any, Dict, List

JSON = Dict[str, Any]

class Key:
    def __init__(self, path: List[str]):
        self.path = path
    def __hash__(self):
        result = 0
        for id in self.path:
            result ^= hash(id)
        return result
    def __eq__(self, other):
        return type(self) is type(other) and self.path == other.path

Option = collections.namedtuple('Option', ['name', 'value'])

# pivot a dict of options keyed by their display name to a dict keyed by their path
def pivot(options: Dict[str, JSON]) -> Dict[Key, Option]:
    result: Dict[Key, Option] = dict()
    for (name, opt) in options.items():
        result[Key(opt['loc'])] = Option(name, opt)
    return result

# pivot back to indexed-by-full-name
# like the docbook build we'll just fail if multiple options with differing locs
# render to the same option name.
def unpivot(options: Dict[Key, Option]) -> Dict[str, JSON]:
    result: Dict[str, Dict] = dict()
    for (key, opt) in options.items():
        if opt.name in result:
            raise RuntimeError(
                'multiple options with colliding ids found',
                opt.name,
                result[opt.name]['loc'],
                opt.value['loc'],
            )
        result[opt.name] = opt.value
    return result

# converts in-place!
def convertMD(options: Dict[str, Any]) -> str:
    import mistune
    import re
    from xml.sax.saxutils import escape, quoteattr

    admonitions = {
        '.warning': 'warning',
        '.important': 'important',
        '.note': 'note'
    }
    class Renderer(mistune.renderers.BaseRenderer):
        def __init__(self, path):
            self.path = path
        def _get_method(self, name):
            try:
                return super(Renderer, self)._get_method(name)
            except AttributeError:
                def not_supported(*args, **kwargs):
                    raise NotImplementedError("md node not supported yet", self.path, name, args, **kwargs)
                return not_supported

        def text(self, text):
            return escape(text)
        def paragraph(self, text):
            return text + "\n\n"
        def newline(self):
            return "<literallayout>\n</literallayout>"
        def codespan(self, text):
            return f"<literal>{escape(text)}</literal>"
        def block_code(self, text, info=None):
            info = f" language={quoteattr(info)}" if info is not None else ""
            return f"<programlisting{info}>\n{escape(text)}</programlisting>"
        def link(self, link, text=None, title=None):
            if link[0:1] == '#':
                attr = "linkend"
                link = quoteattr(link[1:])
            else:
                # try to faithfully reproduce links that were of the form <link href="..."/>
                # in docbook format
                if text == link:
                    text = ""
                attr = "xlink:href"
                link = quoteattr(link)
            return f"<link {attr}={link}>{text}</link>"
        def list(self, text, ordered, level, start=None):
            if ordered:
                raise NotImplementedError("ordered lists not supported yet")
            return f"<itemizedlist>\n{text}\n</itemizedlist>"
        def list_item(self, text, level):
            return f"<listitem><para>{text}</para></listitem>\n"
        def block_text(self, text):
            return text
        def emphasis(self, text):
            return f"<emphasis>{text}</emphasis>"
        def strong(self, text):
            return f"<emphasis role=\"strong\">{text}</emphasis>"
        def admonition(self, text, kind):
            if kind not in admonitions:
                raise NotImplementedError(f"admonition {kind} not supported yet")
            tag = admonitions[kind]
            # we don't keep whitespace here because usually we'll contain only
            # a single paragraph and the original docbook string is no longer
            # available to restore the trailer.
            return f"<{tag}><para>{text.rstrip()}</para></{tag}>"
        def block_quote(self, text):
            return f"
<para>{text}</para>
"
        def command(self, text):
            return f"<command>{escape(text)}</command>"
        def option(self, text):
            return f"<option>{escape(text)}</option>"
        def file(self, text):
            return f"<filename>{escape(text)}</filename>"
        def manpage(self, page, section):
            title = f"<refentrytitle>{escape(page)}</refentrytitle>"
            vol = f"<manvolnum>{escape(section)}</manvolnum>"
            return f"<citerefentry>{title}{vol}</citerefentry>"

        def finalize(self, data):
            return "".join(data)

    plugins = []

    COMMAND_PATTERN = r'\{command\}`(.*?)`'
    def command(md):
        def parse(self, m, state):
            return ('command', m.group(1))
        md.inline.register_rule('command', COMMAND_PATTERN, parse)
        md.inline.rules.append('command')
    plugins.append(command)

    FILE_PATTERN = r'\{file\}`(.*?)`'
    def file(md):
        def parse(self, m, state):
            return ('file', m.group(1))
        md.inline.register_rule('file', FILE_PATTERN, parse)
        md.inline.rules.append('file')
    plugins.append(file)

    OPTION_PATTERN = r'\{option\}`(.*?)`'
    def option(md):
        def parse(self, m, state):
            return ('option', m.group(1))
        md.inline.register_rule('option', OPTION_PATTERN, parse)
        md.inline.rules.append('option')
    plugins.append(option)

    MANPAGE_PATTERN = r'\{manpage\}`(.*?)\((.+?)\)`'
    def manpage(md):
        def parse(self, m, state):
            return ('manpage', m.group(1), m.group(2))
        md.inline.register_rule('manpage', MANPAGE_PATTERN, parse)
        md.inline.rules.append('manpage')
    plugins.append(manpage)

    ADMONITION_PATTERN = re.compile(r'^::: \{([^\n]*?)\}\n(.*?)^:::\n', flags=re.MULTILINE|re.DOTALL)
    def admonition(md):
        def parse(self, m, state):
            return {
                'type': 'admonition',
                'children': self.parse(m.group(2), state),
                'params': [ m.group(1) ],
            }
        md.block.register_rule('admonition', ADMONITION_PATTERN, parse)
        md.block.rules.append('admonition')
    plugins.append(admonition)

    def convertString(path: str, text: str) -> str:
        rendered = mistune.markdown(text, renderer=Renderer(path), plugins=plugins)
        # keep trailing spaces so we can diff the generated XML to check for conversion bugs.
        return rendered.rstrip() + text[len(text.rstrip()):]

    def optionIs(option: Dict[str, Any], key: str, typ: str) -> bool:
        if key not in option: return False
        if type(option[key]) != dict: return False
        if '_type' not in option[key]: return False
        return option[key]['_type'] == typ

    for (name, option) in options.items():
        if optionIs(option, 'description', 'mdDoc'):
            option['description'] = convertString(name, option['description']['text'])
        if optionIs(option, 'example', 'literalMD'):
            docbook = convertString(name, option['example']['text'])
            option['example'] = { '_type': 'literalDocBook', 'text': docbook }
        if optionIs(option, 'default', 'literalMD'):
            docbook = convertString(name, option['default']['text'])
            option['default'] = { '_type': 'literalDocBook', 'text': docbook }

    return options

warningsAreErrors = sys.argv[1] == "--warnings-are-errors"
optOffset = 1 if warningsAreErrors else 0
options = pivot(json.load(open(sys.argv[1 + optOffset], 'r')))
overrides = pivot(json.load(open(sys.argv[2 + optOffset], 'r')))

# fix up declaration paths in lazy options, since we don't eval them from a full nixpkgs dir
for (k, v) in options.items():
    # The _module options are not declared in nixos/modules
    if v.value['loc'][0] != "_module":
        v.value['declarations'] = list(map(lambda s: f'nixos/modules/{s}' if isinstance(s, str) else s, v.value['declarations']))

# merge both descriptions
for (k, v) in overrides.items():
    cur = options.setdefault(k, v).value
    for (ok, ov) in v.value.items():
        if ok == 'declarations':
            decls = cur[ok]
            for d in ov:
                if d not in decls:
                    decls += [d]
        elif ok == "type":
            # ignore types of placeholder options
            if ov != "_unspecified" or cur[ok] == "_unspecified":
                cur[ok] = ov
        elif ov is not None or cur.get(ok, None) is None:
            cur[ok] = ov

severity = "error" if warningsAreErrors else "warning"

# check that every option has a description
hasWarnings = False
for (k, v) in options.items():
    if v.value.get('description', None) is None:
        hasWarnings = True
        print(f"\x1b[1;31m{severity}: option {v.name} has no description\x1b[0m", file=sys.stderr)
        v.value['description'] = "This option has no description."
    if v.value.get('type', "unspecified") == "unspecified":
        hasWarnings = True
        print(
            f"\x1b[1;31m{severity}: option {v.name} has no type. Please specify a valid type, see " +
            "https://nixos.org/manual/nixos/stable/index.html#sec-option-types\x1b[0m", file=sys.stderr)

if hasWarnings and warningsAreErrors:
    print(
        "\x1b[1;31m" +
        "Treating warnings as errors. Set documentation.nixos.options.warningsAreErrors " +
        "to false to ignore these warnings." +
        "\x1b[0m",
        file=sys.stderr)
    sys.exit(1)

json.dump(convertMD(unpivot(options)), fp=sys.stdout)
<commit_msg>nixos/make-options-doc: reuse markdown instance in mergeJSON<commit_after>import collections
import json
import sys
from typing import Any, Dict, List

# for MD conversion
import mistune
import re
from xml.sax.saxutils import escape, quoteattr

JSON = Dict[str, Any]

class Key:
    def __init__(self, path: List[str]):
        self.path = path
    def __hash__(self):
        result = 0
        for id in self.path:
            result ^= hash(id)
        return result
    def __eq__(self, other):
        return type(self) is type(other) and self.path == other.path

Option = collections.namedtuple('Option', ['name', 'value'])

# pivot a dict of options keyed by their display name to a dict keyed by their path
def pivot(options: Dict[str, JSON]) -> Dict[Key, Option]:
    result: Dict[Key, Option] = dict()
    for (name, opt) in options.items():
        result[Key(opt['loc'])] = Option(name, opt)
    return result

# pivot back to indexed-by-full-name
# like the docbook build we'll just fail if multiple options with differing locs
# render to the same option name.
def unpivot(options: Dict[Key, Option]) -> Dict[str, JSON]:
    result: Dict[str, Dict] = dict()
    for (key, opt) in options.items():
        if opt.name in result:
            raise RuntimeError(
                'multiple options with colliding ids found',
                opt.name,
                result[opt.name]['loc'],
                opt.value['loc'],
            )
        result[opt.name] = opt.value
    return result

admonitions = {
    '.warning': 'warning',
    '.important': 'important',
    '.note': 'note'
}
class Renderer(mistune.renderers.BaseRenderer):
    def _get_method(self, name):
        try:
            return super(Renderer, self)._get_method(name)
        except AttributeError:
            def not_supported(*args, **kwargs):
                raise NotImplementedError("md node not supported yet", name, args, **kwargs)
            return not_supported

    def text(self, text):
        return escape(text)
    def paragraph(self, text):
        return text + "\n\n"
    def newline(self):
        return "<literallayout>\n</literallayout>"
    def codespan(self, text):
        return f"<literal>{escape(text)}</literal>"
    def block_code(self, text, info=None):
        info = f" language={quoteattr(info)}" if info is not None else ""
        return f"<programlisting{info}>\n{escape(text)}</programlisting>"
    def link(self, link, text=None, title=None):
        if link[0:1] == '#':
            attr = "linkend"
            link = quoteattr(link[1:])
        else:
            # try to faithfully reproduce links that were of the form <link href="..."/>
            # in docbook format
            if text == link:
                text = ""
            attr = "xlink:href"
            link = quoteattr(link)
        return f"<link {attr}={link}>{text}</link>"
    def list(self, text, ordered, level, start=None):
        if ordered:
            raise NotImplementedError("ordered lists not supported yet")
        return f"<itemizedlist>\n{text}\n</itemizedlist>"
    def list_item(self, text, level):
        return f"<listitem><para>{text}</para></listitem>\n"
    def block_text(self, text):
        return text
    def emphasis(self, text):
        return f"<emphasis>{text}</emphasis>"
    def strong(self, text):
        return f"<emphasis role=\"strong\">{text}</emphasis>"
    def admonition(self, text, kind):
        if kind not in admonitions:
            raise NotImplementedError(f"admonition {kind} not supported yet")
        tag = admonitions[kind]
        # we don't keep whitespace here because usually we'll contain only
        # a single paragraph and the original docbook string is no longer
        # available to restore the trailer.
        return f"<{tag}><para>{text.rstrip()}</para></{tag}>"
    def block_quote(self, text):
        return f"
<para>{text}</para>
"
    def command(self, text):
        return f"<command>{escape(text)}</command>"
    def option(self, text):
        return f"<option>{escape(text)}</option>"
    def file(self, text):
        return f"<filename>{escape(text)}</filename>"
    def manpage(self, page, section):
        title = f"<refentrytitle>{escape(page)}</refentrytitle>"
        vol = f"<manvolnum>{escape(section)}</manvolnum>"
        return f"<citerefentry>{title}{vol}</citerefentry>"

    def finalize(self, data):
        return "".join(data)

def p_command(md):
    COMMAND_PATTERN = r'\{command\}`(.*?)`'
    def parse(self, m, state):
        return ('command', m.group(1))
    md.inline.register_rule('command', COMMAND_PATTERN, parse)
    md.inline.rules.append('command')

def p_file(md):
    FILE_PATTERN = r'\{file\}`(.*?)`'
    def parse(self, m, state):
        return ('file', m.group(1))
    md.inline.register_rule('file', FILE_PATTERN, parse)
    md.inline.rules.append('file')

def p_option(md):
    OPTION_PATTERN = r'\{option\}`(.*?)`'
    def parse(self, m, state):
        return ('option', m.group(1))
    md.inline.register_rule('option', OPTION_PATTERN, parse)
    md.inline.rules.append('option')

def p_manpage(md):
    MANPAGE_PATTERN = r'\{manpage\}`(.*?)\((.+?)\)`'
    def parse(self, m, state):
        return ('manpage', m.group(1), m.group(2))
    md.inline.register_rule('manpage', MANPAGE_PATTERN, parse)
    md.inline.rules.append('manpage')

def p_admonition(md):
    ADMONITION_PATTERN = re.compile(r'^::: \{([^\n]*?)\}\n(.*?)^:::\n', flags=re.MULTILINE|re.DOTALL)
    def parse(self, m, state):
        return {
            'type': 'admonition',
            'children': self.parse(m.group(2), state),
            'params': [ m.group(1) ],
        }
    md.block.register_rule('admonition', ADMONITION_PATTERN, parse)
    md.block.rules.append('admonition')

md = mistune.create_markdown(renderer=Renderer(), plugins=[
    p_command, p_file, p_option, p_manpage, p_admonition
])

# converts in-place!
def convertMD(options: Dict[str, Any]) -> str:
    def convertString(path: str, text: str) -> str:
        try:
            rendered = md(text)
            # keep trailing spaces so we can diff the generated XML to check for conversion bugs.
            return rendered.rstrip() + text[len(text.rstrip()):]
        except:
            print(f"error in {path}")
            raise

    def optionIs(option: Dict[str, Any], key: str, typ: str) -> bool:
        if key not in option: return False
        if type(option[key]) != dict: return False
        if '_type' not in option[key]: return False
        return option[key]['_type'] == typ

    for (name, option) in options.items():
        if optionIs(option, 'description', 'mdDoc'):
            option['description'] = convertString(name, option['description']['text'])
        if optionIs(option, 'example', 'literalMD'):
            docbook = convertString(name, option['example']['text'])
            option['example'] = { '_type': 'literalDocBook', 'text': docbook }
        if optionIs(option, 'default', 'literalMD'):
            docbook = convertString(name, option['default']['text'])
            option['default'] = { '_type': 'literalDocBook', 'text': docbook }

    return options

warningsAreErrors = sys.argv[1] == "--warnings-are-errors"
optOffset = 1 if warningsAreErrors else 0
options = pivot(json.load(open(sys.argv[1 + optOffset], 'r')))
overrides = pivot(json.load(open(sys.argv[2 + optOffset], 'r')))

# fix up declaration paths in lazy options, since we don't eval them from a full nixpkgs dir
for (k, v) in options.items():
    # The _module options are not declared in nixos/modules
    if v.value['loc'][0] != "_module":
        v.value['declarations'] = list(map(lambda s: f'nixos/modules/{s}' if isinstance(s, str) else s, v.value['declarations']))

# merge both descriptions
for (k, v) in overrides.items():
    cur = options.setdefault(k, v).value
    for (ok, ov) in v.value.items():
        if ok == 'declarations':
            decls = cur[ok]
            for d in ov:
                if d not in decls:
                    decls += [d]
        elif ok == "type":
            # ignore types of placeholder options
            if ov != "_unspecified" or cur[ok] == "_unspecified":
                cur[ok] = ov
        elif ov is not None or cur.get(ok, None) is None:
            cur[ok] = ov

severity = "error" if warningsAreErrors else "warning"

# check that every option has a description
hasWarnings = False
for (k, v) in options.items():
    if v.value.get('description', None) is None:
        hasWarnings = True
        print(f"\x1b[1;31m{severity}: option {v.name} has no description\x1b[0m", file=sys.stderr)
        v.value['description'] = "This option has no description."
    if v.value.get('type', "unspecified") == "unspecified":
        hasWarnings = True
        print(
            f"\x1b[1;31m{severity}: option {v.name} has no type. Please specify a valid type, see " +
            "https://nixos.org/manual/nixos/stable/index.html#sec-option-types\x1b[0m", file=sys.stderr)

if hasWarnings and warningsAreErrors:
    print(
        "\x1b[1;31m" +
        "Treating warnings as errors. Set documentation.nixos.options.warningsAreErrors " +
        "to false to ignore these warnings." +
        "\x1b[0m",
        file=sys.stderr)
    sys.exit(1)

json.dump(convertMD(unpivot(options)), fp=sys.stdout)
<|endoftext|>


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

try:
    import simplejson as json
except ImportError:
    import json
import logging
import pprint
import urllib2
import werkzeug

from openerp import http, SUPERUSER_ID
from openerp.http import request

_logger = logging.getLogger(__name__)

class PaypalController(http.Controller):
    _notify_url = '/payment/paypal/ipn/'
    _return_url = '/payment/paypal/dpn/'
    _cancel_url = '/payment/paypal/cancel/'

    def _get_return_url(self, **post):
        """ Extract the return URL from the data coming from paypal. """
        return_url = post.pop('return_url', '')
        if not return_url:
            custom = json.loads(post.pop('custom', '{}'))
            return_url = custom.get('return_url', '/')
        return return_url

    def paypal_validate_data(self, **post):
        """ Paypal IPN: three steps validation to ensure data correctness

         - step 1: return an empty HTTP 200 response -> will be done at the end
           by returning ''
         - step 2: POST the complete, unaltered message back to Paypal (preceded
           by cmd=_notify-validate), with same encoding
         - step 3: paypal send either VERIFIED or INVALID (single word)

        Once data is validated, process it. """
        res = False
        new_post = dict(post, cmd='_notify-validate')
        urequest = urllib2.Request("https://www.sandbox.paypal.com/cgi-bin/webscr", werkzeug.url_encode(new_post))
        uopen = urllib2.urlopen(urequest)
        resp = uopen.read()
        if resp == 'VERIFIED':
            _logger.info('Paypal: validated data')
            cr, uid, context = request.cr, SUPERUSER_ID, request.context
            res = request.registry['payment.transaction'].form_feedback(cr, uid, post, 'paypal', context=context)
        elif resp == 'INVALID':
            _logger.warning('Paypal: answered INVALID on data verification')
        else:
            _logger.warning('Paypal: unrecognized paypal answer, received %s instead of VERIFIED or INVALID' % resp.text)
        return res

    @http.route('/payment/paypal/ipn/', type='http', auth='none', methods=['POST'])
    def paypal_ipn(self, **post):
        """ Paypal IPN. """
        _logger.info('Beginning Paypal IPN form_feedback with post data %s', pprint.pformat(post))  # debug
        self.paypal_validate_data(**post)
        return ''

    @http.route('/payment/paypal/dpn', type='http', auth="none", methods=['POST'])
    def paypal_dpn(self, **post):
        """ Paypal DPN """
        _logger.info('Beginning Paypal DPN form_feedback with post data %s', pprint.pformat(post))  # debug
        return_url = self._get_return_url(**post)
        self.paypal_validate_data(**post)
        return werkzeug.utils.redirect(return_url)

    @http.route('/payment/paypal/cancel', type='http', auth="none")
    def paypal_cancel(self, **post):
        """ When the user cancels its Paypal payment: GET on this route """
        cr, uid, context = request.cr, SUPERUSER_ID, request.context
        _logger.info('Beginning Paypal cancel with post data %s', pprint.pformat(post))  # debug
        return_url = self._get_return_url(**post)
        return werkzeug.utils.redirect(return_url)
<commit_msg>[FIX] payment_paypal: use the validate url according to the acquirer env<commit_after># -*- coding: utf-8 -*-

try:
    import simplejson as json
except ImportError:
    import json
import logging
import pprint
import urllib2
import werkzeug

from openerp import http, SUPERUSER_ID
from openerp.http import request

_logger = logging.getLogger(__name__)

class PaypalController(http.Controller):
    _notify_url = '/payment/paypal/ipn/'
    _return_url = '/payment/paypal/dpn/'
    _cancel_url = '/payment/paypal/cancel/'

    def _get_return_url(self, **post):
        """ Extract the return URL from the data coming from paypal. """
        return_url = post.pop('return_url', '')
        if not return_url:
            custom = json.loads(post.pop('custom', '{}'))
            return_url = custom.get('return_url', '/')
        return return_url

    def paypal_validate_data(self, **post):
        """ Paypal IPN: three steps validation to ensure data correctness

         - step 1: return an empty HTTP 200 response -> will be done at the end
           by returning ''
         - step 2: POST the complete, unaltered message back to Paypal (preceded
           by cmd=_notify-validate), with same encoding
         - step 3: paypal send either VERIFIED or INVALID (single word)

        Once data is validated, process it. """
        res = False
        new_post = dict(post, cmd='_notify-validate')
        cr, uid, context = request.cr, request.uid, request.context
        reference = post.get('item_number')
        tx = None
        if reference:
            tx_ids = request.registry['payment.transaction'].search(cr, uid, [('reference', '=', reference)], context=context)
            if tx_ids:
                tx = request.registry['payment.transaction'].browse(cr, uid, tx_ids[0], context=context)
        paypal_urls = request.registry['payment.acquirer']._get_paypal_urls(cr, uid, tx and tx.acquirer_id and tx.acquirer_id.env or 'prod', context=context)
        validate_url = paypal_urls['paypal_form_url']
        urequest = urllib2.Request(validate_url, werkzeug.url_encode(new_post))
        uopen = urllib2.urlopen(urequest)
        resp = uopen.read()
        if resp == 'VERIFIED':
            _logger.info('Paypal: validated data')
            res = request.registry['payment.transaction'].form_feedback(cr, uid, post, 'paypal', context=context)
        elif resp == 'INVALID':
            _logger.warning('Paypal: answered INVALID on data verification')
        else:
            _logger.warning('Paypal: unrecognized paypal answer, received %s instead of VERIFIED or INVALID' % resp.text)
        return res

    @http.route('/payment/paypal/ipn/', type='http', auth='none', methods=['POST'])
    def paypal_ipn(self, **post):
        """ Paypal IPN. """
        _logger.info('Beginning Paypal IPN form_feedback with post data %s', pprint.pformat(post))  # debug
        self.paypal_validate_data(**post)
        return ''

    @http.route('/payment/paypal/dpn', type='http', auth="none", methods=['POST'])
    def paypal_dpn(self, **post):
        """ Paypal DPN """
        _logger.info('Beginning Paypal DPN form_feedback with post data %s', pprint.pformat(post))  # debug
        return_url = self._get_return_url(**post)
        self.paypal_validate_data(**post)
        return werkzeug.utils.redirect(return_url)

    @http.route('/payment/paypal/cancel', type='http', auth="none")
    def paypal_cancel(self, **post):
        """ When the user cancels its Paypal payment: GET on this route """
        cr, uid, context = request.cr, SUPERUSER_ID, request.context
        _logger.info('Beginning Paypal cancel with post data %s', pprint.pformat(post))  # debug
        return_url = self._get_return_url(**post)
        return werkzeug.utils.redirect(return_url)
<|endoftext|>


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

def gcd(a,b,c):
  while b:
    a, b = b, a % b
  while c:
    a, c = c, a % c
  return a

class periodicity:

  def __init__(self,geometry,period_type,axis=None,plane=None,plane_thickness=None):

    if period_type=="0D":
      self._period_type="0D"

    elif period_type=="1D":
      self._period_type="1D"

      axis=numpy.array(axis,dtype='int')
      axis.shape=(1,3)

      if (axis==numpy.array([[0,0,0]])).all():
        axis=None
      self._axis = axis/gcd(axis[0,0],axis[0,1],axis[0,2])
      self._axis_cart=geometry.coord_transform(axis, "lattice")

    elif period_type=="2D":
      self._period_type="2D"

      axis=numpy.array(axis,dtype='int')
      axis.shape=(2,3)

      if (axis==numpy.array([[0,0,0]])).all():
        axis=None
      self._axis = numpy.vstack((axis[1]/gcd(axis[1,0],axis[1,1],axis[1,2]),
                                 axis[0]/gcd(axis[0,0],axis[0,1],axis[0,2])))
      self._axis_cart=geometry.coord_transform(axis, "lattice")

    else:
      #TODO: Raise Exeption
      exit('InternalError:\n'+
           'No valid period_type given.'
           +'\nExiting...\n')

  def get_axis(self,coordsys="lattice"):
    if (self._period_type=="1D" or self._period_type=="2D"):
      if coordsys=="lattice":
        return self._axis
      elif coordsys=="cartesian":
        return self._axis_cart
      else:
        #TODO: Raise Exeption
        exit('InternalError:\n'+
           'No valid coordinate system given.'
           +'\nExiting...\n')
    else:
      #TODO: Raise Exeption
      exit('InternalError:\n'+
           'get_axis() called, but period_type is not 1D or 2D.'
           +'\nExiting...\n')

  def period_type_is(self,testtype):
    return self._period_type==testtype

  @classmethod
  def from_dict(cls,geometry,d):
    #Reads periodicity from dict
    if "periodicity" not in d.keys():

      return cls(geometry,"0D")
    else:
      period_type = d["periodicity"].get("period_type","0D")

      if period_type=="0D":

        return cls(geometry,"0D")

      elif period_type=="1D":

        axis = d["periodicity"].get("axis",None)

        if axis==None:
          exit('Error:\n'+
           'Item axis not specified but needed.'
           +'\nExiting...\n')
        try:
          axis=numpy.array([int(el) for el in axis.split()])
        except ValueError:
          exit('Error:\n'+
           'Supplied string for axis not convertible to integer-array.'
           +'\nExiting...\n')

        try:
          axis.shape=(1,3)
        except ValueError:
          exit('Error:\n'+
          'Wrong number of elements supplied for axis.'
           +'\nExiting...\n')

        return cls(geometry,"1D",axis)

      elif period_type=="2D":

        axis = d["periodicity"].get("axis",None)

        if axis==None:
          exit('Error:\n'+
           'Item axis not specified but needed.'
           +'\nExiting...\n')
        try:
          axis=numpy.array([int(el) for el in axis.split()])
        except ValueError:
          exit('Error:\n'+
           'Supplied string for axis not convertible to integer-array.'
           +'\nExiting...\n')

        try:
          axis.shape=(1,6)
        except ValueError:
          exit('Error:\n'+
          'Wrong number of elements supplied for axis.'
           +'\nExiting...\n')

        return cls(geometry,"2D",axis)<commit_msg>Restructured periodicity.py and added comments<commit_after># -*- coding: utf-8 -*-
import numpy

def gcd(a,b,c):
  '''Return greatest common divisor.'''
  while b:
    a, b = b, a % b
  while c:
    a, c = c, a % c
  return a

class periodicity:
  '''Class holding information about type of periodicity, and axes.'''
  def __init__(self,geometry,period_type,axis=None):

    if period_type=="0D":
      self._period_type="0D"

    else:
      axis=numpy.array(axis,dtype='int')
      axis.shape=(-1,3)

      if period_type=="1D":
        self._period_type="1D"

        if (axis==0).all():
          raise ValueError, "Value of axis is invalid."

        #shorten axis if possible by division with gcd
        self._axis = axis/gcd(axis[0,0],axis[0,1],axis[0,2])

      elif period_type=="2D":
        self._period_type="2D"

        if ((axis[0]==0).all() or axis[1]==0).all():
          raise ValueError, "Value of axis is invalid."

        #shorten axes if possiple by division with gcd
        self._axis = numpy.vstack((axis[1]/gcd(axis[1,0],axis[1,1],axis[1,2]),
                                   axis[0]/gcd(axis[0,0],axis[0,1],axis[0,2])))

      else:
        raise ValueError, "Value of period_type is invalid."

      self._axis_cart=geometry.coord_transform(self._axis, "lattice")

  def get_axis(self,coordsys="lattice"):
    '''Returns axis'''
    if (self._period_type=="1D" or self._period_type=="2D"):
      if coordsys=="lattice":
        return self._axis
      elif coordsys=="cartesian":
        return self._axis_cart
      else:
        raise ValueError, "Value of coorsys is invalid."
    else:
      raise ValueError, "get_axis() called, but period_type is not 1D or 2D."

  def period_type_is(self,testtype):
    '''Returns True if argument matches period_type.'''
    return self._period_type==testtype

  @classmethod
  def from_dict(cls,geometry,d):
    '''Reads periodicity from dict and checks data types'''
    if "periodicity" not in d.keys():
      return cls(geometry,"0D")

    else:
      period_type = d["periodicity"].get("period_type","0D")

      if period_type=="0D":
        return cls(geometry,"0D")

      elif period_type=="1D":

        axis = d["periodicity"].get("axis",None)

        if axis==None:
          exit('Error:\n'+
           'Item axis not specified but needed.'
           +'\nExiting...\n')
        try:
          axis=numpy.array([int(el) for el in axis.split()])
        except ValueError:
          exit('Error:\n'+
           'Supplied string for axis not convertible to integer-array.'
           +'\nExiting...\n')

        try:
          axis.shape=(1,3)
        except ValueError:
          exit('Error:\n'+
          'Wrong number of elements supplied for axis.'
           +'\nExiting...\n')

        if (axis==0).all():
          exit('Error:\n'+
          'Value of axis is invalid.'
          +'\nExiting...\n')

        return cls(geometry,"1D",axis)

      elif period_type=="2D":

        axis = d["periodicity"].get("axis",None)

        if axis==None:
          exit('Error:\n'+
           'Item axis not specified but needed.'
           +'\nExiting...\n')
        try:
          axis=numpy.array([int(el) for el in axis.split()])
        except ValueError:
          exit('Error:\n'+
           'Supplied string for axis not convertible to integer-array.'
           +'\nExiting...\n')

        try:
          axis.shape=(2,3)
        except ValueError:
          exit('Error:\n'+
          'Wrong number of elements supplied for axis.'
           +'\nExiting...\n')

        if ((axis[0]==0).all() or axis[1]==0).all():
          exit('Error:\n'+
          'Value of axis is invalid.'
          +'\nExiting...\n')

        return cls(geometry,"2D",axis)<|endoftext|>