<commit_before>"""Support for KNX/IP covers."""
from xknx.devices import Cover as XknxCover

from homeassistant.components.cover import (
    ATTR_POSITION,
    ATTR_TILT_POSITION,
    DEVICE_CLASS_BLIND,
    DEVICE_CLASSES,
    SUPPORT_CLOSE,
    SUPPORT_CLOSE_TILT,
    SUPPORT_OPEN,
    SUPPORT_OPEN_TILT,
    SUPPORT_SET_POSITION,
    SUPPORT_SET_TILT_POSITION,
    SUPPORT_STOP,
    CoverEntity,
)
from homeassistant.core import callback
from homeassistant.helpers.event import async_track_utc_time_change

from .const import DOMAIN
from .knx_entity import KnxEntity

async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
    """Set up cover(s) for KNX platform."""
    entities = []
    for device in hass.data[DOMAIN].xknx.devices:
        if isinstance(device, XknxCover):
            entities.append(KNXCover(device))
    async_add_entities(entities)

class KNXCover(KnxEntity, CoverEntity):
    """Representation of a KNX cover."""

    def __init__(self, device: XknxCover):
        """Initialize the cover."""
        super().__init__(device)

        self._unsubscribe_auto_updater = None

    @callback
    async def after_update_callback(self, device):
        """Call after device was updated."""
        self.async_write_ha_state()
        if self._device.is_traveling():
            self.start_auto_updater()

    @property
    def device_class(self):
        """Return the class of this device, from component DEVICE_CLASSES."""
        if self._device.device_class in DEVICE_CLASSES:
            return self._device.device_class
        if self._device.supports_angle:
            return DEVICE_CLASS_BLIND
        return None

    @property
    def supported_features(self):
        """Flag supported features."""
        supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
        if self._device.supports_stop:
            supported_features |= SUPPORT_STOP
        if self._device.supports_angle:
            supported_features |= (
                SUPPORT_SET_TILT_POSITION | SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT
            )
        return supported_features

    @property
    def current_cover_position(self):
        """Return the current position of the cover.

        None is unknown, 0 is closed, 100 is fully open.
        """
        # In KNX 0 is open, 100 is closed.
        try:
            return 100 - self._device.current_position()
        except TypeError:
            return None

    @property
    def is_closed(self):
        """Return if the cover is closed."""
        # state shall be "unknown" when xknx travelcalculator is not initialized
        if self._device.current_position() is None:
            return None
        return self._device.is_closed()

    @property
    def is_opening(self):
        """Return if the cover is opening or not."""
        return self._device.is_opening()

    @property
    def is_closing(self):
        """Return if the cover is closing or not."""
        return self._device.is_closing()

    async def async_close_cover(self, **kwargs):
        """Close the cover."""
        await self._device.set_down()

    async def async_open_cover(self, **kwargs):
        """Open the cover."""
        await self._device.set_up()

    async def async_set_cover_position(self, **kwargs):
        """Move the cover to a specific position."""
        knx_position = 100 - kwargs[ATTR_POSITION]
        await self._device.set_position(knx_position)

    async def async_stop_cover(self, **kwargs):
        """Stop the cover."""
        await self._device.stop()
        self.stop_auto_updater()

    @property
    def current_cover_tilt_position(self):
        """Return current tilt position of cover."""
        if not self._device.supports_angle:
            return None
        try:
            return 100 - self._device.current_angle()
        except TypeError:
            return None

    async def async_set_cover_tilt_position(self, **kwargs):
        """Move the cover tilt to a specific position."""
        knx_tilt_position = 100 - kwargs[ATTR_TILT_POSITION]
        await self._device.set_angle(knx_tilt_position)

    async def async_open_cover_tilt(self, **kwargs):
        """Open the cover tilt."""
        await self._device.set_short_up()

    async def async_close_cover_tilt(self, **kwargs):
        """Close the cover tilt."""
        await self._device.set_short_down()

    def start_auto_updater(self):
        """Start the autoupdater to update Home Assistant while cover is moving."""
        if self._unsubscribe_auto_updater is None:
            self._unsubscribe_auto_updater = async_track_utc_time_change(
                self.hass, self.auto_updater_hook
            )

    def stop_auto_updater(self):
        """Stop the autoupdater."""
        if self._unsubscribe_auto_updater is not None:
            self._unsubscribe_auto_updater()
            self._unsubscribe_auto_updater = None

    @callback
    def auto_updater_hook(self, now):
        """Call for the autoupdater."""
        self.async_write_ha_state()
        if self._device.position_reached():
            self.stop_auto_updater()

        self.hass.add_job(self._device.auto_stop_if_necessary())
<commit_msg>Add stop tilt support to KNX (#46947)<commit_after>"""Support for KNX/IP covers."""
from xknx.devices import Cover as XknxCover

from homeassistant.components.cover import (
    ATTR_POSITION,
    ATTR_TILT_POSITION,
    DEVICE_CLASS_BLIND,
    DEVICE_CLASSES,
    SUPPORT_CLOSE,
    SUPPORT_CLOSE_TILT,
    SUPPORT_OPEN,
    SUPPORT_OPEN_TILT,
    SUPPORT_SET_POSITION,
    SUPPORT_SET_TILT_POSITION,
    SUPPORT_STOP,
    SUPPORT_STOP_TILT,
    CoverEntity,
)
from homeassistant.core import callback
from homeassistant.helpers.event import async_track_utc_time_change

from .const import DOMAIN
from .knx_entity import KnxEntity

async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
    """Set up cover(s) for KNX platform."""
    entities = []
    for device in hass.data[DOMAIN].xknx.devices:
        if isinstance(device, XknxCover):
            entities.append(KNXCover(device))
    async_add_entities(entities)

class KNXCover(KnxEntity, CoverEntity):
    """Representation of a KNX cover."""

    def __init__(self, device: XknxCover):
        """Initialize the cover."""
        super().__init__(device)

        self._unsubscribe_auto_updater = None

    @callback
    async def after_update_callback(self, device):
        """Call after device was updated."""
        self.async_write_ha_state()
        if self._device.is_traveling():
            self.start_auto_updater()

    @property
    def device_class(self):
        """Return the class of this device, from component DEVICE_CLASSES."""
        if self._device.device_class in DEVICE_CLASSES:
            return self._device.device_class
        if self._device.supports_angle:
            return DEVICE_CLASS_BLIND
        return None

    @property
    def supported_features(self):
        """Flag supported features."""
        supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
        if self._device.supports_stop:
            supported_features |= SUPPORT_STOP
        if self._device.supports_angle:
            supported_features |= (
                SUPPORT_SET_TILT_POSITION
                | SUPPORT_OPEN_TILT
                | SUPPORT_CLOSE_TILT
                | SUPPORT_STOP_TILT
            )
        return supported_features

    @property
    def current_cover_position(self):
        """Return the current position of the cover.

        None is unknown, 0 is closed, 100 is fully open.
        """
        # In KNX 0 is open, 100 is closed.
        try:
            return 100 - self._device.current_position()
        except TypeError:
            return None

    @property
    def is_closed(self):
        """Return if the cover is closed."""
        # state shall be "unknown" when xknx travelcalculator is not initialized
        if self._device.current_position() is None:
            return None
        return self._device.is_closed()

    @property
    def is_opening(self):
        """Return if the cover is opening or not."""
        return self._device.is_opening()

    @property
    def is_closing(self):
        """Return if the cover is closing or not."""
        return self._device.is_closing()

    async def async_close_cover(self, **kwargs):
        """Close the cover."""
        await self._device.set_down()

    async def async_open_cover(self, **kwargs):
        """Open the cover."""
        await self._device.set_up()

    async def async_set_cover_position(self, **kwargs):
        """Move the cover to a specific position."""
        knx_position = 100 - kwargs[ATTR_POSITION]
        await self._device.set_position(knx_position)

    async def async_stop_cover(self, **kwargs):
        """Stop the cover."""
        await self._device.stop()
        self.stop_auto_updater()

    @property
    def current_cover_tilt_position(self):
        """Return current tilt position of cover."""
        if not self._device.supports_angle:
            return None
        try:
            return 100 - self._device.current_angle()
        except TypeError:
            return None

    async def async_set_cover_tilt_position(self, **kwargs):
        """Move the cover tilt to a specific position."""
        knx_tilt_position = 100 - kwargs[ATTR_TILT_POSITION]
        await self._device.set_angle(knx_tilt_position)

    async def async_open_cover_tilt(self, **kwargs):
        """Open the cover tilt."""
        await self._device.set_short_up()

    async def async_close_cover_tilt(self, **kwargs):
        """Close the cover tilt."""
        await self._device.set_short_down()

    async def async_stop_cover_tilt(self, **kwargs):
        """Stop the cover tilt."""
        await self._device.stop()
        self.stop_auto_updater()

    def start_auto_updater(self):
        """Start the autoupdater to update Home Assistant while cover is moving."""
        if self._unsubscribe_auto_updater is None:
            self._unsubscribe_auto_updater = async_track_utc_time_change(
                self.hass, self.auto_updater_hook
            )

    def stop_auto_updater(self):
        """Stop the autoupdater."""
        if self._unsubscribe_auto_updater is not None:
            self._unsubscribe_auto_updater()
            self._unsubscribe_auto_updater = None

    @callback
    def auto_updater_hook(self, now):
        """Call for the autoupdater."""
        self.async_write_ha_state()
        if self._device.position_reached():
            self.stop_auto_updater()

        self.hass.add_job(self._device.auto_stop_if_necessary())
<|endoftext|>


<commit_before>from os import listdir, path
import settings
basedir = path.abspath(path.dirname(__file__))
datadir = path.join(basedir, settings.GITDJANGO_DIRNAME)
datadir = path.abspath(datadir)
print datadir
for app in listdir(datadir):
    if app[0] == '.':
        continue
    module = __import__(app,{},{},fromlist=['load'])
    if 'loaddir' in dir(module.load):
        module.load.loaddir(path.join(datadir, app), clear=True)
    else:
        import warnings
        warnings.warn("Don't know how to import '%s'" % app)
<commit_msg>We shouldn't be printing a random path<commit_after>from os import listdir, path
import settings
basedir = path.abspath(path.dirname(__file__))
datadir = path.join(basedir, settings.GITDJANGO_DIRNAME)
datadir = path.abspath(datadir)
for app in listdir(datadir):
    if app[0] == '.':
        continue
    module = __import__(app,{},{},fromlist=['load'])
    if 'loaddir' in dir(module.load):
        module.load.loaddir(path.join(datadir, app), clear=True)
    else:
        import warnings
        warnings.warn("Don't know how to import '%s'" % app)
<|endoftext|>


<commit_before>"""Support for LCN lights."""
from __future__ import annotations

from typing import Any

import pypck

from homeassistant.components.light import (
    ATTR_BRIGHTNESS,
    ATTR_TRANSITION,
    COLOR_MODE_BRIGHTNESS,
    COLOR_MODE_ONOFF,
    DOMAIN as DOMAIN_LIGHT,
    SUPPORT_TRANSITION,
    LightEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ADDRESS, CONF_DOMAIN, CONF_ENTITIES
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType

from . import LcnEntity
from .const import (
    CONF_DIMMABLE,
    CONF_DOMAIN_DATA,
    CONF_OUTPUT,
    CONF_TRANSITION,
    OUTPUT_PORTS,
)
from .helpers import DeviceConnectionType, InputType, get_device_connection

PARALLEL_UPDATES = 0

def create_lcn_light_entity(
    hass: HomeAssistant, entity_config: ConfigType, config_entry: ConfigEntry
) -> LcnEntity:
    """Set up an entity for this domain."""
    device_connection = get_device_connection(
        hass, entity_config[CONF_ADDRESS], config_entry
    )

    if entity_config[CONF_DOMAIN_DATA][CONF_OUTPUT] in OUTPUT_PORTS:
        return LcnOutputLight(entity_config, config_entry.entry_id, device_connection)
    # in RELAY_PORTS
    return LcnRelayLight(entity_config, config_entry.entry_id, device_connection)

async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up LCN light entities from a config entry."""
    entities = []

    for entity_config in config_entry.data[CONF_ENTITIES]:
        if entity_config[CONF_DOMAIN] == DOMAIN_LIGHT:
            entities.append(create_lcn_light_entity(hass, entity_config, config_entry))

    async_add_entities(entities)

class LcnOutputLight(LcnEntity, LightEntity):
    """Representation of a LCN light for output ports."""

    _attr_supported_features = SUPPORT_TRANSITION

    def __init__(
        self, config: ConfigType, entry_id: str, device_connection: DeviceConnectionType
    ) -> None:
        """Initialize the LCN light."""
        super().__init__(config, entry_id, device_connection)

        self.output = pypck.lcn_defs.OutputPort[config[CONF_DOMAIN_DATA][CONF_OUTPUT]]

        self._transition = pypck.lcn_defs.time_to_ramp_value(
            config[CONF_DOMAIN_DATA][CONF_TRANSITION]
        )
        self.dimmable = config[CONF_DOMAIN_DATA][CONF_DIMMABLE]

        self._brightness = 255
        self._is_on = False
        self._is_dimming_to_zero = False

        if self.dimmable:
            self._attr_color_mode = COLOR_MODE_BRIGHTNESS
        else:
            self._attr_color_mode = COLOR_MODE_ONOFF
        self._attr_supported_color_modes = {self._attr_color_mode}

    async def async_added_to_hass(self) -> None:
        """Run when entity about to be added to hass."""
        await super().async_added_to_hass()
        if not self.device_connection.is_group:
            await self.device_connection.activate_status_request_handler(self.output)

    async def async_will_remove_from_hass(self) -> None:
        """Run when entity will be removed from hass."""
        await super().async_will_remove_from_hass()
        if not self.device_connection.is_group:
            await self.device_connection.cancel_status_request_handler(self.output)

    @property
    def brightness(self) -> int | None:
        """Return the brightness of this light between 0..255."""
        return self._brightness

    @property
    def is_on(self) -> bool:
        """Return True if entity is on."""
        return self._is_on

    async def async_turn_on(self, **kwargs: Any) -> None:
        """Turn the entity on."""
        if ATTR_BRIGHTNESS in kwargs:
            percent = int(kwargs[ATTR_BRIGHTNESS] / 255.0 * 100)
        else:
            percent = 100
        if ATTR_TRANSITION in kwargs:
            transition = pypck.lcn_defs.time_to_ramp_value(
                kwargs[ATTR_TRANSITION] * 1000
            )
        else:
            transition = self._transition

        if not await self.device_connection.dim_output(
            self.output.value, percent, transition
        ):
            return
        self._is_on = True
        self._is_dimming_to_zero = False
        self.async_write_ha_state()

    async def async_turn_off(self, **kwargs: Any) -> None:
        """Turn the entity off."""
        if ATTR_TRANSITION in kwargs:
            transition = pypck.lcn_defs.time_to_ramp_value(
                kwargs[ATTR_TRANSITION] * 1000
            )
        else:
            transition = self._transition

        if not await self.device_connection.dim_output(
            self.output.value, 0, transition
        ):
            return
        self._is_dimming_to_zero = bool(transition)
        self._is_on = False
        self.async_write_ha_state()

    def input_received(self, input_obj: InputType) -> None:
        """Set light state when LCN input object (command) is received."""
        if (
            not isinstance(input_obj, pypck.inputs.ModStatusOutput)
            or input_obj.get_output_id() != self.output.value
        ):
            return

        self._brightness = int(input_obj.get_percent() / 100.0 * 255)
        if self.brightness == 0:
            self._is_dimming_to_zero = False
        if not self._is_dimming_to_zero and self.brightness is not None:
            self._is_on = self.brightness > 0
        self.async_write_ha_state()

class LcnRelayLight(LcnEntity, LightEntity):
    """Representation of a LCN light for relay ports."""

    _attr_color_mode = COLOR_MODE_ONOFF
    _attr_supported_color_modes = {COLOR_MODE_ONOFF}

    def __init__(
        self, config: ConfigType, entry_id: str, device_connection: DeviceConnectionType
    ) -> None:
        """Initialize the LCN light."""
        super().__init__(config, entry_id, device_connection)

        self.output = pypck.lcn_defs.RelayPort[config[CONF_DOMAIN_DATA][CONF_OUTPUT]]

        self._is_on = False

    async def async_added_to_hass(self) -> None:
        """Run when entity about to be added to hass."""
        await super().async_added_to_hass()
        if not self.device_connection.is_group:
            await self.device_connection.activate_status_request_handler(self.output)

    async def async_will_remove_from_hass(self) -> None:
        """Run when entity will be removed from hass."""
        await super().async_will_remove_from_hass()
        if not self.device_connection.is_group:
            await self.device_connection.cancel_status_request_handler(self.output)

    @property
    def is_on(self) -> bool:
        """Return True if entity is on."""
        return self._is_on

    async def async_turn_on(self, **kwargs: Any) -> None:
        """Turn the entity on."""
        states = [pypck.lcn_defs.RelayStateModifier.NOCHANGE] * 8
        states[self.output.value] = pypck.lcn_defs.RelayStateModifier.ON
        if not await self.device_connection.control_relays(states):
            return
        self._is_on = True
        self.async_write_ha_state()

    async def async_turn_off(self, **kwargs: Any) -> None:
        """Turn the entity off."""
        states = [pypck.lcn_defs.RelayStateModifier.NOCHANGE] * 8
        states[self.output.value] = pypck.lcn_defs.RelayStateModifier.OFF
        if not await self.device_connection.control_relays(states):
            return
        self._is_on = False
        self.async_write_ha_state()

    def input_received(self, input_obj: InputType) -> None:
        """Set light state when LCN input object (command) is received."""
        if not isinstance(input_obj, pypck.inputs.ModStatusRelays):
            return

        self._is_on = input_obj.get_state(self.output.value)
        self.async_write_ha_state()
<commit_msg>Use ColorMode enum in lcn (#70515)<commit_after>"""Support for LCN lights."""
from __future__ import annotations

from typing import Any

import pypck

from homeassistant.components.light import (
    ATTR_BRIGHTNESS,
    ATTR_TRANSITION,
    DOMAIN as DOMAIN_LIGHT,
    SUPPORT_TRANSITION,
    ColorMode,
    LightEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ADDRESS, CONF_DOMAIN, CONF_ENTITIES
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType

from . import LcnEntity
from .const import (
    CONF_DIMMABLE,
    CONF_DOMAIN_DATA,
    CONF_OUTPUT,
    CONF_TRANSITION,
    OUTPUT_PORTS,
)
from .helpers import DeviceConnectionType, InputType, get_device_connection

PARALLEL_UPDATES = 0

def create_lcn_light_entity(
    hass: HomeAssistant, entity_config: ConfigType, config_entry: ConfigEntry
) -> LcnEntity:
    """Set up an entity for this domain."""
    device_connection = get_device_connection(
        hass, entity_config[CONF_ADDRESS], config_entry
    )

    if entity_config[CONF_DOMAIN_DATA][CONF_OUTPUT] in OUTPUT_PORTS:
        return LcnOutputLight(entity_config, config_entry.entry_id, device_connection)
    # in RELAY_PORTS
    return LcnRelayLight(entity_config, config_entry.entry_id, device_connection)

async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up LCN light entities from a config entry."""
    entities = []

    for entity_config in config_entry.data[CONF_ENTITIES]:
        if entity_config[CONF_DOMAIN] == DOMAIN_LIGHT:
            entities.append(create_lcn_light_entity(hass, entity_config, config_entry))

    async_add_entities(entities)

class LcnOutputLight(LcnEntity, LightEntity):
    """Representation of a LCN light for output ports."""

    _attr_supported_features = SUPPORT_TRANSITION

    def __init__(
        self, config: ConfigType, entry_id: str, device_connection: DeviceConnectionType
    ) -> None:
        """Initialize the LCN light."""
        super().__init__(config, entry_id, device_connection)

        self.output = pypck.lcn_defs.OutputPort[config[CONF_DOMAIN_DATA][CONF_OUTPUT]]

        self._transition = pypck.lcn_defs.time_to_ramp_value(
            config[CONF_DOMAIN_DATA][CONF_TRANSITION]
        )
        self.dimmable = config[CONF_DOMAIN_DATA][CONF_DIMMABLE]

        self._brightness = 255
        self._is_on = False
        self._is_dimming_to_zero = False

        if self.dimmable:
            self._attr_color_mode = ColorMode.BRIGHTNESS
        else:
            self._attr_color_mode = ColorMode.ONOFF
        self._attr_supported_color_modes = {self._attr_color_mode}

    async def async_added_to_hass(self) -> None:
        """Run when entity about to be added to hass."""
        await super().async_added_to_hass()
        if not self.device_connection.is_group:
            await self.device_connection.activate_status_request_handler(self.output)

    async def async_will_remove_from_hass(self) -> None:
        """Run when entity will be removed from hass."""
        await super().async_will_remove_from_hass()
        if not self.device_connection.is_group:
            await self.device_connection.cancel_status_request_handler(self.output)

    @property
    def brightness(self) -> int | None:
        """Return the brightness of this light between 0..255."""
        return self._brightness

    @property
    def is_on(self) -> bool:
        """Return True if entity is on."""
        return self._is_on

    async def async_turn_on(self, **kwargs: Any) -> None:
        """Turn the entity on."""
        if ATTR_BRIGHTNESS in kwargs:
            percent = int(kwargs[ATTR_BRIGHTNESS] / 255.0 * 100)
        else:
            percent = 100
        if ATTR_TRANSITION in kwargs:
            transition = pypck.lcn_defs.time_to_ramp_value(
                kwargs[ATTR_TRANSITION] * 1000
            )
        else:
            transition = self._transition

        if not await self.device_connection.dim_output(
            self.output.value, percent, transition
        ):
            return
        self._is_on = True
        self._is_dimming_to_zero = False
        self.async_write_ha_state()

    async def async_turn_off(self, **kwargs: Any) -> None:
        """Turn the entity off."""
        if ATTR_TRANSITION in kwargs:
            transition = pypck.lcn_defs.time_to_ramp_value(
                kwargs[ATTR_TRANSITION] * 1000
            )
        else:
            transition = self._transition

        if not await self.device_connection.dim_output(
            self.output.value, 0, transition
        ):
            return
        self._is_dimming_to_zero = bool(transition)
        self._is_on = False
        self.async_write_ha_state()

    def input_received(self, input_obj: InputType) -> None:
        """Set light state when LCN input object (command) is received."""
        if (
            not isinstance(input_obj, pypck.inputs.ModStatusOutput)
            or input_obj.get_output_id() != self.output.value
        ):
            return

        self._brightness = int(input_obj.get_percent() / 100.0 * 255)
        if self.brightness == 0:
            self._is_dimming_to_zero = False
        if not self._is_dimming_to_zero and self.brightness is not None:
            self._is_on = self.brightness > 0
        self.async_write_ha_state()

class LcnRelayLight(LcnEntity, LightEntity):
    """Representation of a LCN light for relay ports."""

    _attr_color_mode = ColorMode.ONOFF
    _attr_supported_color_modes = {ColorMode.ONOFF}

    def __init__(
        self, config: ConfigType, entry_id: str, device_connection: DeviceConnectionType
    ) -> None:
        """Initialize the LCN light."""
        super().__init__(config, entry_id, device_connection)

        self.output = pypck.lcn_defs.RelayPort[config[CONF_DOMAIN_DATA][CONF_OUTPUT]]

        self._is_on = False

    async def async_added_to_hass(self) -> None:
        """Run when entity about to be added to hass."""
        await super().async_added_to_hass()
        if not self.device_connection.is_group:
            await self.device_connection.activate_status_request_handler(self.output)

    async def async_will_remove_from_hass(self) -> None:
        """Run when entity will be removed from hass."""
        await super().async_will_remove_from_hass()
        if not self.device_connection.is_group:
            await self.device_connection.cancel_status_request_handler(self.output)

    @property
    def is_on(self) -> bool:
        """Return True if entity is on."""
        return self._is_on

    async def async_turn_on(self, **kwargs: Any) -> None:
        """Turn the entity on."""
        states = [pypck.lcn_defs.RelayStateModifier.NOCHANGE] * 8
        states[self.output.value] = pypck.lcn_defs.RelayStateModifier.ON
        if not await self.device_connection.control_relays(states):
            return
        self._is_on = True
        self.async_write_ha_state()

    async def async_turn_off(self, **kwargs: Any) -> None:
        """Turn the entity off."""
        states = [pypck.lcn_defs.RelayStateModifier.NOCHANGE] * 8
        states[self.output.value] = pypck.lcn_defs.RelayStateModifier.OFF
        if not await self.device_connection.control_relays(states):
            return
        self._is_on = False
        self.async_write_ha_state()

    def input_received(self, input_obj: InputType) -> None:
        """Set light state when LCN input object (command) is received."""
        if not isinstance(input_obj, pypck.inputs.ModStatusRelays):
            return

        self._is_on = input_obj.get_state(self.output.value)
        self.async_write_ha_state()
<|endoftext|>


<commit_before>import logging
import os
import datetime
import itertools
import shutil
import gzip
import pickle
import yaml
from functools import partial

from hashlib import sha1

from lobster import util

class JobProvider(object):
    def __init__(self, config):
        self.config = config
        self.basedirs = [config['configdir'], config['startdir']]
        self.workdir = config.get('workdir', os.getcwd())
        self.stageout = config.get('stageout location', os.getcwd())
        self.statusfile = os.path.join(self.workdir, 'status.yaml')

        self.extra_inputs = {}
        self.args = {}
        self.outputs = {}
        self.outputformats = {}

        create = not util.checkpoint(self.workdir, 'id')
        if create:
            self.taskid = 'lobster_{0}_{1}'.format(
                self.config['id'],
                sha1(str(datetime.datetime.utcnow())).hexdigest()[-16:])
            with open(self.statusfile, 'wb') as f:
                yaml.dump({'id': self.taskid}, f, default_flow_style=False)
        else:
            self.taskid = util.checkpoint(self.workdir, 'id')

        for cfg in config['tasks']:
            label = cfg['label']

            self.extra_inputs[label] = map(
                    partial(util.findpath, self.basedirs),
                    cfg.get('extra inputs', []))
            self.args[label] = cfg.get('parameters', [])
            self.outputs[label] = []
            self.outputformats[label] = cfg.get("output format", "{base}_{id}.{ext}")

            taskdir = os.path.join(self.workdir, label)
            stageoutdir = os.path.join(self.stageout, label)
            if create:
                for dir in [taskdir, stageoutdir]:
                    if not os.path.exists(dir):
                        os.makedirs(dir)
                    else:
                        # TODO warn about non-empty stageout directories
                        pass

                shutil.copy(config['filepath'], os.path.join(self.workdir, 'lobster_config.yaml'))

    def done(self):
        raise NotImplementedError

    def obtain(self):
        raise NotImplementedError

    def release(self, id, return_code, output, task):
        raise NotImplementedError

    def work_left(self):
        raise NotImplementedError

class SimpleJobProvider(JobProvider):
    def __init__(self, config):
        super(SimpleJobProvider, self).__init__(config)

        self.__cmds = {}
        self.__max = config.get('max')
        self.__done = 0
        self.__running = 0
        self.__id = 0
        self.__unique_args = {}

        self.__labels = itertools.cycle([cfg['label'] for cfg in config['tasks']])

        for cfg in config['tasks']:
            self.__cmds[cfg['label']] = cfg['cmd']
            self.__unique_args[cfg['label']] = cfg.get('unique parameters', [])

    def done(self):
        return self.__done == self.__max

    def obtain(self, num=1, bijective=False):
        if bijective:
            raise NotImplementedError

        tasks = []

        label = self.__labels.next()
        sdir = os.path.join(self.stageout, label)
        for i in range(num):
            if self.__id < self.__max:
                self.__running += 1
                self.__id += 1

                inputs = [(x, os.path.basename(x)) for x in self.extra_inputs[label]]
                outputs = []
                for filename in self.outputs[label]:
                    base, ext = os.path.splitext(filename)
                    outname = self.outputformats[label].format(base=base, ext=ext[1:], id=self.__id)
                    outputs.append((os.path.join(sdir, outname), filename))

                logging.info("creating {0}".format(self.__id))

                cmd = self.__cmds[label]
                if self.args[label]:
                    cmd += ' ' + ' '.join(self.args[label])
                if self.__unique_args[label]:
                    cmd += ' %s' % self.__unique_args[label].pop()
                tasks.append(("{0}_{1}".format(label, self.__id), cmd, inputs, outputs))
            else:
                break

        return tasks

    def release(self, tasks):
        for task in tasks:
            self.__running -= 1
            if task.return_status == 0:
                self.__done += 1
            logging.info("job {0} returned with return code {1} [{2} jobs finished / {3} total ]".format(task.tag, task.return_status, self.__done, self.__max))

            if task.output:
                label, id = task.tag.split('_')
                f = gzip.open(os.path.join(self.workdir, label, id+'_job.log.gz'), 'wb')
                f.write(task.output)
                f.close()

    def work_left(self):
        return self.__max - self.__done
<commit_msg>Include a checkpoint for restarts.<commit_after>import logging
import os
import datetime
import itertools
import shutil
import gzip
import pickle
import yaml
from functools import partial

from hashlib import sha1

from lobster import util

class JobProvider(object):
    def __init__(self, config):
        self.config = config
        self.basedirs = [config['configdir'], config['startdir']]
        self.workdir = config.get('workdir', os.getcwd())
        self.stageout = config.get('stageout location', os.getcwd())
        self.statusfile = os.path.join(self.workdir, 'status.yaml')

        self.extra_inputs = {}
        self.args = {}
        self.outputs = {}
        self.outputformats = {}

        create = not util.checkpoint(self.workdir, 'id')
        if create:
            self.taskid = 'lobster_{0}_{1}'.format(
                self.config['id'],
                sha1(str(datetime.datetime.utcnow())).hexdigest()[-16:])
            with open(self.statusfile, 'wb') as f:
                yaml.dump({'id': self.taskid}, f, default_flow_style=False)
        else:
            self.taskid = util.checkpoint(self.workdir, 'id')
            util.register_checkpoint(self.workdir, 'RESTARTED', str(datetime.datetime.utcnow()))

        for cfg in config['tasks']:
            label = cfg['label']

            self.extra_inputs[label] = map(
                    partial(util.findpath, self.basedirs),
                    cfg.get('extra inputs', []))
            self.args[label] = cfg.get('parameters', [])
            self.outputs[label] = []
            self.outputformats[label] = cfg.get("output format", "{base}_{id}.{ext}")

            taskdir = os.path.join(self.workdir, label)
            stageoutdir = os.path.join(self.stageout, label)
            if create:
                for dir in [taskdir, stageoutdir]:
                    if not os.path.exists(dir):
                        os.makedirs(dir)
                    else:
                        # TODO warn about non-empty stageout directories
                        pass

                shutil.copy(config['filepath'], os.path.join(self.workdir, 'lobster_config.yaml'))

    def done(self):
        raise NotImplementedError

    def obtain(self):
        raise NotImplementedError

    def release(self, id, return_code, output, task):
        raise NotImplementedError

    def work_left(self):
        raise NotImplementedError

class SimpleJobProvider(JobProvider):
    def __init__(self, config):
        super(SimpleJobProvider, self).__init__(config)

        self.__cmds = {}
        self.__max = config.get('max')
        self.__done = 0
        self.__running = 0
        self.__id = 0
        self.__unique_args = {}

        self.__labels = itertools.cycle([cfg['label'] for cfg in config['tasks']])

        for cfg in config['tasks']:
            self.__cmds[cfg['label']] = cfg['cmd']
            self.__unique_args[cfg['label']] = cfg.get('unique parameters', [])

    def done(self):
        return self.__done == self.__max

    def obtain(self, num=1, bijective=False):
        if bijective:
            raise NotImplementedError

        tasks = []

        label = self.__labels.next()
        sdir = os.path.join(self.stageout, label)
        for i in range(num):
            if self.__id < self.__max:
                self.__running += 1
                self.__id += 1

                inputs = [(x, os.path.basename(x)) for x in self.extra_inputs[label]]
                outputs = []
                for filename in self.outputs[label]:
                    base, ext = os.path.splitext(filename)
                    outname = self.outputformats[label].format(base=base, ext=ext[1:], id=self.__id)
                    outputs.append((os.path.join(sdir, outname), filename))

                logging.info("creating {0}".format(self.__id))

                cmd = self.__cmds[label]
                if self.args[label]:
                    cmd += ' ' + ' '.join(self.args[label])
                if self.__unique_args[label]:
                    cmd += ' %s' % self.__unique_args[label].pop()
                tasks.append(("{0}_{1}".format(label, self.__id), cmd, inputs, outputs))
            else:
                break

        return tasks

    def release(self, tasks):
        for task in tasks:
            self.__running -= 1
            if task.return_status == 0:
                self.__done += 1
            logging.info("job {0} returned with return code {1} [{2} jobs finished / {3} total ]".format(task.tag, task.return_status, self.__done, self.__max))

            if task.output:
                label, id = task.tag.split('_')
                f = gzip.open(os.path.join(self.workdir, label, id+'_job.log.gz'), 'wb')
                f.write(task.output)
                f.close()

    def work_left(self):
        return self.__max - self.__done
<|endoftext|>


<commit_before>from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponseRedirect, Http404
from django.template import RequestContext
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.views.generic import date_based
from django.conf import settings

from blog.models import Post
from blog.forms import *
import datetime

try:
    from notification import models as notification
except ImportError:
    notification = None

try:
    from friends.models import Friendship
    friends = True
except ImportError:
    friends = False

def blogs(request, username=None):
    blogs = Post.objects.filter(status=2).order_by("-publish")
    if username is not None:
        user = get_object_or_404(User, username=username.lower())
        blogs = blogs.filter(author=user)
    return render_to_response("blog/blogs.html", {"blogs": blogs}, context_instance=RequestContext(request))

def post(request, username, year, month, slug):
    post = Post.objects.filter(slug=slug, publish__year=int(year), publish__month=int(month)).filter(author__username=username)
    if not post:
        raise Http404

    return render_to_response("blog/post.html", {
        "post": post[0],
    }, context_instance=RequestContext(request))

def your_posts(request):
    user = request.user
    blogs = Post.objects.filter(author=user)

    return render_to_response("blog/your_posts.html", {"blogs": blogs}, context_instance=RequestContext(request))

@login_required
def destroy(request, id):
    post = Post.objects.get(pk=id)
    user = request.user
    title = post.title
    if post.author != request.user:
            request.user.message_set.create(message="You can't delete posts that aren't yours")
            return HttpResponseRedirect(reverse("blog_list_yours"))

    if request.method == "POST" and request.POST["action"] == "delete":
        post.delete()
        request.user.message_set.create(message=_("Successfully deleted post '%s'") % title)
        return HttpResponseRedirect(reverse("blog_list_yours"))
    else:
        return HttpResponseRedirect(reverse("blog_list_yours"))

    return render_to_response(context_instance=RequestContext(request))

@login_required
def new(request):
    if request.method == "POST":
        if request.POST["action"] == "create":
            blog_form = BlogForm(request.user, request.POST)
            if blog_form.is_valid():
                blog = blog_form.save(commit=False)
                blog.author = request.user
                if settings.BEHIND_PROXY:
                    blog.creator_ip = request.META["HTTP_X_FORWARDED_FOR"]
                else:
                    blog.creator_ip = request.META['REMOTE_ADDR']
                blog.save()
                # @@@ should message be different if published?
                request.user.message_set.create(message=_("Successfully saved post '%s'") % blog.title)
                if notification:
                    if blog.status == 2: # published
                        if friends: # @@@ might be worth having a shortcut for sending to all friends
                            notification.send((x['friend'] for x in Friendship.objects.friends_for_user(blog.author)), "blog_friend_post", {"post": blog})

                return HttpResponseRedirect(reverse("blog_list_yours"))
        else:
            blog_form = BlogForm()
    else:
        blog_form = BlogForm()

    return render_to_response("blog/new.html", {
        "blog_form": blog_form
    }, context_instance=RequestContext(request))

@login_required
def edit(request, id):
    post = get_object_or_404(Post, id=id)

    if request.method == "POST":
        if post.author != request.user:
            request.user.message_set.create(message="You can't edit posts that aren't yours")
            return HttpResponseRedirect(reverse("blog_list_yours"))
        if request.POST["action"] == "update":
            blog_form = BlogForm(request.user, request.POST, instance=post)
            if blog_form.is_valid():
                blog = blog_form.save(commit=False)
                blog.save()
                request.user.message_set.create(message=_("Successfully updated post '%s'") % blog.title)
                if notification:
                    if blog.status == 2: # published
                        if friends: # @@@ might be worth having a shortcut for sending to all friends
                            notification.send((x['friend'] for x in Friendship.objects.friends_for_user(blog.author)), "blog_friend_post", {"post": blog})

                return HttpResponseRedirect(reverse("blog_list_yours"))
        else:
            blog_form = BlogForm(instance=post)

    else:
        blog_form = BlogForm(instance=post)

    return render_to_response("blog/edit.html", {
        "blog_form": blog_form,
        "post": post,
    }, context_instance=RequestContext(request))
<commit_msg>throw 404 if the blog entry is a draft and you are not the author<commit_after>from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponseRedirect, Http404
from django.template import RequestContext
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.views.generic import date_based
from django.conf import settings

from blog.models import Post
from blog.forms import *
import datetime

try:
    from notification import models as notification
except ImportError:
    notification = None

try:
    from friends.models import Friendship
    friends = True
except ImportError:
    friends = False

def blogs(request, username=None):
    blogs = Post.objects.filter(status=2).order_by("-publish")
    if username is not None:
        user = get_object_or_404(User, username=username.lower())
        blogs = blogs.filter(author=user)
    return render_to_response("blog/blogs.html", {"blogs": blogs}, context_instance=RequestContext(request))

def post(request, username, year, month, slug):
    post = Post.objects.filter(slug=slug, publish__year=int(year), publish__month=int(month)).filter(author__username=username)
    if not post:
        raise Http404

    if post[0].status == 1 and post[0].author != request.user:
        raise Http404

    return render_to_response("blog/post.html", {
        "post": post[0],
    }, context_instance=RequestContext(request))

def your_posts(request):
    user = request.user
    blogs = Post.objects.filter(author=user)

    return render_to_response("blog/your_posts.html", {"blogs": blogs}, context_instance=RequestContext(request))

@login_required
def destroy(request, id):
    post = Post.objects.get(pk=id)
    user = request.user
    title = post.title
    if post.author != request.user:
            request.user.message_set.create(message="You can't delete posts that aren't yours")
            return HttpResponseRedirect(reverse("blog_list_yours"))

    if request.method == "POST" and request.POST["action"] == "delete":
        post.delete()
        request.user.message_set.create(message=_("Successfully deleted post '%s'") % title)
        return HttpResponseRedirect(reverse("blog_list_yours"))
    else:
        return HttpResponseRedirect(reverse("blog_list_yours"))

    return render_to_response(context_instance=RequestContext(request))

@login_required
def new(request):
    if request.method == "POST":
        if request.POST["action"] == "create":
            blog_form = BlogForm(request.user, request.POST)
            if blog_form.is_valid():
                blog = blog_form.save(commit=False)
                blog.author = request.user
                if settings.BEHIND_PROXY:
                    blog.creator_ip = request.META["HTTP_X_FORWARDED_FOR"]
                else:
                    blog.creator_ip = request.META['REMOTE_ADDR']
                blog.save()
                # @@@ should message be different if published?
                request.user.message_set.create(message=_("Successfully saved post '%s'") % blog.title)
                if notification:
                    if blog.status == 2: # published
                        if friends: # @@@ might be worth having a shortcut for sending to all friends
                            notification.send((x['friend'] for x in Friendship.objects.friends_for_user(blog.author)), "blog_friend_post", {"post": blog})

                return HttpResponseRedirect(reverse("blog_list_yours"))
        else:
            blog_form = BlogForm()
    else:
        blog_form = BlogForm()

    return render_to_response("blog/new.html", {
        "blog_form": blog_form
    }, context_instance=RequestContext(request))

@login_required
def edit(request, id):
    post = get_object_or_404(Post, id=id)

    if request.method == "POST":
        if post.author != request.user:
            request.user.message_set.create(message="You can't edit posts that aren't yours")
            return HttpResponseRedirect(reverse("blog_list_yours"))
        if request.POST["action"] == "update":
            blog_form = BlogForm(request.user, request.POST, instance=post)
            if blog_form.is_valid():
                blog = blog_form.save(commit=False)
                blog.save()
                request.user.message_set.create(message=_("Successfully updated post '%s'") % blog.title)
                if notification:
                    if blog.status == 2: # published
                        if friends: # @@@ might be worth having a shortcut for sending to all friends
                            notification.send((x['friend'] for x in Friendship.objects.friends_for_user(blog.author)), "blog_friend_post", {"post": blog})

                return HttpResponseRedirect(reverse("blog_list_yours"))
        else:
            blog_form = BlogForm(instance=post)

    else:
        blog_form = BlogForm(instance=post)

    return render_to_response("blog/edit.html", {
        "blog_form": blog_form,
        "post": post,
    }, context_instance=RequestContext(request))
<|endoftext|>


<commit_before>import jwt, datetime, re

from skf import settings
from flask import request, abort
from skf.api.restplus import api
from skf.database import db
from skf.database.logs import logs

def security_headers():
    """This decorator passes multiple security headers"""
    return {'X-Frame-Options': 'deny',
            'X-XSS-Protection': '1',
            'X-Content-Type-Options': 'nosniff',
            'Cache-Control': 'no-store, no-cache',
            'Strict-Transport-Security': 'max-age=16070400; includeSubDomains',
            'Server': 'Security Knowledge Framework API'}

def log(message, threat, status):
    """Create log entry and write events triggerd by the user, contains FAIL or SUCCESS and threat LOW MEDIUM HIGH"""
    now = datetime.datetime.now()
    dateLog = now.strftime("%Y-%m-%d")
    dateTime = now.strftime("%H:%M")
    try:
        ip = request.remote_addr
        if request.headers.get('Authorization'):
            token = request.headers.get('Authorization').split()[0]
            checkClaims = jwt.decode(token, settings.JWT_SECRET, algorithms='HS256')
            userID = checkClaims['UserId']
        else:
            userID = "0"
        event = logs(dateLog, dateTime, threat, ip, userID, status, message)
        db.session.add(event)
        db.session.commit()
    except:
        userID = "0"
        ip = "0.0.0.0"
        event = "Datelog: "+dateLog+" "+" Datetime: "+dateTime+" "+"Threat: "+threat+" "+" IP:"+ip+" "+"UserId: "+userID+" "+"Status: "+status+" "+"Message: "+message

def val_alpha(value):
    """User input validation for checking a-zA-Z"""
    match = re.findall(r"[^\w]|[\d]", str(value))
    if match:
        log("User supplied not an a-zA-Z", "MEDIUM", "FAIL")
        abort(400, "Validation Error")
    else:
        return True

def val_alpha_num(value):
    """User input validation for checking a-z A-Z 0-9 _ . -"""
    match = re.findall(r"[^\ \w\.-]", value)
    if match:
        log("User supplied not an a-z A-Z 0-9 _ . - value", "MEDIUM", "FAIL")
        abort(400, "Validation Error")
    else:
        return True

def val_alpha_num_special(value):
    """User input validation for checking a-z A-Z 0-9 _ . - ' , " """
    match = re.findall(r"[^\ \w_\.\-\'\",\+\(\)\/\:@\?\&\=\%]", value)
    if match:
        log("User supplied not an a-z A-Z 0-9 _ . - +' \" , value", "MEDIUM", "FAIL")
        abort(400, "Validation Error")
    else:
        return True

def val_num(value):
    """User input validation for checking numeric values only 0-9"""
    if not isinstance( value, int ):
        log("User supplied not an 0-9", "MEDIUM", "FAIL")
        abort(400, "Validation Error")
    else:
        return True

def val_float(value):
    """User input validation for checking float values only 0-9 ."""
    if not isinstance( value, float ):
        log("User supplied not a float value.", "MEDIUM", "FAIL")
        abort(400, "Validation Error")
    else:
        return True

def validate_privilege(self, privilege):
    """Validates the JWT privileges"""
    if not request.headers.get('Authorization'):
        log("Request sent with missing JWT header", "HIGH", "FAIL")
        abort(403, 'JWT missing authorization header')
    try:
        check_privilege = select_privilege_jwt(self)
    except jwt.exceptions.DecodeError:
        log("User JWT header could not be decoded", "HIGH", "FAIL")
        abort(403, 'JWT decode error')
    except jwt.exceptions.ExpiredSignature:
        log("User JWT header is expired", "HIGH", "FAIL")
        abort(403, 'JWT token expired')
    privileges = check_privilege['privilege'].split(':')
    for value in privileges:
        if value == privilege:
            return True
    log("User JWT header contains wrong privilege", "HIGH", "FAIL")
    return  abort(403, 'JWT wrong privileges')

def select_userid_jwt(self):
    """Returns the userID from the JWT authorization token"""
    token = request.headers.get('Authorization').split()[0]
    try:
        checkClaims = jwt.decode(token, settings.JWT_SECRET, algorithms='HS256')
    except jwt.exceptions.DecodeError:
        log("User JWT header could not be decoded", "HIGH", "FAIL")
        abort(403, 'JWT decode error')
    except jwt.exceptions.ExpiredSignature:
        log("User JWT header is expired", "HIGH", "FAIL")
        abort(403, 'JWT token expired')
    return checkClaims['UserId']

def select_privilege_jwt(self):
    """Returns the privileges from the JWT authorization token"""
    token = request.headers.get('Authorization').split()[0]
    try:
        check_privilege = jwt.decode(token, settings.JWT_SECRET, algorithms='HS256')
    except jwt.exceptions.DecodeError:
        log("User JWT header could not be decoded", "HIGH", "FAIL")
        abort(403, 'JWT decode error')
    except jwt.exceptions.ExpiredSignature:
        log("User JWT header is expired", "HIGH", "FAIL")
        abort(403, 'JWT token expired')
    return check_privilege
<commit_msg>allow question mark in questions string<commit_after>import jwt, datetime, re

from skf import settings
from flask import request, abort
from skf.api.restplus import api
from skf.database import db
from skf.database.logs import logs

def security_headers():
    """This decorator passes multiple security headers"""
    return {'X-Frame-Options': 'deny',
            'X-XSS-Protection': '1',
            'X-Content-Type-Options': 'nosniff',
            'Cache-Control': 'no-store, no-cache',
            'Strict-Transport-Security': 'max-age=16070400; includeSubDomains',
            'Server': 'Security Knowledge Framework API'}

def log(message, threat, status):
    """Create log entry and write events triggerd by the user, contains FAIL or SUCCESS and threat LOW MEDIUM HIGH"""
    now = datetime.datetime.now()
    dateLog = now.strftime("%Y-%m-%d")
    dateTime = now.strftime("%H:%M")
    try:
        ip = request.remote_addr
        if request.headers.get('Authorization'):
            token = request.headers.get('Authorization').split()[0]
            checkClaims = jwt.decode(token, settings.JWT_SECRET, algorithms='HS256')
            userID = checkClaims['UserId']
        else:
            userID = "0"
        event = logs(dateLog, dateTime, threat, ip, userID, status, message)
        db.session.add(event)
        db.session.commit()
    except:
        userID = "0"
        ip = "0.0.0.0"
        event = "Datelog: "+dateLog+" "+" Datetime: "+dateTime+" "+"Threat: "+threat+" "+" IP:"+ip+" "+"UserId: "+userID+" "+"Status: "+status+" "+"Message: "+message

def val_alpha(value):
    """User input validation for checking a-zA-Z"""
    match = re.findall(r"[^\w]|[\d]", str(value))
    if match:
        log("User supplied not an a-zA-Z ?", "MEDIUM", "FAIL")
        abort(400, "Validation Error")
    else:
        return True

def val_alpha_num(value):
    """User input validation for checking a-z A-Z 0-9 _ . - ?"""
    match = re.findall(r"[^\ \w\.-\?]", value)
    if match:
        log("User supplied not an a-z A-Z 0-9 _ . - ? value", "MEDIUM", "FAIL")
        abort(400, "Validation Error")
    else:
        return True

def val_alpha_num_special(value):
    """User input validation for checking a-z A-Z 0-9 _ . - ' , " """
    match = re.findall(r"[^\ \w_\.\-\'\",\+\(\)\/\:@\?\&\=\%]", value)
    if match:
        log("User supplied not an a-z A-Z 0-9 _ . - +' \" , value", "MEDIUM", "FAIL")
        abort(400, "Validation Error")
    else:
        return True

def val_num(value):
    """User input validation for checking numeric values only 0-9"""
    if not isinstance( value, int ):
        log("User supplied not an 0-9", "MEDIUM", "FAIL")
        abort(400, "Validation Error")
    else:
        return True

def val_float(value):
    """User input validation for checking float values only 0-9 ."""
    if not isinstance( value, float ):
        log("User supplied not a float value.", "MEDIUM", "FAIL")
        abort(400, "Validation Error")
    else:
        return True

def validate_privilege(self, privilege):
    """Validates the JWT privileges"""
    if not request.headers.get('Authorization'):
        log("Request sent with missing JWT header", "HIGH", "FAIL")
        abort(403, 'JWT missing authorization header')
    try:
        check_privilege = select_privilege_jwt(self)
    except jwt.exceptions.DecodeError:
        log("User JWT header could not be decoded", "HIGH", "FAIL")
        abort(403, 'JWT decode error')
    except jwt.exceptions.ExpiredSignature:
        log("User JWT header is expired", "HIGH", "FAIL")
        abort(403, 'JWT token expired')
    privileges = check_privilege['privilege'].split(':')
    for value in privileges:
        if value == privilege:
            return True
    log("User JWT header contains wrong privilege", "HIGH", "FAIL")
    return  abort(403, 'JWT wrong privileges')

def select_userid_jwt(self):
    """Returns the userID from the JWT authorization token"""
    token = request.headers.get('Authorization').split()[0]
    try:
        checkClaims = jwt.decode(token, settings.JWT_SECRET, algorithms='HS256')
    except jwt.exceptions.DecodeError:
        log("User JWT header could not be decoded", "HIGH", "FAIL")
        abort(403, 'JWT decode error')
    except jwt.exceptions.ExpiredSignature:
        log("User JWT header is expired", "HIGH", "FAIL")
        abort(403, 'JWT token expired')
    return checkClaims['UserId']

def select_privilege_jwt(self):
    """Returns the privileges from the JWT authorization token"""
    token = request.headers.get('Authorization').split()[0]
    try:
        check_privilege = jwt.decode(token, settings.JWT_SECRET, algorithms='HS256')
    except jwt.exceptions.DecodeError:
        log("User JWT header could not be decoded", "HIGH", "FAIL")
        abort(403, 'JWT decode error')
    except jwt.exceptions.ExpiredSignature:
        log("User JWT header is expired", "HIGH", "FAIL")
        abort(403, 'JWT token expired')
    return check_privilege
<|endoftext|>


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

# MIT license
#
# Copyright (C) 2018 by XESS Corp.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

"""
Functions for finding/displaying parts and footprints.
"""

from __future__ import absolute_import, division, print_function, unicode_literals

import os
import os.path
import re
from builtins import open, super

from future import standard_library

from .common import *
from .logger import logger
from .utilities import *

standard_library.install_aliases()

# TODO: Use push-down automata to parse nested parenthetical expression
#       of AND/OR clauses for use in advanced part searching.
#       https://stackoverflow.com/questions/4284991/parsing-nested-parentheses-in-python-grab-content-by-level

def parse_search_terms(terms):
    """
    Return a regular expression for a sequence of search terms.

    Substitute a zero-width lookahead assertion (?= ) for each term. Thus,
    the "abc def" would become "(?=.*(abc))(?=.*(def))" and would match any string
    containing both "abc" and "def". Or "abc (def|ghi)" would become
    "(?=.*(abc))((?=.*(def|ghi))" and would match any string containing
    "abc" and "def" or "ghi". Quoted terms can be used for phrases containing
    whitespace.
    """

    # Place the quote-delimited REs before the RE for sequences of
    # non-white chars to prevent the initial portion of a quoted string from being
    # gathered up as a non-white character sequence.
    terms = terms.strip().rstrip()  # Remove leading/trailing spaces.
    terms = re.sub(r"\s*\|\s*", r"|", terms) # Remove spaces around OR operator.
    terms = re.sub(r"((\".*?\")|(\'.*?\')|(\S+))\s*", r"(?=.*(\1))", terms)
    terms = re.sub(r"[\'\"]", "", terms) # Remove quotes.
    terms = terms + '.*'
    return terms

def search_parts_iter(terms, tool=None):
    """Return a list of (lib, part) sequences that match a regex term."""

    import skidl
    from .schlib import SchLib

    if tool is None:
        tool = skidl.get_default_tool()

    terms = parse_search_terms(terms)

    def mk_list(l):
        """Make a list out of whatever is given."""
        if isinstance(l, (list, tuple)):
            return l
        if not l:
            return []
        return [l]

    # Gather all the lib files from all the directories in the search paths.
    lib_files = list()
    lib_suffixes = tuple(to_list(skidl.lib_suffixes[tool]))
    for lib_dir in skidl.lib_search_paths[tool]:

        # Get all the library files in the search path.
        try:
            files = os.listdir(lib_dir)
        except (FileNotFoundError, OSError):
            logger.warning("Could not open directory '{}'".format(lib_dir))
            files = []

        files = [(lib_dir, l) for l in files if l.endswith(lib_suffixes)]
        lib_files.extend(files)

    num_lib_files = len(lib_files)

    # Now search through the lib files for parts that match the search terms.
    for idx, (lib_dir, lib_file) in enumerate(lib_files):

        # If just entered a new lib file, yield the name of the file and
        # where it is within the total number of files to search.
        # (This is used for progress indicators.)
        yield "LIB", lib_file, idx + 1, num_lib_files

        # Parse the lib file to create a part library.
        lib = SchLib(
            os.path.join(lib_dir, lib_file), tool=tool
        )  # Open the library file.

        # Search the current library for parts with the given terms.
        for part in mk_list(
            # Get any matching parts from the library file.
            lib.get_parts(use_backup_lib=False, search_text=terms)
        ):
            # Parse the part to instantiate the complete object.
            part.parse(get_name_only=True)

            # Yield the part and its containing library.
            yield "PART", lib_file, part, part.name

            # Also return aliases.
            for alias in list(part.aliases):
                yield "PART", lib_file, part, alias

def search_parts(terms, tool=None):
    """
    Print a list of parts with the regex terms within their name, alias, description or keywords.
    """

    parts = set()
    for part in search_parts_iter(terms, tool):
        if part[0] == "LIB":
            print(" " * 79, "\rSearching {} ...".format(part[1]), end="\r")
        elif part[0] == "PART":
            parts.add(part[1:4])
    print(" " * 79, end="\r")

    # Print each part name sorted by the library where it was found.
    for lib_file, part, part_name in sorted(list(parts), key=lambda p: p[0]):
        print(
            "{}: {} ({})".format(
                lib_file, part_name, getattr(part, "description", "???")
            )
        )

def show_part(lib, part_name, tool=None):
    """
    Print the I/O pins for a given part in a library.

    Args:
        lib: Either a SchLib object or the name of a library.
        part_name: The name of the part in the library.
        tool: The ECAD tool format for the library.

    Returns:
        A Part object.
    """

    import skidl
    from .part import Part
    from .defines import TEMPLATE

    if tool is None:
        tool = skidl.get_default_tool()
    try:
        return Part(lib, re.escape(part_name), tool=tool, dest=TEMPLATE)
    except Exception:
        return None

class FootprintCache(dict):
    """Dict for storing footprints from all directories."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.reset()  # Cache starts off empty, hence invalid.

    def reset(self):
        self.clear()  # Clear out cache.
        self.valid = False  # Cache is empty, hence invalid.

    def load(self, path):
        """Load cache with footprints from libraries in fp-lib-table file."""

        # Read contents of footprint library file into a single string.
        try:
            # Look for fp-lib-table file.
            with open(os.path.join(path, "fp-lib-table")) as fp:
                tbl = fp.read()
        except FileNotFoundError:
            # fp-lib-table file was not found, so check the directory for footprints.
            nickname, ext = os.path.splitext(os.path.basename(path))
            tbl = '(fp_lib_table\n(lib (name {nickname})(type KiCad)(uri {path})(options "")(descr ""))\n)'.format(
                **locals()
            )

        # Get individual "(lib ...)" entries from the string.
        libs = re.findall(
            r"\(\s*lib\s* .*? \)\)", tbl, flags=re.IGNORECASE | re.VERBOSE | re.DOTALL
        )

        # Add the footprint modules found in each enabled KiCad libray.
        for lib in libs:

            # Skip disabled libraries.
            disabled = re.findall(
                r"\(\s*disabled\s*\)", lib, flags=re.IGNORECASE | re.VERBOSE
            )
            if disabled:
                continue

            # Skip non-KiCad libraries (primarily git repos).
            type_ = re.findall(
                r'(?:\(\s*type\s*) ("[^"]*?"|[^)]*?) (?:\s*\))',
                lib,
                flags=re.IGNORECASE | re.VERBOSE,
            )[0]
            if type_.lower() != "kicad":
                continue

            # Get the library directory and nickname.
            uri = re.findall(
                r'(?:\(\s*uri\s*) ("[^"]*?"|[^)]*?) (?:\s*\))',
                lib,
                flags=re.IGNORECASE | re.VERBOSE,
            )[0]
            nickname = re.findall(
                r'(?:\(\s*name\s*) ("[^"]*?"|[^)]*?) (?:\s*\))',
                lib,
                flags=re.IGNORECASE | re.VERBOSE,
            )[0]

            # Remove any quotes around the URI or nickname.
            uri = rmv_quotes(uri)
            nickname = rmv_quotes(nickname)

            # Expand variables and ~ in the URI.
            uri = os.path.expandvars(os.path.expanduser(uri))

            # Get a list of all the footprint module files in the top-level of the library URI.
            filenames = [
                fn
                for fn in os.listdir(uri)
                if os.path.isfile(os.path.join(uri, fn))
                and fn.lower().endswith(".kicad_mod")
            ]

            # Create an entry in the cache for this nickname. (This will overwrite
            # any previous nickname entry, so make sure to scan fp-lib-tables in order of
            # increasing priority.) Each entry contains the path to the directory containing
            # the footprint module and a dictionary of the modules keyed by the module name
            # with an associated value containing the module file contents (which starts off
            # as None).
            self[nickname] = {
                "path": uri,
                "modules": {os.path.splitext(fn)[0]: None for fn in filenames},
            }

# Cache for storing footprints read from .kicad_mod files.
footprint_cache = FootprintCache()

def search_footprints_iter(terms, tool=None):
    """Return a list of (lib, footprint) sequences that match a regex term."""

    import skidl

    if tool is None:
        tool = skidl.get_default_tool()

    terms = parse_search_terms(terms)

    # If the cache isn't valid, then make it valid by gathering all the
    # footprint files from all the directories in the search paths.
    if not footprint_cache.valid:
        footprint_cache.clear()
        for path in skidl.footprint_search_paths[tool]:
            footprint_cache.load(path)

    # Get the number of footprint libraries to be searched..
    num_fp_libs = len(footprint_cache)

    # Now search through the libraries for footprints that match the search terms.
    for idx, fp_lib in enumerate(footprint_cache):

        # If just entered a new library, yield the name of the lib and
        # where it is within the total number of libs to search.
        # (This is used for progress indicators.)
        yield "LIB", fp_lib, idx + 1, num_fp_libs

        # Get path to library directory and dict of footprint modules.
        path = footprint_cache[fp_lib]["path"]
        modules = footprint_cache[fp_lib]["modules"]

        # Search each module in the library.
        for module_name in modules:

            # If the cache isn't valid, then read each footprint file and store
            # it's contents in the cache.
            if not footprint_cache.valid:
                file = os.path.join(path, module_name + ".kicad_mod")
                with open(file, "r") as fp:
                    try:
                        # Remove any linefeeds that would interfere with fullmatch() later on.
                        modules[module_name] = [l.rstrip() for l in fp.readlines()]
                    except UnicodeDecodeError:
                        try:
                            modules[module_name] = [
                                l.decode("utf-8").rstrip() for l in fp.readlines()
                            ]
                        except AttributeError:
                            modules[module_name] = ""

            # Get the contents of the footprint file from the cache.
            module_text = tuple(modules[module_name])

            # Count the pads so it can be added to the text being searched.
            # Join all the module text lines, search for the number of
            # occurrences of "(pad", and then count them.
            # A set is used so pads with the same num/name are only counted once.
            # Place the pad count before everything else so the space that
            # terminates it won't be stripped off later. This is necessary
            # so (for example) "#pads=20 " won't match "#pads=208".
            num_pads = len(
                set(re.findall(r"\(\s*pad\s+([^\s)]+)", " ".join(module_text)))
            )
            num_pads_str = "#pads={}".format(num_pads)

            # Create a string with the module name, library name, number of pads,
            # description and tags.
            search_text = "\n".join([num_pads_str, fp_lib, module_name])
            for line in module_text:
                if "(descr " in line or "(tags " in line:
                    search_text = "\n".join([search_text, line])

            # Search the string for a match with the search terms.
            if fullmatch(
                terms, search_text, flags=re.IGNORECASE | re.MULTILINE | re.DOTALL
            ):
                yield "MODULE", fp_lib, module_text, module_name

    # At the end, all modules have been scanned and the footprint cache is valid.
    footprint_cache.valid = True

def search_footprints(terms, tool=None):
    """
    Print a list of footprints with the regex term within their description/tags.
    """

    footprints = []
    for fp in search_footprints_iter(terms, tool):
        if fp[0] == "LIB":
            print(" " * 79, "\rSearching {} ...".format(fp[1]), end="\r")
        elif fp[0] == "MODULE":
            footprints.append(fp[1:4])
    print(" " * 79, end="\r")

    # Print each module name sorted by the library where it was found.
    for lib_file, module_text, module_name in sorted(
        footprints, key=lambda f: (f[0], f[2])
    ):
        descr = "???"
        tags = "???"
        for line in module_text:
            try:
                descr = line.split("(descr ")[1].rsplit(")", 1)[0]
            except IndexError:
                pass
            try:
                tags = line.split("(tags ")[1].rsplit(")", 1)[0]
            except IndexError:
                pass
        print("{}: {} ({} - {})".format(lib_file, module_name, descr, tags))

def show_footprint(lib, module_name, tool=None):
    """
    Print the pads for a given module in a library.

    Args:
        lib: The name of a library.
        module_name: The name of the footprint in the library.
        tool: The ECAD tool format for the library.

    Returns:
        A Part object.
    """

    import skidl

    if tool is None:
        tool = skidl.get_default_tool()

    os.environ["KISYSMOD"] = os.pathsep.join(skidl.footprint_search_paths[tool])
    return pym.Module.from_library(lib, module_name)

# Define some shortcuts.
search = search_parts
show = show_part
<commit_msg>Update part_query.py<commit_after># -*- coding: utf-8 -*-

# MIT license
#
# Copyright (C) 2018 by XESS Corp.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

"""
Functions for finding/displaying parts and footprints.
"""

from __future__ import absolute_import, division, print_function, unicode_literals

import os
import os.path
import re
from builtins import open, super

from future import standard_library

from .common import *
from .logger import logger
from .utilities import *

standard_library.install_aliases()

# TODO: Use push-down automata to parse nested parenthetical expression
#       of AND/OR clauses for use in advanced part searching.
#       https://stackoverflow.com/questions/4284991/parsing-nested-parentheses-in-python-grab-content-by-level

def parse_search_terms(terms):
    """
    Return a regular expression for a sequence of search terms.

    Substitute a zero-width lookahead assertion (?= ) for each term. Thus,
    the "abc def" would become "(?=.*(abc))(?=.*(def))" and would match any string
    containing both "abc" and "def". Or "abc (def|ghi)" would become
    "(?=.*(abc))((?=.*(def|ghi))" and would match any string containing
    "abc" and "def" or "ghi". Quoted terms can be used for phrases containing
    whitespace.
    """

    # Place the quote-delimited REs before the RE for sequences of
    # non-white chars to prevent the initial portion of a quoted string from being
    # gathered up as a non-white character sequence.
    terms = terms.strip().rstrip()  # Remove leading/trailing spaces.
    terms = re.sub(r"\s*\|\s*", r"|", terms) # Remove spaces around OR operator.
    terms = re.sub(r"((\".*?\")|(\'.*?\')|(\S+))\s*", r"(?=.*(\1))", terms)
    terms = re.sub(r"[\'\"]", "", terms) # Remove quotes.
    terms = terms + '.*'
    return terms

def search_parts_iter(terms, tool=None):
    """Return a list of (lib, part) sequences that match a regex term."""

    import skidl
    from .schlib import SchLib

    if tool is None:
        tool = skidl.get_default_tool()

    terms = parse_search_terms(terms)

    def mk_list(l):
        """Make a list out of whatever is given."""
        if isinstance(l, (list, tuple)):
            return l
        if not l:
            return []
        return [l]

    # Gather all the lib files from all the directories in the search paths.
    lib_files = list()
    lib_suffixes = tuple(to_list(skidl.lib_suffixes[tool]))
    for lib_dir in skidl.lib_search_paths[tool]:

        # Get all the library files in the search path.
        try:
            files = os.listdir(lib_dir)
        except (FileNotFoundError, OSError):
            logger.warning("Could not open directory '{}'".format(lib_dir))
            files = []

        files = [(lib_dir, l) for l in files if l.endswith(lib_suffixes)]
        lib_files.extend(files)

    num_lib_files = len(lib_files)

    # Now search through the lib files for parts that match the search terms.
    for idx, (lib_dir, lib_file) in enumerate(lib_files):

        # If just entered a new lib file, yield the name of the file and
        # where it is within the total number of files to search.
        # (This is used for progress indicators.)
        yield "LIB", lib_file, idx + 1, num_lib_files

        # Parse the lib file to create a part library.
        lib = SchLib(
            os.path.join(lib_dir, lib_file), tool=tool
        )  # Open the library file.

        # Search the current library for parts with the given terms.
        for part in mk_list(
            # Get any matching parts from the library file.
            lib.get_parts(use_backup_lib=False, search_text=terms)
        ):
            # Parse the part to instantiate the complete object.
            part.parse(get_name_only=True)

            # Yield the part and its containing library.
            yield "PART", lib_file, part, part.name

            # Also return aliases.
            for alias in list(part.aliases):
                yield "PART", lib_file, part, alias

def search_parts(terms, tool=None):
    """
    Print a list of parts with the regex terms within their name, alias, description or keywords.
    """

    parts = set()
    for part in search_parts_iter(terms, tool):
        if part[0] == "LIB":
            print(" " * 79, "\rSearching {} ...".format(part[1]), sep="", end="\r")
        elif part[0] == "PART":
            parts.add(part[1:4])
    print(" " * 79, end="\r")

    # Print each part name sorted by the library where it was found.
    for lib_file, part, part_name in sorted(list(parts), key=lambda p: p[0]):
        print(
            "{}: {} ({})".format(
                lib_file, part_name, getattr(part, "description", "???")
            )
        )

def show_part(lib, part_name, tool=None):
    """
    Print the I/O pins for a given part in a library.

    Args:
        lib: Either a SchLib object or the name of a library.
        part_name: The name of the part in the library.
        tool: The ECAD tool format for the library.

    Returns:
        A Part object.
    """

    import skidl
    from .part import Part
    from .defines import TEMPLATE

    if tool is None:
        tool = skidl.get_default_tool()
    try:
        return Part(lib, re.escape(part_name), tool=tool, dest=TEMPLATE)
    except Exception:
        return None

class FootprintCache(dict):
    """Dict for storing footprints from all directories."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.reset()  # Cache starts off empty, hence invalid.

    def reset(self):
        self.clear()  # Clear out cache.
        self.valid = False  # Cache is empty, hence invalid.

    def load(self, path):
        """Load cache with footprints from libraries in fp-lib-table file."""

        # Read contents of footprint library file into a single string.
        try:
            # Look for fp-lib-table file.
            with open(os.path.join(path, "fp-lib-table")) as fp:
                tbl = fp.read()
        except FileNotFoundError:
            # fp-lib-table file was not found, so check the directory for footprints.
            nickname, ext = os.path.splitext(os.path.basename(path))
            tbl = '(fp_lib_table\n(lib (name {nickname})(type KiCad)(uri {path})(options "")(descr ""))\n)'.format(
                **locals()
            )

        # Get individual "(lib ...)" entries from the string.
        libs = re.findall(
            r"\(\s*lib\s* .*? \)\)", tbl, flags=re.IGNORECASE | re.VERBOSE | re.DOTALL
        )

        # Add the footprint modules found in each enabled KiCad libray.
        for lib in libs:

            # Skip disabled libraries.
            disabled = re.findall(
                r"\(\s*disabled\s*\)", lib, flags=re.IGNORECASE | re.VERBOSE
            )
            if disabled:
                continue

            # Skip non-KiCad libraries (primarily git repos).
            type_ = re.findall(
                r'(?:\(\s*type\s*) ("[^"]*?"|[^)]*?) (?:\s*\))',
                lib,
                flags=re.IGNORECASE | re.VERBOSE,
            )[0]
            if type_.lower() != "kicad":
                continue

            # Get the library directory and nickname.
            uri = re.findall(
                r'(?:\(\s*uri\s*) ("[^"]*?"|[^)]*?) (?:\s*\))',
                lib,
                flags=re.IGNORECASE | re.VERBOSE,
            )[0]
            nickname = re.findall(
                r'(?:\(\s*name\s*) ("[^"]*?"|[^)]*?) (?:\s*\))',
                lib,
                flags=re.IGNORECASE | re.VERBOSE,
            )[0]

            # Remove any quotes around the URI or nickname.
            uri = rmv_quotes(uri)
            nickname = rmv_quotes(nickname)

            # Expand variables and ~ in the URI.
            uri = os.path.expandvars(os.path.expanduser(uri))

            # Get a list of all the footprint module files in the top-level of the library URI.
            filenames = [
                fn
                for fn in os.listdir(uri)
                if os.path.isfile(os.path.join(uri, fn))
                and fn.lower().endswith(".kicad_mod")
            ]

            # Create an entry in the cache for this nickname. (This will overwrite
            # any previous nickname entry, so make sure to scan fp-lib-tables in order of
            # increasing priority.) Each entry contains the path to the directory containing
            # the footprint module and a dictionary of the modules keyed by the module name
            # with an associated value containing the module file contents (which starts off
            # as None).
            self[nickname] = {
                "path": uri,
                "modules": {os.path.splitext(fn)[0]: None for fn in filenames},
            }

# Cache for storing footprints read from .kicad_mod files.
footprint_cache = FootprintCache()

def search_footprints_iter(terms, tool=None):
    """Return a list of (lib, footprint) sequences that match a regex term."""

    import skidl

    if tool is None:
        tool = skidl.get_default_tool()

    terms = parse_search_terms(terms)

    # If the cache isn't valid, then make it valid by gathering all the
    # footprint files from all the directories in the search paths.
    if not footprint_cache.valid:
        footprint_cache.clear()
        for path in skidl.footprint_search_paths[tool]:
            footprint_cache.load(path)

    # Get the number of footprint libraries to be searched..
    num_fp_libs = len(footprint_cache)

    # Now search through the libraries for footprints that match the search terms.
    for idx, fp_lib in enumerate(footprint_cache):

        # If just entered a new library, yield the name of the lib and
        # where it is within the total number of libs to search.
        # (This is used for progress indicators.)
        yield "LIB", fp_lib, idx + 1, num_fp_libs

        # Get path to library directory and dict of footprint modules.
        path = footprint_cache[fp_lib]["path"]
        modules = footprint_cache[fp_lib]["modules"]

        # Search each module in the library.
        for module_name in modules:

            # If the cache isn't valid, then read each footprint file and store
            # it's contents in the cache.
            if not footprint_cache.valid:
                file = os.path.join(path, module_name + ".kicad_mod")
                with open(file, "r") as fp:
                    try:
                        # Remove any linefeeds that would interfere with fullmatch() later on.
                        modules[module_name] = [l.rstrip() for l in fp.readlines()]
                    except UnicodeDecodeError:
                        try:
                            modules[module_name] = [
                                l.decode("utf-8").rstrip() for l in fp.readlines()
                            ]
                        except AttributeError:
                            modules[module_name] = ""

            # Get the contents of the footprint file from the cache.
            module_text = tuple(modules[module_name])

            # Count the pads so it can be added to the text being searched.
            # Join all the module text lines, search for the number of
            # occurrences of "(pad", and then count them.
            # A set is used so pads with the same num/name are only counted once.
            # Place the pad count before everything else so the space that
            # terminates it won't be stripped off later. This is necessary
            # so (for example) "#pads=20 " won't match "#pads=208".
            num_pads = len(
                set(re.findall(r"\(\s*pad\s+([^\s)]+)", " ".join(module_text)))
            )
            num_pads_str = "#pads={}".format(num_pads)

            # Create a string with the module name, library name, number of pads,
            # description and tags.
            search_text = "\n".join([num_pads_str, fp_lib, module_name])
            for line in module_text:
                if "(descr " in line or "(tags " in line:
                    search_text = "\n".join([search_text, line])

            # Search the string for a match with the search terms.
            if fullmatch(
                terms, search_text, flags=re.IGNORECASE | re.MULTILINE | re.DOTALL
            ):
                yield "MODULE", fp_lib, module_text, module_name

    # At the end, all modules have been scanned and the footprint cache is valid.
    footprint_cache.valid = True

def search_footprints(terms, tool=None):
    """
    Print a list of footprints with the regex term within their description/tags.
    """

    footprints = []
    for fp in search_footprints_iter(terms, tool):
        if fp[0] == "LIB":
            print(" " * 79, "\rSearching {} ...".format(fp[1]), end="\r")
        elif fp[0] == "MODULE":
            footprints.append(fp[1:4])
    print(" " * 79, end="\r")

    # Print each module name sorted by the library where it was found.
    for lib_file, module_text, module_name in sorted(
        footprints, key=lambda f: (f[0], f[2])
    ):
        descr = "???"
        tags = "???"
        for line in module_text:
            try:
                descr = line.split("(descr ")[1].rsplit(")", 1)[0]
            except IndexError:
                pass
            try:
                tags = line.split("(tags ")[1].rsplit(")", 1)[0]
            except IndexError:
                pass
        print("{}: {} ({} - {})".format(lib_file, module_name, descr, tags))

def show_footprint(lib, module_name, tool=None):
    """
    Print the pads for a given module in a library.

    Args:
        lib: The name of a library.
        module_name: The name of the footprint in the library.
        tool: The ECAD tool format for the library.

    Returns:
        A Part object.
    """

    import skidl

    if tool is None:
        tool = skidl.get_default_tool()

    os.environ["KISYSMOD"] = os.pathsep.join(skidl.footprint_search_paths[tool])
    return pym.Module.from_library(lib, module_name)

# Define some shortcuts.
search = search_parts
show = show_part
<|endoftext|>


<commit_before>#!/usr/bin/env python3

"""
slackin - Slack invite generator

Usage:
    slackin [-p <port>] [-i <ms>] [-c <channels>] <slack-subdomain> <api-token>
    slackin (-h | --help)
    slackin (-v | --version)

Options:
    <slack-subdomain>        The name/subdomain of your team
    <api-token>              An authorized API token
    -h --help                Output usage information
    -v --version             Output the version number
    -p --port <port>         Port to listen on (default: 3000)
    -c --channels <channels> Comma-seperated list of channels
    -i --interval <ms>       How frequently to poll Slack (default: 1000)
"""

VERSION = "v1.0"

import os
import sys
import json
import time
import copy
import uuid

import requests
import docopt

from flask import Flask, render_template, request, send_from_directory, make_response
from werkzeug.contrib.cache import SimpleCache

cache = SimpleCache()
app = Flask(__name__, static_url_path="/static")

def get_data():
    team = cache.get("team")
    if team is None:
        print("==> Updating team data ...")
        r = requests.get("https://slack.com/api/team.info", params={
            "token": app.config["token"]})
        j = r.json()
        assert j["ok"]
        team = j["team"]
        cache.set("team", team, timeout=(app.config["interval"] / 1000))

    users = cache.get("users")
    if users is None:
        print("==> Updating user data ...")
        r = requests.get("https://slack.com/api/users.list", params={
            "token": app.config["token"],
            "presence": 1})
        j = r.json()
        assert j["ok"]
        users = j["members"]
        cache.set("users", users, timeout=(app.config["interval"] / 1000))

    return team, users

@app.route("/")
def index():
    team, users = get_data()

    users = [u for u in users if not u["is_bot"] and not u["deleted"]]
    active = [u for u in users if u["presence"] == "active"]

    return render_template("index.html",
        subdomain=team["domain"],
        logo=team["icon"]["image_132"],
        users_active=len(active),
        users_total=len(users))

@app.route("/invite", methods=["POST"])
def invite():
    r = requests.post("https://slack.com/api/users.admin.invite", data={
        "token": app.config["token"],
        "email": request.json["email"]})
    assert r.json()["ok"]
    return "success"

@app.route("/slackin.js")
def badge_js():
    return app.send_static_file("badge.js")

@app.route("/iframe")
def iframe():
    team, users = get_data()

    users = [u for u in users if not u["is_bot"] and not u["deleted"]]
    active = [u for u in users if u["presence"] == "active"]

    return render_template("iframe.html",
        large="slack-btn-large" if "large" in request.args.keys() else "",
        users_active=len(active),
        users_total=len(users))

@app.route("/iframe/dialog")
def dialog():
    team, users = get_data()

    users = [u for u in users if not u["is_bot"] and not u["deleted"]]
    active = [u for u in users if u["presence"] == "active"]

    return render_template("dialog.html",
        subdomain=team["domain"],
        users_active=len(active),
        users_total=len(users))

@app.route("/badge.svg")
def badge_svg():
    _, users = get_data()

    users = [u for u in users if not u["is_bot"] and not u["deleted"]]
    active = [u for u in users if u["presence"] == "active"]
    users, active = len(users), len(active)

    if active > 0:
        value = "{}/{}".format(active, users)
    else:
        value = str(users) if users > 0 else "-"

    left_width = 47
    right_width = 12 + len(value) * 7

    svg = render_template('badge.svg',
        value=value,
        left_width=left_width,
        right_width=right_width,
        total_width=(left_width + right_width),
        left_x=round(left_width / 2),
        right_x=round(right_width / 2) + left_width)
    response = make_response(svg)
    response.content_type = 'image/svg+xml'
    return response

@app.route("/socket.io/")
def socket():
    return "", 501
    # not implemented because i cannot be arsed
    # also because flask_socketio doesn't support Py3

def main():
    args = docopt.docopt(__doc__, version=VERSION)
    if args["--port"] is None:
        args["--port"] = 3000
    if args["--interval"] is None:
        args["--interval"] = 1000

    app.config["token"] = args["<api-token>"]
    app.config["team"] = args["<slack-subdomain>"]
    app.config["interval"] = int(args["--interval"])
    #app.config["channels"] = args["--channels"]
    # also not implemented because i cannot be arsed

    app.run(host="0.0.0.0", port=int(args["--port"]), debug=os.environ.get("DEBUG"))
<commit_msg>Handling of empty email addresses<commit_after>#!/usr/bin/env python3

"""
slackin - Slack invite generator

Usage:
    slackin [-p <port>] [-i <ms>] [-c <channels>] <slack-subdomain> <api-token>
    slackin (-h | --help)
    slackin (-v | --version)

Options:
    <slack-subdomain>        The name/subdomain of your team
    <api-token>              An authorized API token
    -h --help                Output usage information
    -v --version             Output the version number
    -p --port <port>         Port to listen on (default: 3000)
    -c --channels <channels> Comma-seperated list of channels
    -i --interval <ms>       How frequently to poll Slack (default: 1000)
"""

VERSION = "v1.0"

import os
import sys
import json
import time
import copy
import uuid

import requests
import docopt

from flask import Flask, render_template, request, send_from_directory, make_response
from werkzeug.contrib.cache import SimpleCache

cache = SimpleCache()
app = Flask(__name__, static_url_path="/static")

def get_data():
    team = cache.get("team")
    if team is None:
        print("==> Updating team data ...")
        r = requests.get("https://slack.com/api/team.info", params={
            "token": app.config["token"]})
        j = r.json()
        assert j["ok"]
        team = j["team"]
        cache.set("team", team, timeout=(app.config["interval"] / 1000))

    users = cache.get("users")
    if users is None:
        print("==> Updating user data ...")
        r = requests.get("https://slack.com/api/users.list", params={
            "token": app.config["token"],
            "presence": 1})
        j = r.json()
        assert j["ok"]
        users = j["members"]
        cache.set("users", users, timeout=(app.config["interval"] / 1000))

    return team, users

@app.route("/")
def index():
    team, users = get_data()

    users = [u for u in users if not u["is_bot"] and not u["deleted"]]
    active = [u for u in users if u["presence"] == "active"]

    return render_template("index.html",
        subdomain=team["domain"],
        logo=team["icon"]["image_132"],
        users_active=len(active),
        users_total=len(users))

@app.route("/invite", methods=["POST"])
def invite():
    if "@" not in request.json["email"]:
        return "missing email", 400
    r = requests.post("https://slack.com/api/users.admin.invite", data={
        "token": app.config["token"],
        "email": request.json["email"]})
    assert r.json()["ok"]
    return "success"

@app.route("/slackin.js")
def badge_js():
    return app.send_static_file("badge.js")

@app.route("/iframe")
def iframe():
    team, users = get_data()

    users = [u for u in users if not u["is_bot"] and not u["deleted"]]
    active = [u for u in users if u["presence"] == "active"]

    return render_template("iframe.html",
        large="slack-btn-large" if "large" in request.args.keys() else "",
        users_active=len(active),
        users_total=len(users))

@app.route("/iframe/dialog")
def dialog():
    team, users = get_data()

    users = [u for u in users if not u["is_bot"] and not u["deleted"]]
    active = [u for u in users if u["presence"] == "active"]

    return render_template("dialog.html",
        subdomain=team["domain"],
        users_active=len(active),
        users_total=len(users))

@app.route("/badge.svg")
def badge_svg():
    _, users = get_data()

    users = [u for u in users if not u["is_bot"] and not u["deleted"]]
    active = [u for u in users if u["presence"] == "active"]
    users, active = len(users), len(active)

    if active > 0:
        value = "{}/{}".format(active, users)
    else:
        value = str(users) if users > 0 else "-"

    left_width = 47
    right_width = 12 + len(value) * 7

    svg = render_template('badge.svg',
        value=value,
        left_width=left_width,
        right_width=right_width,
        total_width=(left_width + right_width),
        left_x=round(left_width / 2),
        right_x=round(right_width / 2) + left_width)
    response = make_response(svg)
    response.content_type = 'image/svg+xml'
    return response

@app.route("/socket.io/")
def socket():
    return "", 501
    # not implemented because i cannot be arsed
    # also because flask_socketio doesn't support Py3

def main():
    args = docopt.docopt(__doc__, version=VERSION)
    if args["--port"] is None:
        args["--port"] = 3000
    if args["--interval"] is None:
        args["--interval"] = 1000

    app.config["token"] = args["<api-token>"]
    app.config["team"] = args["<slack-subdomain>"]
    app.config["interval"] = int(args["--interval"])
    #app.config["channels"] = args["--channels"]
    # also not implemented because i cannot be arsed

    app.run(host="0.0.0.0", port=int(args["--port"]), debug=os.environ.get("DEBUG"))
<|endoftext|>