<commit_before>import base64
import json
import logging
import os
import re
import threading
from collections import Mapping
from datetime import datetime
from inspect import getfullargspec
from pathlib import Path
from pkgutil import iter_modules
from typing import Any, Optional, Union, Generator, Tuple

from bs4 import BeautifulSoup
from requests import Response
from torrentool.api import Torrent

if False:  # pragma: nocover
    from .base_tracker import GenericTracker  # noqa
    from .base_rpc import BaseRPC  # noqa
    from .base_bot import BaseBot  # noqa
    from .base_notifier import BaseNotifier  # noqa

LOGGER = logging.getLogger(__name__)

_THREAD_LOCAL = threading.local()

# This regex is used to get hyperlink from torrent comment.
RE_LINK = re.compile(r'(?P<url>https?://[^\s]+)')

def encode_value(value: str, encoding: str = None) -> Union[str, bytes]:
    """Encodes a value.

    :param value:
    :param encoding: Encoding charset.

    """
    if encoding is None:
        return value

    return value.encode(encoding)

def base64encode(string_or_bytes: Union[str, bytes]) -> bytes:
    """Return base64 encoded input

    :param string_or_bytes:

    """
    if isinstance(string_or_bytes, str):
        string_or_bytes = string_or_bytes.encode()

    return base64.encodebytes(string_or_bytes).decode('ascii').encode()

class GlobalParam:
    """Represents global parameter value holder.
    Global params can used anywhere in torrt.

    """
    @staticmethod
    def set(name: str, value: Any):
        setattr(_THREAD_LOCAL, name, value)

    @staticmethod
    def get(name: str) -> Any:
        return getattr(_THREAD_LOCAL, name, None)

def dump_contents(filename: str, contents: Union[bytes, Response]):
    """Dumps contents into a file with a given name.

    :param filename:
    :param contents:

    """
    dump_into = GlobalParam.get('dump_into')

    if not dump_into:
        return

    if hasattr(contents, 'encode_contents'):
        # soup
        text = contents.encode_contents()

    else:
        # requests lib response
        text = contents.content

    with open(str(Path(dump_into) / filename), 'wb') as f:
        f.write(text)

def configure_entity(type_name: str, registry, alias: str, settings_dict: dict = None) -> Optional['WithSettings']:
    """Configures and spawns objects using given settings.

    Successful configuration is saved.

    :param type_name: Entity type name to be used in massages.
    :param registry: Registry object.
    :param alias: Entity alias.
    :param settings_dict: Settings dictionary to configure object with.

    """
    LOGGER.info(f'Configuring `{alias}` {type_name.lower()} ...')

    entity_cls = registry.get(alias)

    if entity_cls is not None:

        obj = entity_cls.spawn_with_settings(settings_dict or {})
        configured = obj.test_configuration()

        if configured:
            obj.save_settings()
            LOGGER.info(f'{type_name} `{alias}` is configured')

            return obj

        else:
            LOGGER.error(f'{type_name} `{alias}` configuration failed. Check your settings')

    else:
        LOGGER.error(f'{type_name} `{alias}` is unknown')

def import_classes():
    """Dynamically imports RPC classes and tracker handlers from their directories."""

    LOGGER.debug('Importing RPC classes ...')
    import_from_path('rpc')

    LOGGER.debug('Importing Tracker classes ...')
    import_from_path('trackers')

    LOGGER.debug('Importing Notifier classes ...')
    import_from_path('notifiers')

    LOGGER.debug('Importing Bot classes ...')
    import_from_path('bots')

def import_from_path(path: str):
    """Dynamically imports modules from package.
    It is an .egg-friendly alternative to os.listdir() walking.

    :param path: path under torrt

    """
    for _, pname, ispkg in iter_modules([str(Path(__file__).parent / path)]):
        __import__(f'torrt.{path}.{pname}')

def parse_torrent(torrent: bytes) -> dict:
    """Returns a dictionary with basic information from torrent contents.

        keys:
            * hash;
            * name;
            * files;
            * torrent (torrent file contents just from input).

    :param torrent:

    """
    torrent_info = Torrent.from_string(torrent)

    files_from_torrent = [a_file[0] for a_file in torrent_info.files]
    info = {
        'hash': str(torrent_info.info_hash),
        'name': torrent_info.name,
        'files': files_from_torrent,
        'torrent': torrent
    }
    return info

def parse_torrent_file(filepath: str) -> dict:
    """Reads a torrent file from filesystem and returns information about it.

    :param filepath:

    """

    with open(filepath, 'rb') as f:
        contents = f.read()

    return parse_torrent(contents)

def make_soup(html: str) -> BeautifulSoup:
    """Returns BeautifulSoup object from a html.

    :param html:

    """
    return BeautifulSoup(html, 'lxml')

def get_url_from_string(string: str) -> str:
    """Returns URL from a string, e.g. torrent comment.

    :param string:

    """
    match = RE_LINK.search(string)

    try:
        match = match.group('url')

    except AttributeError:
        match = ''

    return match

def get_iso_from_timestamp(ts: int) -> str:
    """Get ISO formatted string from timestamp.

    :param ts: timestamp

    """
    return datetime.fromtimestamp(ts).isoformat(' ')

def update_dict(old_dict: dict, new_dict: dict) -> dict:
    """Updates [inplace] old dictionary with data from a new one with respect to existing values.

    :param old_dict:
    :param new_dict:

    """
    for key, val in new_dict.items():

        if isinstance(val, Mapping):
            old_dict[key] = update_dict(old_dict.get(key, {}), val)

        else:
            old_dict[key] = new_dict[key]

    return old_dict

def structure_torrent_data(target_dict: dict, hash_str: str, data: dict):
    """Updated target dict with torrent data structured suitably
    for config storage.

    :param target_dict: dictionary to update inplace
    :param hash_str: torrent identifying hash
    :param data: torrent data received from RPC (see parse_torrent())

    """
    data = dict(data)

    if 'hash' not in data:
        data['hash'] = hash_str

    if 'name' not in data:
        data['name'] = None

    if 'url' not in data:
        data['url'] = None

    target_dict[hash_str] = {
        'hash': data['hash'],
        'name': data['name'],
        'url': data['url']
    }

def get_torrent_from_url(url: Optional[str]) -> Optional[dict]:
    """Downloads torrent from a given URL and returns it as string.

    :param url:

    """
    LOGGER.debug(f'Downloading torrent file from `{url}` ...')

    tracker = TrackerObjectsRegistry.get_for_string(url)  # type: GenericTracker

    if tracker:
        result = tracker.get_torrent(url)

        if result is None:
            LOGGER.warning(f'Unable to get torrent from `{url}`')

        else:
            LOGGER.debug(f'Torrent was downloaded from `{url}`')
            return result

    else:
        LOGGER.warning(f'Tracker handler for `{url}` is not registered')

    return None

def iter_rpc() -> Generator[Tuple[str, 'BaseRPC'], None, None]:
    """Generator to iterate through available and enable RPC objects.
        tuple - rpc_alias, rpc_object

    """
    rpc_objects = RPCObjectsRegistry.get()

    if not rpc_objects:
        LOGGER.error('No RPC objects registered, unable to proceed')
        return

    for rpc_alias, rpc_object in rpc_objects.items():

        if not rpc_object.enabled:
            LOGGER.debug(f'RPC `{rpc_object.alias}` is disabled, skipped.')
            continue

        yield rpc_alias, rpc_object

def iter_bots() -> Generator[Tuple[str, 'BaseBot'], None, None]:
    """Generator to iterate through available bots objects.
        tuple - bot_alias, bot_object

    """
    bot_objects = BotObjectsRegistry.get()

    if not bot_objects:
        LOGGER.error('No Bot objects registered, unable to proceed')
        return

    for alias, object in bot_objects.items():
        yield alias, object

def iter_notifiers() -> Generator[Tuple[str, 'BaseNotifier'], None, None]:
    """Generator to iterate through available notifier objects.
        tuple - notifier_alias, notifier_object

    """
    notifier_objects = NotifierObjectsRegistry.get()

    if not notifier_objects:
        LOGGER.debug('No Notifier registered. Notification skipped')
        return

    for notifier_alias, notifier_object in notifier_objects.items():

        yield notifier_alias, notifier_object

class WithSettings:
    """Introduces settings support for class objects.

    NB: * Settings names are taken from inheriting classes __init__() methods.
        * __init__() method MUST use keyword arguments only.
        * Inheriting classes MUST save settings under object properties with the same name as in __init__().

    """

    alias: str = None

    config_entry_name: str = None
    settings: dict = {}

    @classmethod
    def spawn_with_settings(cls, settings: dict) -> 'WithSettings':
        """Spawns and returns object initialized with given settings.

        :param settings:

        """
        LOGGER.debug(f'Spawning `{cls.__name__}` object with the given settings ...')

        return cls(**settings)

    def save_settings(self):
        """Saves object settings into torrt configuration file."""

        settings = {}

        try:
            settings_names = getfullargspec(self.__init__)[0]

            del settings_names[0]  # do not need `self`

            for name in settings_names:
                settings[name] = getattr(self, name)

        except TypeError:
            pass  # Probably __init__ method is not user-defined.

        config.update({self.config_entry_name: {self.alias: settings}})

class TorrtConfig:
    """Gives methods to work with torrt configuration file."""

    USER_DATA_PATH = Path('~').expanduser() / '.torrt'
    USER_SETTINGS_FILE = USER_DATA_PATH / 'config.json'

    _basic_settings = {
        'time_last_check': 0,
        'walk_interval_hours': 1,
        'rpc': {},
        'trackers': {},
        'torrents': {},
        'notifiers': {},
        'bots': {}
    }

    @classmethod
    def drop_section(cls, realm: str, key: str):
        """Drops config section by its key (name) and updates config.

        :param realm:
        :param key:

        """
        try:
            cfg = cls.load()
            del cfg[realm][key]
            cls.save(cfg)

        except KeyError:
            pass

    @classmethod
    def bootstrap(cls):
        """Initializes configuration file if needed."""

        if not cls.USER_DATA_PATH.exists():
            os.makedirs(str(cls.USER_DATA_PATH))

        if not cls.USER_SETTINGS_FILE.exists():
            cls.save(cls._basic_settings)

        # My precious.
        os.chmod(str(cls.USER_SETTINGS_FILE), 0o600)

    @classmethod
    def update(cls, settings_dict: dict):
        """Updates configuration file with given settings.

        :param settings_dict:

        """
        cls.save(update_dict(cls.load(), settings_dict))

    @classmethod
    def load(cls) -> dict:
        """Returns current settings dictionary."""

        LOGGER.debug(f'Loading configuration file {cls.USER_SETTINGS_FILE} ...')

        cls.bootstrap()

        with open(str(cls.USER_SETTINGS_FILE)) as f:
            settings = json.load(f)

        # Pick up settings entries added in new version
        # and put them into old user config.
        for k, v in cls._basic_settings.items():
            if k not in settings:
                settings[k] = v

        return settings

    @classmethod
    def save(cls, settings_dict: dict):
        """Saves a given dict as torrt configuration.

        :param settings_dict:

        """
        LOGGER.debug(f'Saving configuration file {cls.USER_SETTINGS_FILE} ...')

        with open(str(cls.USER_SETTINGS_FILE), 'w') as f:
            json.dump(settings_dict, f, indent=4)

config = TorrtConfig

class ObjectsRegistry:

    __slots__ = ['_items']

    def __init__(self):
        self._items = {}

    def add(self, obj: Any):
        """Add an object to registry.

        NB: object MUST have `alias` attribute.

        :param obj:

        """
        name = getattr(obj, 'alias')

        LOGGER.debug(f'Registering `{name}` from {obj} ...')

        self._items[name] = obj

    def get(self, obj_alias: str = None) -> Union[dict, Any]:
        """Returns registered objects or a definite object by its alias,
        or registry items if no alias provided.

        :param obj_alias:

        """
        if obj_alias is None:
            return self._items

        return self._items.get(obj_alias)

    def get_for_string(self, string: str) -> Optional[Any]:
        """Returns registered object which can handle a given string.

        :param string:

        """
        for name, obj in self._items.items():
            can_handle_method = getattr(obj, 'can_handle', None)

            if can_handle_method and can_handle_method(string):
                return obj

            elif name in string:
                return self._items[name]

        return None

RPCClassesRegistry = ObjectsRegistry()
RPCObjectsRegistry = ObjectsRegistry()
TrackerClassesRegistry = ObjectsRegistry()
TrackerObjectsRegistry = ObjectsRegistry()
NotifierClassesRegistry = ObjectsRegistry()
NotifierObjectsRegistry = ObjectsRegistry()
BotClassesRegistry = ObjectsRegistry()
BotObjectsRegistry = ObjectsRegistry()
<commit_msg>Improve WithSettings<commit_after>import base64
import json
import logging
import os
import re
import threading
from collections import Mapping
from datetime import datetime
from inspect import getfullargspec
from pathlib import Path
from pkgutil import iter_modules
from typing import Any, Optional, Union, Generator, Tuple

from bs4 import BeautifulSoup
from requests import Response
from torrentool.api import Torrent

if False:  # pragma: nocover
    from .base_tracker import GenericTracker  # noqa
    from .base_rpc import BaseRPC  # noqa
    from .base_bot import BaseBot  # noqa
    from .base_notifier import BaseNotifier  # noqa

LOGGER = logging.getLogger(__name__)

_THREAD_LOCAL = threading.local()

# This regex is used to get hyperlink from torrent comment.
RE_LINK = re.compile(r'(?P<url>https?://[^\s]+)')

def encode_value(value: str, encoding: str = None) -> Union[str, bytes]:
    """Encodes a value.

    :param value:
    :param encoding: Encoding charset.

    """
    if encoding is None:
        return value

    return value.encode(encoding)

def base64encode(string_or_bytes: Union[str, bytes]) -> bytes:
    """Return base64 encoded input

    :param string_or_bytes:

    """
    if isinstance(string_or_bytes, str):
        string_or_bytes = string_or_bytes.encode()

    return base64.encodebytes(string_or_bytes).decode('ascii').encode()

class GlobalParam:
    """Represents global parameter value holder.
    Global params can used anywhere in torrt.

    """
    @staticmethod
    def set(name: str, value: Any):
        setattr(_THREAD_LOCAL, name, value)

    @staticmethod
    def get(name: str) -> Any:
        return getattr(_THREAD_LOCAL, name, None)

def dump_contents(filename: str, contents: Union[bytes, Response]):
    """Dumps contents into a file with a given name.

    :param filename:
    :param contents:

    """
    dump_into = GlobalParam.get('dump_into')

    if not dump_into:
        return

    if hasattr(contents, 'encode_contents'):
        # soup
        text = contents.encode_contents()

    else:
        # requests lib response
        text = contents.content

    with open(str(Path(dump_into) / filename), 'wb') as f:
        f.write(text)

def configure_entity(type_name: str, registry, alias: str, settings_dict: dict = None) -> Optional['WithSettings']:
    """Configures and spawns objects using given settings.

    Successful configuration is saved.

    :param type_name: Entity type name to be used in massages.
    :param registry: Registry object.
    :param alias: Entity alias.
    :param settings_dict: Settings dictionary to configure object with.

    """
    LOGGER.info(f'Configuring `{alias}` {type_name.lower()} ...')

    entity_cls = registry.get(alias)

    if entity_cls is not None:

        obj = entity_cls.spawn_with_settings(settings_dict or {})
        configured = obj.test_configuration()

        if configured:
            obj.save_settings()
            LOGGER.info(f'{type_name} `{alias}` is configured')

            return obj

        else:
            LOGGER.error(f'{type_name} `{alias}` configuration failed. Check your settings')

    else:
        LOGGER.error(f'{type_name} `{alias}` is unknown')

def import_classes():
    """Dynamically imports RPC classes and tracker handlers from their directories."""

    LOGGER.debug('Importing RPC classes ...')
    import_from_path('rpc')

    LOGGER.debug('Importing Tracker classes ...')
    import_from_path('trackers')

    LOGGER.debug('Importing Notifier classes ...')
    import_from_path('notifiers')

    LOGGER.debug('Importing Bot classes ...')
    import_from_path('bots')

def import_from_path(path: str):
    """Dynamically imports modules from package.
    It is an .egg-friendly alternative to os.listdir() walking.

    :param path: path under torrt

    """
    for _, pname, ispkg in iter_modules([str(Path(__file__).parent / path)]):
        __import__(f'torrt.{path}.{pname}')

def parse_torrent(torrent: bytes) -> dict:
    """Returns a dictionary with basic information from torrent contents.

        keys:
            * hash;
            * name;
            * files;
            * torrent (torrent file contents just from input).

    :param torrent:

    """
    torrent_info = Torrent.from_string(torrent)

    files_from_torrent = [a_file[0] for a_file in torrent_info.files]
    info = {
        'hash': str(torrent_info.info_hash),
        'name': torrent_info.name,
        'files': files_from_torrent,
        'torrent': torrent
    }
    return info

def parse_torrent_file(filepath: str) -> dict:
    """Reads a torrent file from filesystem and returns information about it.

    :param filepath:

    """

    with open(filepath, 'rb') as f:
        contents = f.read()

    return parse_torrent(contents)

def make_soup(html: str) -> BeautifulSoup:
    """Returns BeautifulSoup object from a html.

    :param html:

    """
    return BeautifulSoup(html, 'lxml')

def get_url_from_string(string: str) -> str:
    """Returns URL from a string, e.g. torrent comment.

    :param string:

    """
    match = RE_LINK.search(string)

    try:
        match = match.group('url')

    except AttributeError:
        match = ''

    return match

def get_iso_from_timestamp(ts: int) -> str:
    """Get ISO formatted string from timestamp.

    :param ts: timestamp

    """
    return datetime.fromtimestamp(ts).isoformat(' ')

def update_dict(old_dict: dict, new_dict: dict) -> dict:
    """Updates [inplace] old dictionary with data from a new one with respect to existing values.

    :param old_dict:
    :param new_dict:

    """
    for key, val in new_dict.items():

        if isinstance(val, Mapping):
            old_dict[key] = update_dict(old_dict.get(key, {}), val)

        else:
            old_dict[key] = new_dict[key]

    return old_dict

def structure_torrent_data(target_dict: dict, hash_str: str, data: dict):
    """Updated target dict with torrent data structured suitably
    for config storage.

    :param target_dict: dictionary to update inplace
    :param hash_str: torrent identifying hash
    :param data: torrent data received from RPC (see parse_torrent())

    """
    data = dict(data)

    if 'hash' not in data:
        data['hash'] = hash_str

    if 'name' not in data:
        data['name'] = None

    if 'url' not in data:
        data['url'] = None

    target_dict[hash_str] = {
        'hash': data['hash'],
        'name': data['name'],
        'url': data['url']
    }

def get_torrent_from_url(url: Optional[str]) -> Optional[dict]:
    """Downloads torrent from a given URL and returns it as string.

    :param url:

    """
    LOGGER.debug(f'Downloading torrent file from `{url}` ...')

    tracker = TrackerObjectsRegistry.get_for_string(url)  # type: GenericTracker

    if tracker:
        result = tracker.get_torrent(url)

        if result is None:
            LOGGER.warning(f'Unable to get torrent from `{url}`')

        else:
            LOGGER.debug(f'Torrent was downloaded from `{url}`')
            return result

    else:
        LOGGER.warning(f'Tracker handler for `{url}` is not registered')

    return None

def iter_rpc() -> Generator[Tuple[str, 'BaseRPC'], None, None]:
    """Generator to iterate through available and enable RPC objects.
        tuple - rpc_alias, rpc_object

    """
    rpc_objects = RPCObjectsRegistry.get()

    if not rpc_objects:
        LOGGER.error('No RPC objects registered, unable to proceed')
        return

    for rpc_alias, rpc_object in rpc_objects.items():

        if not rpc_object.enabled:
            LOGGER.debug(f'RPC `{rpc_object.alias}` is disabled, skipped.')
            continue

        yield rpc_alias, rpc_object

def iter_bots() -> Generator[Tuple[str, 'BaseBot'], None, None]:
    """Generator to iterate through available bots objects.
        tuple - bot_alias, bot_object

    """
    bot_objects = BotObjectsRegistry.get()

    if not bot_objects:
        LOGGER.error('No Bot objects registered, unable to proceed')
        return

    for alias, object in bot_objects.items():
        yield alias, object

def iter_notifiers() -> Generator[Tuple[str, 'BaseNotifier'], None, None]:
    """Generator to iterate through available notifier objects.
        tuple - notifier_alias, notifier_object

    """
    notifier_objects = NotifierObjectsRegistry.get()

    if not notifier_objects:
        LOGGER.debug('No Notifier registered. Notification skipped')
        return

    for notifier_alias, notifier_object in notifier_objects.items():

        yield notifier_alias, notifier_object

class WithSettings:
    """Introduces settings support for class objects.

    NB: * Settings names are taken from inheriting classes __init__() methods.
        * __init__() method MUST use keyword arguments only.
        * Inheriting classes MUST save settings under object properties with the same name as in __init__().

    """
    alias: str = None

    config_entry_name: str = None
    settings: dict = {}

    def __init__(self, **kwargs):
        pass

    def __str__(self) -> str:
        return self.alias

    @classmethod
    def spawn_with_settings(cls, settings: dict) -> 'WithSettings':
        """Spawns and returns object initialized with given settings.

        :param settings:

        """
        LOGGER.debug(f'Spawning `{cls.__name__}` object with the given settings ...')

        return cls(**settings)

    def save_settings(self):
        """Saves object settings into torrt configuration file."""

        settings = {}

        try:
            settings_names = getfullargspec(self.__init__)[0]

            del settings_names[0]  # do not need `self`

            for name in settings_names:
                settings[name] = getattr(self, name)

        except TypeError:
            pass  # Probably __init__ method is not user-defined.

        config.update({self.config_entry_name: {self.alias: settings}})

class TorrtConfig:
    """Gives methods to work with torrt configuration file."""

    USER_DATA_PATH = Path('~').expanduser() / '.torrt'
    USER_SETTINGS_FILE = USER_DATA_PATH / 'config.json'

    _basic_settings = {
        'time_last_check': 0,
        'walk_interval_hours': 1,
        'rpc': {},
        'trackers': {},
        'torrents': {},
        'notifiers': {},
        'bots': {}
    }

    @classmethod
    def drop_section(cls, realm: str, key: str):
        """Drops config section by its key (name) and updates config.

        :param realm:
        :param key:

        """
        try:
            cfg = cls.load()
            del cfg[realm][key]
            cls.save(cfg)

        except KeyError:
            pass

    @classmethod
    def bootstrap(cls):
        """Initializes configuration file if needed."""

        if not cls.USER_DATA_PATH.exists():
            os.makedirs(str(cls.USER_DATA_PATH))

        if not cls.USER_SETTINGS_FILE.exists():
            cls.save(cls._basic_settings)

        # My precious.
        os.chmod(str(cls.USER_SETTINGS_FILE), 0o600)

    @classmethod
    def update(cls, settings_dict: dict):
        """Updates configuration file with given settings.

        :param settings_dict:

        """
        cls.save(update_dict(cls.load(), settings_dict))

    @classmethod
    def load(cls) -> dict:
        """Returns current settings dictionary."""

        LOGGER.debug(f'Loading configuration file {cls.USER_SETTINGS_FILE} ...')

        cls.bootstrap()

        with open(str(cls.USER_SETTINGS_FILE)) as f:
            settings = json.load(f)

        # Pick up settings entries added in new version
        # and put them into old user config.
        for k, v in cls._basic_settings.items():
            if k not in settings:
                settings[k] = v

        return settings

    @classmethod
    def save(cls, settings_dict: dict):
        """Saves a given dict as torrt configuration.

        :param settings_dict:

        """
        LOGGER.debug(f'Saving configuration file {cls.USER_SETTINGS_FILE} ...')

        with open(str(cls.USER_SETTINGS_FILE), 'w') as f:
            json.dump(settings_dict, f, indent=4)

config = TorrtConfig

class ObjectsRegistry:

    __slots__ = ['_items']

    def __init__(self):
        self._items = {}

    def add(self, obj: Any):
        """Add an object to registry.

        NB: object MUST have `alias` attribute.

        :param obj:

        """
        name = getattr(obj, 'alias')

        LOGGER.debug(f'Registering `{name}` from {obj} ...')

        self._items[name] = obj

    def get(self, obj_alias: str = None) -> Union[dict, Any]:
        """Returns registered objects or a definite object by its alias,
        or registry items if no alias provided.

        :param obj_alias:

        """
        if obj_alias is None:
            return self._items

        return self._items.get(obj_alias)

    def get_for_string(self, string: str) -> Optional[Any]:
        """Returns registered object which can handle a given string.

        :param string:

        """
        for name, obj in self._items.items():
            can_handle_method = getattr(obj, 'can_handle', None)

            if can_handle_method and can_handle_method(string):
                return obj

            elif name in string:
                return self._items[name]

        return None

RPCClassesRegistry = ObjectsRegistry()
RPCObjectsRegistry = ObjectsRegistry()
TrackerClassesRegistry = ObjectsRegistry()
TrackerObjectsRegistry = ObjectsRegistry()
NotifierClassesRegistry = ObjectsRegistry()
NotifierObjectsRegistry = ObjectsRegistry()
BotClassesRegistry = ObjectsRegistry()
BotObjectsRegistry = ObjectsRegistry()
<|endoftext|>


<commit_before>
#-----------------------------------------------------------------------------
# Auto generated from the DSLTrans transformation and the properties to prove
#-----------------------------------------------------------------------------

import time

from path_condition_generator import PathConditionGenerator
from pyramify.PyRamify import PyRamify

from util.ecore_utils import EcoreUtils
from core.himesis_plus import buildPreListFromClassNames

from PropertyVerification.v2.atomic_contract import AtomicContract
from PropertyVerification.v2.ContractProver import ContractProver

from PropertyVerification.v2.if_then_contract import IfThenContract
from PropertyVerification.v2.prop_logic import NotContract, AndContract

from core.himesis_utils import graph_to_dot, load_directory
from util.test_script_utils import select_rules, get_sub_and_super_classes,\
    load_transformation, changePropertyProverMetamodel, set_supertypes, load_contracts
from util.slicer import Slicer
from util.parser import load_parser

class Prover():

    def do_proof(self,args):

        pyramify = PyRamify(verbosity=args.verbosity, draw_svg=args.draw_svg)

        transformation_dir = "mbeddr2C_MM/real_transformation/no_contains"

        r0 = 'Hlayer0rule0'
        r1 = 'Hlayer0rule1'
        r2 = 'Hlayer0rule2'
        r3 = 'Hlayer0rule3'
        r4 = 'Hlayer0rule4'
        r5 = 'Hlayer0rule6'
        r6 = 'Hlayer0rule7'
        r7 = 'Hlayer0rule8'
        r8 = 'Hlayer0rule9'
        r9 = 'Hlayer0rule10'
        r10 = 'Hlayer0rule11'
        r11 = 'Hlayer1rule0'
        r12 = 'Hlayer1rule1'
        r13 = 'Hlayer1rule2'
        r14 = 'Hlayer1rule3'
        r15 = 'Hlayer1rule4'
        r16 = 'Hlayer1rule5'
        r17 = 'Hlayer1rule6'
        r18 = 'Hlayer1rule7'
        r19 = 'Hlayer1rule8'
        r20 = 'Hlayer1rule9'
        r21 = 'Hlayer1rule10'
        r22 = 'Hlayer1rule11'
        r23 = 'Hlayer1rule12'
        r24 = 'Hlayer1rule13'
        r25 = 'Hlayer1rule14'
        r26 = 'Hlayer1rule15'
        r27 = 'Hlayer2rule1'
        r28 = 'Hlayer2rule2'
        r29 = 'Hlayer2rule3'
        r30 = 'Hlayer3rule0'
        r31 = 'Hlayer3rule1'
        r32 = 'Hlayer3rule2'
        r33 = 'Hlayer3rule3'
        r34 = 'Hlayer3rule4'
        r35 = 'Hlayer3rule5'
        r36 = 'Hlayer4rule0'
        r37 = 'Hlayer4rule1'
        r38 = 'Hlayer4rule2'
        r39 = 'Hlayer4rule3'
        r40 = 'Hlayer5rule0'
        r41 = 'Hlayer5rule1'
        r42 = 'Hlayer5rule2'
        r43 = 'Hlayer5rule3'
        r44 = 'Hlayer5rule4'
        r45 = 'Hlayer5rule5'
        r46 = 'Hlayer6rule0'

        full_transformation = [[r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, ],
                               [r11, r12, r13, r14, r15, r16, r17, r18, r19, r20, r21, r22, r23, r24, r25, r26, ],
                               [r27, r28, r29, ], [r30, r31, r32, r33, r34, r35, ], [r36, r37, r38, r39, ],
                               [r40, r41, r42, r43, r44, r45, ], [r46, ],
        ]

        self.rules, self.transformation = load_transformation(transformation_dir, full_transformation)

        inputMM = "./mbeddr2C_MM/ecore_metamodels/Module.ecore"
        outputMM = "./mbeddr2C_MM/ecore_metamodels/C.ecore"
        subclasses_dict, self.superclasses_dict = get_sub_and_super_classes(inputMM, outputMM)

        [self.rules, self.ruleTraceCheckers, backwardPatterns2Rules, backwardPatternsComplete, self.matchRulePatterns, self.ruleCombinators, self.overlapping_rules, self.subsumption, self.loopingRuleSubsumption] = \
            pyramify.ramify_directory(transformation_dir, self.transformation)

        # for rule in self.rules:
        #     graph_to_dot(rule, self.rules[rule])

        pre_metamodel = ["MT_pre__S_MM", "MoTifRule"]
        post_metamodel = ["MT_post__T_MM", "MoTifRule"]

        changePropertyProverMetamodel(pre_metamodel, post_metamodel, subclasses_dict, self.superclasses_dict, ".")
        set_supertypes(self.superclasses_dict, self.rules, self.transformation, self.ruleTraceCheckers, self.matchRulePatterns, self.ruleCombinators)

        # go through all the matchers, combinators and tracers to add polymorphism on all classes in an inheritance hierarchy

        # load the contracts, and add polymorphism
        contracts = load_directory("mbeddr2C_MM/Contracts/")

        atomic_contracts = [
            'AssignmentInstance',
            'GlobalVarGetsCorrectFunctionAddressAtInit',
            'Simpler',
            'VerySimple'
        ]

        if_then_contracts = []
        prop_if_then_contracts = []

        self.atomic_contracts, self.if_then_contracts = load_contracts(contracts, self.superclasses_dict,
                                                                       atomic_contracts, if_then_contracts,
                                                                       prop_if_then_contracts,
                                                                       args.draw_svg)

        slicer = Slicer(self.rules, self.transformation, self.superclasses_dict, self.overlapping_rules)

        if args.slice > -1:
            contract, self.atomic_contracts, self.if_then_contracts = slicer.get_contract(args.slice,
                                                                                self.atomic_contracts,
                                                                                self.if_then_contracts)
            self.rules, self.transformation = slicer.slice_transformation(contract)

        for layer in self.transformation:
            print("Layer:")
            for rule in layer:
                print(rule.name)

        # generate path conditions
        #raise Exception()

        inputMM = "./mbeddr2C_MM/ecore_metamodels/Module.ecore"
        pc_set = PathConditionGenerator(self.transformation, outputMM, self.ruleCombinators, self.ruleTraceCheckers, self.matchRulePatterns, self.overlapping_rules, self.subsumption, self.loopingRuleSubsumption, args)

        #raise Exception()

        ts0 = time.time()
        pc_set.build_path_conditions()
        ts1 = time.time()

        print("\n\nTime to build the set of path conditions: " + str(ts1 - ts0))
        print("Number of path conditions: " + str(pc_set.num_path_conditions))

        # print path conditions to screen

        #pc_set.print_path_conditions_file()

        print("\nContract proving:")

        pc_set.verbosity = 0

        contract_prover = ContractProver()

        contract_prover.prove_contracts(pc_set, self.atomic_contracts, [])  # self.if_then_contracts)
        print("\n\nTime to build the set of path conditions: " + str(ts1-ts0))
        print("Number of path conditions: " + str(pc_set.num_path_conditions))

if __name__ == "__main__":
    parser = load_parser()
    args = parser.parse_args()

    prover = Prover()
    prover.do_proof(args)

<commit_msg>Fix contract for mbeddr.<commit_after>
#-----------------------------------------------------------------------------
# Auto generated from the DSLTrans transformation and the properties to prove
#-----------------------------------------------------------------------------

import time

from path_condition_generator import PathConditionGenerator
from pyramify.PyRamify import PyRamify

from util.ecore_utils import EcoreUtils
from core.himesis_plus import buildPreListFromClassNames

from PropertyVerification.v2.atomic_contract import AtomicContract
from PropertyVerification.v2.ContractProver import ContractProver

from PropertyVerification.v2.if_then_contract import IfThenContract
from PropertyVerification.v2.prop_logic import NotContract, AndContract

from core.himesis_utils import graph_to_dot, load_directory
from util.test_script_utils import select_rules, get_sub_and_super_classes,\
    load_transformation, changePropertyProverMetamodel, set_supertypes, load_contracts
from util.slicer import Slicer
from util.parser import load_parser

class Prover():

    def do_proof(self,args):

        pyramify = PyRamify(verbosity=args.verbosity, draw_svg=args.draw_svg)

        transformation_dir = "mbeddr2C_MM/real_transformation/no_contains"

        r0 = 'Hlayer0rule0'
        r1 = 'Hlayer0rule1'
        r2 = 'Hlayer0rule2'
        r3 = 'Hlayer0rule3'
        r4 = 'Hlayer0rule4'
        r5 = 'Hlayer0rule6'
        r6 = 'Hlayer0rule7'
        r7 = 'Hlayer0rule8'
        r8 = 'Hlayer0rule9'
        r9 = 'Hlayer0rule10'
        r10 = 'Hlayer0rule11'
        r11 = 'Hlayer1rule0'
        r12 = 'Hlayer1rule1'
        r13 = 'Hlayer1rule2'
        r14 = 'Hlayer1rule3'
        r15 = 'Hlayer1rule4'
        r16 = 'Hlayer1rule5'
        r17 = 'Hlayer1rule6'
        r18 = 'Hlayer1rule7'
        r19 = 'Hlayer1rule8'
        r20 = 'Hlayer1rule9'
        r21 = 'Hlayer1rule10'
        r22 = 'Hlayer1rule11'
        r23 = 'Hlayer1rule12'
        r24 = 'Hlayer1rule13'
        r25 = 'Hlayer1rule14'
        r26 = 'Hlayer1rule15'
        r27 = 'Hlayer2rule1'
        r28 = 'Hlayer2rule2'
        r29 = 'Hlayer2rule3'
        r30 = 'Hlayer3rule0'
        r31 = 'Hlayer3rule1'
        r32 = 'Hlayer3rule2'
        r33 = 'Hlayer3rule3'
        r34 = 'Hlayer3rule4'
        r35 = 'Hlayer3rule5'
        r36 = 'Hlayer4rule0'
        r37 = 'Hlayer4rule1'
        r38 = 'Hlayer4rule2'
        r39 = 'Hlayer4rule3'
        r40 = 'Hlayer5rule0'
        r41 = 'Hlayer5rule1'
        r42 = 'Hlayer5rule2'
        r43 = 'Hlayer5rule3'
        r44 = 'Hlayer5rule4'
        r45 = 'Hlayer5rule5'
        r46 = 'Hlayer6rule0'

        full_transformation = [[r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, ],
                               [r11, r12, r13, r14, r15, r16, r17, r18, r19, r20, r21, r22, r23, r24, r25, r26, ],
                               [r27, r28, r29, ], [r30, r31, r32, r33, r34, r35, ], [r36, r37, r38, r39, ],
                               [r40, r41, r42, r43, r44, r45, ], [r46, ],
        ]

        self.rules, self.transformation = load_transformation(transformation_dir, full_transformation)

        inputMM = "./mbeddr2C_MM/ecore_metamodels/Module.ecore"
        outputMM = "./mbeddr2C_MM/ecore_metamodels/C.ecore"
        subclasses_dict, self.superclasses_dict = get_sub_and_super_classes(inputMM, outputMM)

        [self.rules, self.ruleTraceCheckers, backwardPatterns2Rules, backwardPatternsComplete, self.matchRulePatterns, self.ruleCombinators, self.overlapping_rules, self.subsumption, self.loopingRuleSubsumption] = \
            pyramify.ramify_directory(transformation_dir, self.transformation)

        # for rule in self.rules:
        #     graph_to_dot(rule, self.rules[rule])

        pre_metamodel = ["MT_pre__S_MM", "MoTifRule"]
        post_metamodel = ["MT_post__T_MM", "MoTifRule"]

        changePropertyProverMetamodel(pre_metamodel, post_metamodel, subclasses_dict, self.superclasses_dict, ".")
        set_supertypes(self.superclasses_dict, self.rules, self.transformation, self.ruleTraceCheckers, self.matchRulePatterns, self.ruleCombinators)

        # go through all the matchers, combinators and tracers to add polymorphism on all classes in an inheritance hierarchy

        # load the contracts, and add polymorphism
        contracts = load_directory("mbeddr2C_MM/Contracts/")

        atomic_contracts = [
            'AssignmentInstance',
            'GlobalVarGetsCorrectFunctionAddressAtInit',
            'Simple',
            'VerySimple'
        ]

        if_then_contracts = []
        prop_if_then_contracts = []

        self.atomic_contracts, self.if_then_contracts = load_contracts(contracts, self.superclasses_dict,
                                                                       atomic_contracts, if_then_contracts,
                                                                       prop_if_then_contracts,
                                                                       args.draw_svg)

        slicer = Slicer(self.rules, self.transformation, self.superclasses_dict, self.overlapping_rules)

        if args.slice > -1:
            contract, self.atomic_contracts, self.if_then_contracts = slicer.get_contract(args.slice,
                                                                                self.atomic_contracts,
                                                                                self.if_then_contracts)
            self.rules, self.transformation = slicer.slice_transformation(contract)

        for layer in self.transformation:
            print("Layer:")
            for rule in layer:
                print(rule.name)

        # generate path conditions
        #raise Exception()

        inputMM = "./mbeddr2C_MM/ecore_metamodels/Module.ecore"
        pc_set = PathConditionGenerator(self.transformation, outputMM, self.ruleCombinators, self.ruleTraceCheckers, self.matchRulePatterns, self.overlapping_rules, self.subsumption, self.loopingRuleSubsumption, args)

        #raise Exception()

        ts0 = time.time()
        pc_set.build_path_conditions()
        ts1 = time.time()

        print("\n\nTime to build the set of path conditions: " + str(ts1 - ts0))
        print("Number of path conditions: " + str(pc_set.num_path_conditions))

        # print path conditions to screen

        #pc_set.print_path_conditions_file()

        print("\nContract proving:")

        pc_set.verbosity = 0

        contract_prover = ContractProver()

        contract_prover.prove_contracts(pc_set, self.atomic_contracts, [])  # self.if_then_contracts)
        print("\n\nTime to build the set of path conditions: " + str(ts1-ts0))
        print("Number of path conditions: " + str(pc_set.num_path_conditions))

if __name__ == "__main__":
    parser = load_parser()
    args = parser.parse_args()

    prover = Prover()
    prover.do_proof(args)

<|endoftext|>


<commit_before>#!/usr/bin/env python
"""
Integration (not unit) tests for pylast.py
"""
import datetime
import time
import unittest
import yaml # pip install pyyaml

import pylast

def load_secrets():
    with open("test_pylast.yaml", "r") as f: # see test_pylast_example.yaml
        doc = yaml.load(f)
    return doc

class TestSequenceFunctions(unittest.TestCase):

    secrets = None

    def unix_timestamp(self):
        return int(time.mktime(datetime.datetime.now().timetuple()))

    def setUp(self):
        if self.__class__.secrets is None:
            self.__class__.secrets = load_secrets()

        self.username = self.__class__.secrets["username"]
        password_hash = self.__class__.secrets["password_hash"]

        API_KEY       = self.__class__.secrets["api_key"]
        API_SECRET    = self.__class__.secrets["api_secret"]

        self.network = pylast.LastFMNetwork(api_key = API_KEY, api_secret =
    API_SECRET, username = self.username, password_hash = password_hash)

    def test_scrobble(self):
        # Arrange
        artist = "Test Artist"
        title = "Test Title"
        timestamp = self.unix_timestamp()
        lastfm_user = self.network.get_user(self.username)

        # Act
        self.network.scrobble(artist = artist, title = title, timestamp = timestamp)

        # Assert
        last_scrobble = lastfm_user.get_recent_tracks(limit = 1)[0]
        self.assertEqual(str(last_scrobble.track.artist), str(artist))
        self.assertEqual(str(last_scrobble.track.title),  str(title))
        self.assertEqual(str(last_scrobble.timestamp),    str(timestamp))

    def test_unscrobble(self):
        # Arrange
        artist = "Test Artist 2"
        title = "Test Title 2"
        timestamp = self.unix_timestamp()
        library = pylast.Library(user = self.username, network = self.network)
        self.network.scrobble(artist = artist, title = title, timestamp = timestamp)
        lastfm_user = self.network.get_user(self.username)

        # Act
        library.remove_scrobble(artist = artist, title = title, timestamp = timestamp)

        # Assert
        last_scrobble = lastfm_user.get_recent_tracks(limit = 1)[0]
        self.assertNotEqual(str(last_scrobble.timestamp), str(timestamp))

    def test_add_album(self):
        # Arrange
        library = pylast.Library(user = self.username, network = self.network)
        album = self.network.get_album("Test Artist", "Test Album")

        # Act
        library.add_album(album)

        # Assert
        # Nothing here, just that no exception occurred

    def test_get_venue(self):
        # Arrange
        venue_name = "Last.fm Office"
        country_name = "United Kingom"

        # Act
        venue_search = self.network.search_for_venue(venue_name, country_name)
        venue = venue_search.get_next_page()[0]

        # Assert
        self.assertEqual(str(venue.id), "8778225")

    def test_get_user_registration(self):
        # Arrange
        username = "RJ"
        user = self.network.get_user(username)

        # Act
        registered = user.get_registered()

        # Assert
        # Just check date because of timezones
        self.assertIn(u"2002-11-20 ", registered)

    def test_get_user_unixtime_registration(self):
        # Arrange
        username = "RJ"
        user = self.network.get_user(username)

        # Act
        unixtime_registered = user.get_unixtime_registered()

        # Assert
        # Just check date because of timezones
        self.assertEquals(unixtime_registered, u"1037793040")

    def test_get_genderless_user(self):
        # Arrange
        lastfm_user = self.network.get_user("test_user") # currently no gender set

        # Act
        gender = lastfm_user.get_gender()

        # Assert
        self.assertIsNone(gender)

    def test_get_countryless_user(self):
        # Arrange
        lastfm_user = self.network.get_user("test_user") # currently no country set

        # Act
        country = lastfm_user.get_country()

        # Assert
        self.assertIsNone(country)

    def test_love(self):
        # Arrange
        artist = "Test Artist"
        title = "Test Title"
        track = pylast.Track(artist, title, self.network)
        lastfm_user = self.network.get_user(self.username)

        # Act
        track.love()

        # Assert
        loved = lastfm_user.get_loved_tracks(limit = 1)
        self.assertEqual(str(loved[0].track.artist), "Test Artist")
        self.assertEqual(str(loved[0].track.title), "Test Title")

    def test_unlove(self):
        # Arrange
        artist = "Test Artist"
        title = "Test Title"
        track = pylast.Track(artist, title, self.network)
        lastfm_user = self.network.get_user(self.username)
        track.love()

        # Act
        track.unlove()

        # Assert
        loved = lastfm_user.get_loved_tracks(limit = 1)
        if len(loved): # OK to be empty but if not:
            self.assertNotEqual(str(loved.track.artist), "Test Artist")
            self.assertNotEqual(str(loved.track.title), "Test Title")

if __name__ == '__main__':
    unittest.main()

# End of file
<commit_msg>Add test case for bug already fixed by @inversion in https://github.com/hugovk/pylast/commit/c8216a139e216f016773de3eb92a1044ea919c1e. Closes #64.<commit_after>#!/usr/bin/env python
"""
Integration (not unit) tests for pylast.py
"""
import datetime
import time
import unittest
import yaml # pip install pyyaml

import pylast

def load_secrets():
    with open("test_pylast.yaml", "r") as f: # see test_pylast_example.yaml
        doc = yaml.load(f)
    return doc

class TestPyLast(unittest.TestCase):

    secrets = None

    def unix_timestamp(self):
        return int(time.mktime(datetime.datetime.now().timetuple()))

    def setUp(self):
        if self.__class__.secrets is None:
            self.__class__.secrets = load_secrets()

        self.username = self.__class__.secrets["username"]
        password_hash = self.__class__.secrets["password_hash"]

        API_KEY       = self.__class__.secrets["api_key"]
        API_SECRET    = self.__class__.secrets["api_secret"]

        self.network = pylast.LastFMNetwork(api_key = API_KEY, api_secret =
    API_SECRET, username = self.username, password_hash = password_hash)

    def test_scrobble(self):
        # Arrange
        artist = "Test Artist"
        title = "Test Title"
        timestamp = self.unix_timestamp()
        lastfm_user = self.network.get_user(self.username)

        # Act
        self.network.scrobble(artist = artist, title = title, timestamp = timestamp)

        # Assert
        last_scrobble = lastfm_user.get_recent_tracks(limit = 1)[0]
        self.assertEqual(str(last_scrobble.track.artist), str(artist))
        self.assertEqual(str(last_scrobble.track.title),  str(title))
        self.assertEqual(str(last_scrobble.timestamp),    str(timestamp))

    def test_unscrobble(self):
        # Arrange
        artist = "Test Artist 2"
        title = "Test Title 2"
        timestamp = self.unix_timestamp()
        library = pylast.Library(user = self.username, network = self.network)
        self.network.scrobble(artist = artist, title = title, timestamp = timestamp)
        lastfm_user = self.network.get_user(self.username)

        # Act
        library.remove_scrobble(artist = artist, title = title, timestamp = timestamp)

        # Assert
        last_scrobble = lastfm_user.get_recent_tracks(limit = 1)[0]
        self.assertNotEqual(str(last_scrobble.timestamp), str(timestamp))

    def test_add_album(self):
        # Arrange
        library = pylast.Library(user = self.username, network = self.network)
        album = self.network.get_album("Test Artist", "Test Album")

        # Act
        library.add_album(album)

        # Assert
        # Nothing here, just that no exception occurred

    def test_get_venue(self):
        # Arrange
        venue_name = "Last.fm Office"
        country_name = "United Kingom"

        # Act
        venue_search = self.network.search_for_venue(venue_name, country_name)
        venue = venue_search.get_next_page()[0]

        # Assert
        self.assertEqual(str(venue.id), "8778225")

    def test_get_user_registration(self):
        # Arrange
        username = "RJ"
        user = self.network.get_user(username)

        # Act
        registered = user.get_registered()

        # Assert
        # Just check date because of timezones
        self.assertIn(u"2002-11-20 ", registered)

    def test_get_user_unixtime_registration(self):
        # Arrange
        username = "RJ"
        user = self.network.get_user(username)

        # Act
        unixtime_registered = user.get_unixtime_registered()

        # Assert
        # Just check date because of timezones
        self.assertEquals(unixtime_registered, u"1037793040")

    def test_get_genderless_user(self):
        # Arrange
        lastfm_user = self.network.get_user("test_user") # currently no gender set

        # Act
        gender = lastfm_user.get_gender()

        # Assert
        self.assertIsNone(gender)

    def test_get_countryless_user(self):
        # Arrange
        lastfm_user = self.network.get_user("test_user") # currently no country set

        # Act
        country = lastfm_user.get_country()

        # Assert
        self.assertIsNone(country)

    def test_love(self):
        # Arrange
        artist = "Test Artist"
        title = "Test Title"
        track = pylast.Track(artist, title, self.network)
        lastfm_user = self.network.get_user(self.username)

        # Act
        track.love()

        # Assert
        loved = lastfm_user.get_loved_tracks(limit = 1)
        self.assertEqual(str(loved[0].track.artist), "Test Artist")
        self.assertEqual(str(loved[0].track.title), "Test Title")

    def test_unlove(self):
        # Arrange
        artist = "Test Artist"
        title = "Test Title"
        track = pylast.Track(artist, title, self.network)
        lastfm_user = self.network.get_user(self.username)
        track.love()

        # Act
        track.unlove()

        # Assert
        loved = lastfm_user.get_loved_tracks(limit = 1)
        if len(loved): # OK to be empty but if not:
            self.assertNotEqual(str(loved.track.artist), "Test Artist")
            self.assertNotEqual(str(loved.track.title), "Test Title")

    def test_get_100_albums(self):
        # Arrange
        library = pylast.Library(user = self.username, network = self.network)

        # Act
        albums = library.get_albums(limit = 100)

        # Assert
        self.assertGreaterEqual(len(albums), 0)

    def test_get_limitless_albums(self):
        # Arrange
        library = pylast.Library(user = self.username, network = self.network)

        # Act
        albums = library.get_albums(limit = None)

        # Assert
        self.assertGreaterEqual(len(albums), 0)

if __name__ == '__main__':

#     suite = unittest.TestSuite()
#     suite.addTest(TestPyLast('test_get_limitless_albums'))
#     unittest.TextTestRunner().run(suite)

    unittest.main()

# End of file
<|endoftext|>


<commit_before>import sublime
import sublime_plugin

from itertools import chain
import io
import os
import unittest
import tempfile

# A tuple: (low level file_descriptor, path) as returned by `tempfile.mkstemp()`.
TEST_DATA_PATH = None

def make_temp_file():
    """Creates an new temporary file.
    """
    global TEST_DATA_PATH
    TEST_DATA_PATH = tempfile.mkstemp()

class TestsState(object):
    running = False
    view = None
    suite = None

    @staticmethod
    def reset():
        TestsState.view = None
        TestsState.suite = None

    @staticmethod
    def reset_view_state():
        TestsState.view.settings().set('vintage', {})
        TestsState.view.sel().clear()
        TestsState.view.sel().add(sublime.Region(0, 0))

TESTS_SETTINGS = 'Vintageous.tests.vi.test_settings'
TESTS_REGISTERS = 'Vintageous.tests.vi.test_registers'
TESTS_MARKS = 'Vintageous.tests.vi.test_marks'
TESTS_STATE = 'Vintageous.tests.test_state'
TESTS_CONSTANTS = 'Vintageous.tests.vi.test_constants'
TESTS_CMD_DATA = 'Vintageous.tests.vi.test_cmd_data'
TESTS_KEYMAP = 'Vintageous.tests.test_keymap'
TESTS_RUN = 'Vintageous.tests.test_run'

TESTS_CMDS_SET_ACTION = 'Vintageous.tests.commands.test_set_action'
TESTS_CMDS_SET_MOTION = 'Vintageous.tests.commands.test_set_motion'
TESTS_CMDS_MOTION_VI_L = 'Vintageous.tests.commands.test__vi_l'
TESTS_CMDS_MOTION_VI_H = 'Vintageous.tests.commands.test__vi_h'
TESTS_CMDS_MOTION_VI_BIG_G = 'Vintageous.tests.commands.test__vi_big_g'
TESTS_CMDS_MOTION_VI_DOLLAR = 'Vintageous.tests.commands.test__vi_dollar'
TESTS_CMDS_MOTION_VI_J = 'Vintageous.tests.commands.test__vi_j'
TESTS_CMDS_MOTION_VI_K = 'Vintageous.tests.commands.test__vi_k'
TESTS_CMDS_MOTION_VI_BIG_F = 'Vintageous.tests.commands.test__vi_big_f'
TESTS_CMDS_ACTION_CTRL_X = 'Vintageous.tests.commands.test__ctrl_x_and__ctrl_a'
TESTS_CMDS_ACTION_VI_CC = 'Vintageous.tests.commands.test__vi_cc'
TESTS_CMDS_ACTION_VI_BIG_S = 'Vintageous.tests.commands.test__vi_big_s'

TESTS_CMDS_ALL_SUPPORT = [TESTS_CMDS_SET_ACTION, TESTS_CMDS_SET_MOTION]

TESTS_CMDS_ALL_ACTIONS = [TESTS_CMDS_ACTION_CTRL_X,
                          TESTS_CMDS_ACTION_VI_CC,
                          ]

TESTS_CMDS_ALL_MOTIONS = [TESTS_CMDS_MOTION_VI_L,
                          TESTS_CMDS_MOTION_VI_H,
                          TESTS_CMDS_MOTION_VI_BIG_G,
                          TESTS_CMDS_MOTION_VI_DOLLAR,
                          TESTS_CMDS_MOTION_VI_J,
                          TESTS_CMDS_MOTION_VI_K,
                          TESTS_CMDS_MOTION_VI_BIG_F,
                          TESTS_CMDS_ACTION_VI_BIG_S,
                          ]

TESTS_CMDS_ALL = TESTS_CMDS_ALL_MOTIONS + TESTS_CMDS_ALL_ACTIONS + TESTS_CMDS_ALL_SUPPORT

test_suites = {
        '_storage_': ['_pt_run_tests', [TESTS_MARKS, TESTS_SETTINGS, TESTS_REGISTERS, TESTS_CONSTANTS]],

        'settings': ['_pt_run_tests', [TESTS_SETTINGS]],
        'registers': ['_pt_run_tests', [TESTS_REGISTERS]],
        'marks': ['_pt_run_tests', [TESTS_MARKS]],

        'state': ['_pt_run_tests', [TESTS_STATE]],
        'run': ['_pt_run_tests', [TESTS_RUN]],

        'constants': ['_pt_run_tests', [TESTS_CONSTANTS]],

        'cmd_data': ['_pt_run_tests', [TESTS_CMD_DATA]],

        'keymap': ['_pt_run_tests', [TESTS_KEYMAP]],

        'commands': ['_pt_run_tests', TESTS_CMDS_ALL],
}

# Combine all tests under one key for convenience. Ignore keys starting with an underscore. Use
# these for subsets of all the remaining tests that you don't want repeated under '_all_'.
# Convert to list so the 'chain' doesn't get exhausted after the first use.
all_tests = list(chain(*[data[1] for (key, data) in test_suites.items() if not key.startswith('_')]))
test_suites['_all_'] = ['_pt_run_tests', all_tests]

class _ptPrintResults(sublime_plugin.TextCommand):
    def run(self, edit, content):
        view = sublime.active_window().new_file()
        view.insert(edit, 0, content)
        view.set_scratch(True)

class ShowVintageousTestSuites(sublime_plugin.WindowCommand):
    """Displays a quick panel listing all available test stuites.
    """
    def run(self):
        TestsState.running = True
        self.window.show_quick_panel(sorted(test_suites.keys()), self.run_suite)

    def run_suite(self, idx):
        suite_name = sorted(test_suites.keys())[idx]
        TestsState.suite = suite_name
        command_to_run, _ = test_suites[suite_name]

        self.window.run_command(command_to_run, dict(suite_name=suite_name))

class _ptRunTests(sublime_plugin.WindowCommand):
    def run(self, suite_name):
        make_temp_file()
        # We open the file here, but Sublime Text loads it asynchronously, so we continue in an
        # event handler, once it's been fully loaded.
        self.window.open_file(TEST_DATA_PATH[1])

class _ptTestDataDispatcher(sublime_plugin.EventListener):
    def on_load(self, view):
        try:
            if (view.file_name() and view.file_name() == TEST_DATA_PATH[1] and
                TestsState.running):

                    TestsState.running = False
                    TestsState.view = view

                    _, suite_names = test_suites[TestsState.suite]
                    suite = unittest.TestLoader().loadTestsFromNames(suite_names)

                    bucket = io.StringIO()
                    unittest.TextTestRunner(stream=bucket, verbosity=1).run(suite)

                    view.run_command('_pt_print_results', {'content': bucket.getvalue()})
                    w = sublime.active_window()
                    # Close data view.
                    w.run_command('prev_view')
                    TestsState.view.set_scratch(True)
                    w.run_command('close')
                    # Ugly hack to return focus to the results view.
                    w.run_command('show_panel', {'panel': 'console', 'toggle': True})
                    w.run_command('show_panel', {'panel': 'console', 'toggle': True})
        except Exception as e:
            print(e)
        finally:
            try:
                os.close(TEST_DATA_PATH[0])
            except Exception as e:
                print("Could not close temp file...")
                print(e)

class WriteToBuffer(sublime_plugin.TextCommand):
    """Replaces the buffer's content with the specified `text`.

       `text`: Text to be written to the buffer.
       `file_name`: If this file name does not match the receiving view's, abort.
    """
    def run(self, edit, file_name='', text=''):
        if not file_name:
            return

        if self.view.file_name().lower() == file_name.lower():
            self.view.replace(edit, sublime.Region(0, self.view.size()), text)
<commit_msg>implement w<commit_after>import sublime
import sublime_plugin

from itertools import chain
import io
import os
import unittest
import tempfile

# A tuple: (low level file_descriptor, path) as returned by `tempfile.mkstemp()`.
TEST_DATA_PATH = None

def make_temp_file():
    """Creates an new temporary file.
    """
    global TEST_DATA_PATH
    TEST_DATA_PATH = tempfile.mkstemp()

class TestsState(object):
    running = False
    view = None
    suite = None

    @staticmethod
    def reset():
        TestsState.view = None
        TestsState.suite = None

    @staticmethod
    def reset_view_state():
        TestsState.view.settings().set('vintage', {})
        TestsState.view.sel().clear()
        TestsState.view.sel().add(sublime.Region(0, 0))

TESTS_SETTINGS = 'Vintageous.tests.vi.test_settings'
TESTS_REGISTERS = 'Vintageous.tests.vi.test_registers'
TESTS_MARKS = 'Vintageous.tests.vi.test_marks'
TESTS_STATE = 'Vintageous.tests.test_state'
TESTS_CONSTANTS = 'Vintageous.tests.vi.test_constants'
TESTS_CMD_DATA = 'Vintageous.tests.vi.test_cmd_data'
TESTS_KEYMAP = 'Vintageous.tests.test_keymap'
TESTS_RUN = 'Vintageous.tests.test_run'

TESTS_CMDS_SET_ACTION = 'Vintageous.tests.commands.test_set_action'
TESTS_CMDS_SET_MOTION = 'Vintageous.tests.commands.test_set_motion'
TESTS_CMDS_MOTION_VI_L = 'Vintageous.tests.commands.test__vi_l'
TESTS_CMDS_MOTION_VI_H = 'Vintageous.tests.commands.test__vi_h'
TESTS_CMDS_MOTION_VI_BIG_G = 'Vintageous.tests.commands.test__vi_big_g'
TESTS_CMDS_MOTION_VI_DOLLAR = 'Vintageous.tests.commands.test__vi_dollar'
TESTS_CMDS_MOTION_VI_J = 'Vintageous.tests.commands.test__vi_j'
TESTS_CMDS_MOTION_VI_K = 'Vintageous.tests.commands.test__vi_k'
TESTS_CMDS_MOTION_VI_BIG_F = 'Vintageous.tests.commands.test__vi_big_f'
TESTS_CMDS_ACTION_CTRL_X = 'Vintageous.tests.commands.test__ctrl_x_and__ctrl_a'
TESTS_CMDS_ACTION_VI_CC = 'Vintageous.tests.commands.test__vi_cc'
TESTS_CMDS_ACTION_VI_BIG_S = 'Vintageous.tests.commands.test__vi_big_s'

TESTS_UNITS_WORD = 'Vintageous.tests.vi.test_word'

TESTS_CMDS_ALL_SUPPORT = [TESTS_CMDS_SET_ACTION, TESTS_CMDS_SET_MOTION]

TESTS_CMDS_ALL_ACTIONS = [TESTS_CMDS_ACTION_CTRL_X,
                          TESTS_CMDS_ACTION_VI_CC,
                          ]

TESTS_CMDS_ALL_MOTIONS = [TESTS_CMDS_MOTION_VI_L,
                          TESTS_CMDS_MOTION_VI_H,
                          TESTS_CMDS_MOTION_VI_BIG_G,
                          TESTS_CMDS_MOTION_VI_DOLLAR,
                          TESTS_CMDS_MOTION_VI_J,
                          TESTS_CMDS_MOTION_VI_K,
                          TESTS_CMDS_MOTION_VI_BIG_F,
                          TESTS_CMDS_ACTION_VI_BIG_S,
                          ]

TESTS_UNITS_ALL = [TESTS_UNITS_WORD,
                  ]

TESTS_CMDS_ALL = TESTS_CMDS_ALL_MOTIONS + TESTS_CMDS_ALL_ACTIONS + TESTS_CMDS_ALL_SUPPORT

test_suites = {
        '_storage_': ['_pt_run_tests', [TESTS_MARKS, TESTS_SETTINGS, TESTS_REGISTERS, TESTS_CONSTANTS]],

        'settings': ['_pt_run_tests', [TESTS_SETTINGS]],
        'registers': ['_pt_run_tests', [TESTS_REGISTERS]],
        'marks': ['_pt_run_tests', [TESTS_MARKS]],

        'state': ['_pt_run_tests', [TESTS_STATE]],
        'run': ['_pt_run_tests', [TESTS_RUN]],

        'constants': ['_pt_run_tests', [TESTS_CONSTANTS]],

        'cmd_data': ['_pt_run_tests', [TESTS_CMD_DATA]],

        'keymap': ['_pt_run_tests', [TESTS_KEYMAP]],

        'commands': ['_pt_run_tests', TESTS_CMDS_ALL],

        'units': ['_pt_run_tests', TESTS_UNITS_ALL],
}

# Combine all tests under one key for convenience. Ignore keys starting with an underscore. Use
# these for subsets of all the remaining tests that you don't want repeated under '_all_'.
# Convert to list so the 'chain' doesn't get exhausted after the first use.
all_tests = list(chain(*[data[1] for (key, data) in test_suites.items() if not key.startswith('_')]))
test_suites['_all_'] = ['_pt_run_tests', all_tests]

class _ptPrintResults(sublime_plugin.TextCommand):
    def run(self, edit, content):
        view = sublime.active_window().new_file()
        view.insert(edit, 0, content)
        view.set_scratch(True)

class ShowVintageousTestSuites(sublime_plugin.WindowCommand):
    """Displays a quick panel listing all available test stuites.
    """
    def run(self):
        TestsState.running = True
        self.window.show_quick_panel(sorted(test_suites.keys()), self.run_suite)

    def run_suite(self, idx):
        suite_name = sorted(test_suites.keys())[idx]
        TestsState.suite = suite_name
        command_to_run, _ = test_suites[suite_name]

        self.window.run_command(command_to_run, dict(suite_name=suite_name))

class _ptRunTests(sublime_plugin.WindowCommand):
    def run(self, suite_name):
        make_temp_file()
        # We open the file here, but Sublime Text loads it asynchronously, so we continue in an
        # event handler, once it's been fully loaded.
        self.window.open_file(TEST_DATA_PATH[1])

class _ptTestDataDispatcher(sublime_plugin.EventListener):
    def on_load(self, view):
        try:
            if (view.file_name() and view.file_name() == TEST_DATA_PATH[1] and
                TestsState.running):

                    TestsState.running = False
                    TestsState.view = view

                    _, suite_names = test_suites[TestsState.suite]
                    suite = unittest.TestLoader().loadTestsFromNames(suite_names)

                    bucket = io.StringIO()
                    unittest.TextTestRunner(stream=bucket, verbosity=1).run(suite)

                    view.run_command('_pt_print_results', {'content': bucket.getvalue()})
                    w = sublime.active_window()
                    # Close data view.
                    w.run_command('prev_view')
                    TestsState.view.set_scratch(True)
                    w.run_command('close')
                    # Ugly hack to return focus to the results view.
                    w.run_command('show_panel', {'panel': 'console', 'toggle': True})
                    w.run_command('show_panel', {'panel': 'console', 'toggle': True})
        except Exception as e:
            print(e)
        finally:
            try:
                os.close(TEST_DATA_PATH[0])
            except Exception as e:
                print("Could not close temp file...")
                print(e)

class WriteToBuffer(sublime_plugin.TextCommand):
    """Replaces the buffer's content with the specified `text`.

       `text`: Text to be written to the buffer.
       `file_name`: If this file name does not match the receiving view's, abort.
    """
    def run(self, edit, file_name='', text=''):
        if not file_name:
            return

        if self.view.file_name().lower() == file_name.lower():
            self.view.replace(edit, sublime.Region(0, self.view.size()), text)
<|endoftext|>


<commit_before># Test cases can be run with either of the following:
# python -m unittest discover
# nosetests -v --rednose --nologcapture

import unittest
import logging
import json
from flask_api import status    # HTTP Status Codes
import server_promotion as server

######################################################################
#  T E S T   C A S E S
######################################################################
class TestPromotionServer(unittest.TestCase):

    def setUp(self):
        server.app.debug = True
        server.app.logger.addHandler(logging.StreamHandler())
        server.app.logger.setLevel(logging.CRITICAL)

        self.app = server.app.test_client()
        server.inititalize_redis()
        server.data_reset()
        server.data_load({"name": "Buy one, get one free","description": "Buy an item having a cost of atleast 20$ to get one free.Cost of the higher price product will be taken into account", "kind": "sales-promotion1","status": "Active"})
        server.data_load({"name": "Buy one, get two free","description": "Buy an item having a cost of atleast 40$ to get three free.Cost of the higher price product will be taken into account", "kind": "sales-promotion3","status": "Active"})
        server.data_load({"name": "Buy one, get two free","description": "Buy an item having a cost of atleast 60$ to get three free.Cost of the higher price product will be taken into account", "kind": "sales-promotion3","status": "Active"})

    def test_index(self):
        resp = self.app.get('/')
        self.assertEqual( resp.status_code, status.HTTP_200_OK )
        self.assertTrue ('Promotions REST API' in resp.data)

    def test_cancel_promotions_not_present(self):
        resp = self.app.put('/promotions/4/cancel')
        self.assertEqual( resp.status_code, status.HTTP_404_NOT_FOUND )

    def test_cancel_promotions_present(self):
        resp = self.app.put('/promotions/3/cancel')
        self.assertEqual( resp.status_code, status.HTTP_200_OK )

    def test_update_promotions_not_present(self):
        resp = self.app.put('/promotions/4')
        self.assertEqual( resp.status_code, status.HTTP_404_NOT_FOUND )

    def test_update_promotions_with_no_data(self):
        resp = self.app.put('/promotions/3', data=None, content_type='application/json')
        self.assertEqual( resp.status_code, status.HTTP_400_BAD_REQUEST )

    def test_update_promotions_present_valid_data(self):
        updated_promotion = {"name": "Buy one, get two free","description": "Buy an item having a cost of atleast 90$ to get three free.Cost of the higher price product will be taken into account", "kind": "sales-promotion3","status": "Active"}
        data = json.dumps(updated_promotion)
        resp = self.app.put('/promotions/3', data=data, content_type='application/json')
        self.assertEqual( resp.status_code, status.HTTP_200_OK )
        data = json.loads(resp.data)
        self.assertTrue("atleast 90$" in data['description'])
        self.assertEqual(data['kind'], 'sales-promotion3')
        self.assertEqual(data['status'], 'Active')
        self.assertEqual (data['name'], 'Buy one, get two free')

    def test_update_promotions_present_invalid_data(self):
        updated_promotion = {"name": "Buy one, get two free","description": "Buy an item having a cost of atleast 90$ to get three free.Cost of the higher price product will be taken into account", "status": "Active"}
        data = json.dumps(updated_promotion)
        resp = self.app.put('/promotions/3', data=data, content_type='application/json')
        self.assertEqual( resp.status_code, status.HTTP_400_BAD_REQUEST )

######################################################################
# Utility functions
######################################################################

    def get_promotion_count(self):
        # save the current number of promotion schemes
        resp = self.app.get('/promotions')
        self.assertEqual( resp.status_code, status.HTTP_200_OK )
        data = json.loads(resp.data)
        return len(data)

######################################################################
#   M A I N
######################################################################
if __name__ == '__main__':
    unittest.main()
<commit_msg>fixes #39<commit_after># Test cases can be run with either of the following:
# python -m unittest discover
# nosetests -v --rednose --nologcapture

import unittest
import logging
import json
from flask_api import status    # HTTP Status Codes
import server_promotion as server

######################################################################
#  T E S T   C A S E S
######################################################################
class TestPromotionServer(unittest.TestCase):

    def setUp(self):
        server.app.debug = True
        server.app.logger.addHandler(logging.StreamHandler())
        server.app.logger.setLevel(logging.CRITICAL)

        self.app = server.app.test_client()
        server.inititalize_redis()
        server.data_reset()
        server.data_load({"name": "Buy one, get one free","description": "Buy an item having a cost of atleast 20$ to get one free.Cost of the higher price product will be taken into account", "kind": "sales-promotion1","status": "Active"})
        server.data_load({"name": "Buy one, get two free","description": "Buy an item having a cost of atleast 40$ to get three free.Cost of the higher price product will be taken into account", "kind": "sales-promotion3","status": "Active"})
        server.data_load({"name": "Buy one, get two free","description": "Buy an item having a cost of atleast 60$ to get three free.Cost of the higher price product will be taken into account", "kind": "sales-promotion3","status": "Active"})

    def test_index(self):
        resp = self.app.get('/')
        self.assertEqual( resp.status_code, status.HTTP_200_OK )
        self.assertTrue ('Promotions REST API' in resp.data)

    def test_get_promotion(self):
        resp = self.app.get('/promotions/2')
        self.assertEqual( resp.status_code, status.HTTP_200_OK )
        data = json.loads(resp.data)
        self.assertEqual (data['name'], 'Buy one, get two free')

    def test_get_nonexisting_promotion(self):
        resp = self.app.get('/promotions/5')
        self.assertEqual( resp.status_code, status.HTTP_404_NOT_FOUND )

    def test_cancel_promotions_not_present(self):
        resp = self.app.put('/promotions/4/cancel')
        self.assertEqual( resp.status_code, status.HTTP_404_NOT_FOUND )

    def test_cancel_promotions_present(self):
        resp = self.app.put('/promotions/3/cancel')
        self.assertEqual( resp.status_code, status.HTTP_200_OK )

    def test_update_promotions_not_present(self):
        resp = self.app.put('/promotions/4')
        self.assertEqual( resp.status_code, status.HTTP_404_NOT_FOUND )

    def test_update_promotions_with_no_data(self):
        resp = self.app.put('/promotions/3', data=None, content_type='application/json')
        self.assertEqual( resp.status_code, status.HTTP_400_BAD_REQUEST )

    def test_update_promotions_present_valid_data(self):
        updated_promotion = {"name": "Buy one, get two free","description": "Buy an item having a cost of atleast 90$ to get three free.Cost of the higher price product will be taken into account", "kind": "sales-promotion3","status": "Active"}
        data = json.dumps(updated_promotion)
        resp = self.app.put('/promotions/3', data=data, content_type='application/json')
        self.assertEqual( resp.status_code, status.HTTP_200_OK )
        data = json.loads(resp.data)
        self.assertTrue("atleast 90$" in data['description'])
        self.assertEqual(data['kind'], 'sales-promotion3')
        self.assertEqual(data['status'], 'Active')
        self.assertEqual (data['name'], 'Buy one, get two free')

    def test_update_promotions_present_invalid_data(self):
        updated_promotion = {"name": "Buy one, get two free","description": "Buy an item having a cost of atleast 90$ to get three free.Cost of the higher price product will be taken into account", "status": "Active"}
        data = json.dumps(updated_promotion)
        resp = self.app.put('/promotions/3', data=data, content_type='application/json')
        self.assertEqual( resp.status_code, status.HTTP_400_BAD_REQUEST )

######################################################################
# Utility functions
######################################################################

    def get_promotion_count(self):
        # save the current number of promotion schemes
        resp = self.app.get('/promotions')
        self.assertEqual( resp.status_code, status.HTTP_200_OK )
        data = json.loads(resp.data)
        return len(data)

######################################################################
#   M A I N
######################################################################
if __name__ == '__main__':
    unittest.main()
<|endoftext|>


<commit_before># -*- coding: iso8859-1 -*-
#
# Copyright (C) 2003, 2004 Edgewall Software
# Copyright (C) 2003, 2004 Jonas Borgstrm <jonas@edgewall.com>
#
# Trac is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Trac is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# Author: Jonas Borgstrm <jonas@edgewall.com>

import os,os.path
import time
import re

from util import *
from Module import Module
from Wiki import wiki_to_html
import perm

dynvars_re = re.compile('\$([A-Z]+)')
dynvars_disallowed_var_chars_re = re.compile('[^A-Z0-9_]')
dynvars_disallowed_value_chars_re = re.compile('[^a-zA-Z0-9-_@.,]')

class Report (Module):
    template_name = 'report.cs'
    template_rss_name = 'report_rss.cs'
    template_csv_name = 'report_csv.cs'

    def sql_sub_vars(self, sql, args):
        m = re.search(dynvars_re, sql)
        if not m:
            return sql
        aname=m.group()[1:]
        try:
            arg = args[aname]
        except KeyError:
            raise TracError("Dynamic variable '$%s' not defined." % aname)
        self.req.hdf.setValue('report.var.'+aname , arg)
        sql = m.string[:m.start()] + arg + m.string[m.end():]
        return self.sql_sub_vars(sql, args)

    def get_info(self, id, args):
        cursor = self.db.cursor()

        if id == -1:
            # If no particular report was requested, display
            # a list of available reports instead
            cursor.execute("SELECT id AS report, title "
                           "FROM report "
                           "ORDER BY report")
            title = 'Available reports'
        else:
            cursor.execute('SELECT title, sql from report WHERE id=%s', id)
            row = cursor.fetchone()
            try:
                if not row:
                    raise TracError('Report %d does not exist.' % id,
                                'Invalid Report Number')
                title = row[0]
                sql   = self.sql_sub_vars(row[1], args)
                cursor.execute(sql)
            except Exception, e:
                self.error = e
                self.req.hdf.setValue('report.message',
                                      'report failed: %s' % e)
                return None

        # FIXME: fetchall should probably not be used.
        info = cursor.fetchall()
        cols = cursor.rs.col_defs
        # Escape the values so that they are safe to have as html parameters
#        info = map(lambda row: map(lambda x: escape(x), row), info)
        return [cols, info, title]

    def create_report(self, title, sql):
        self.perm.assert_permission(perm.REPORT_CREATE)

        cursor = self.db.cursor()

        cursor.execute('INSERT INTO report (id, title, sql)'
                        'VALUES (NULL, %s, %s)', title, sql)
        id = self.db.db.sqlite_last_insert_rowid()
        self.db.commit()
        self.req.redirect(self.href.report(id))

    def delete_report(self, id):
        self.perm.assert_permission(perm.REPORT_DELETE)

        cursor = self.db.cursor ()
        cursor.execute('DELETE FROM report WHERE id=%s', id)
        self.db.commit()
        self.req.redirect(self.href.report())

    def commit_changes(self, id):
        """
        saves report changes to the database
        """
        self.perm.assert_permission(perm.REPORT_MODIFY)

        cursor = self.db.cursor()
        title = self.args['title']
        sql   = self.args['sql']

        cursor.execute('UPDATE report SET title=%s, sql=%s WHERE id=%s',
                       title, sql, id)
        self.db.commit()
        self.req.redirect(self.href.report(id))

    def render_report_editor(self, id, action='commit', copy=0):
        self.perm.assert_permission(perm.REPORT_MODIFY)
        cursor = self.db.cursor()

        if id == -1:
            title = sql = ""
        else:
            cursor.execute('SELECT title, sql FROM report WHERE id=%s', id)
            row = cursor.fetchone()
            sql = row[1]
            title = row[0]

        if copy:
            title += ' copy'

        self.req.hdf.setValue('report.mode', 'editor')
        self.req.hdf.setValue('report.title', title)
        self.req.hdf.setValue('report.id', str(id))
        self.req.hdf.setValue('report.action', action)
        self.req.hdf.setValue('report.sql', sql)

    def render_report_list(self, id, args={}):
        """
        uses a user specified sql query to extract some information
        from the database and presents it as a html table.
        """
        if self.perm.has_permission(perm.REPORT_CREATE):
            self.req.hdf.setValue('report.create_href',
                                  self.href.report(None, 'new'))

        if id != -1:
            if self.perm.has_permission(perm.REPORT_MODIFY):
                self.req.hdf.setValue('report.edit_href',
                                      self.href.report(id, 'edit'))
            if self.perm.has_permission(perm.REPORT_CREATE):
                self.req.hdf.setValue('report.copy_href',
                                      self.href.report(id, 'copy'))
            if self.perm.has_permission(perm.REPORT_DELETE):
                self.req.hdf.setValue('report.delete_href',
                                      self.href.report(id, 'delete'))

        self.req.hdf.setValue('report.mode', 'list')
        info = self.get_info(id, args)
        if not info:
            return
        [self.cols, self.rows, title] = info
        self.error = None

        self.req.hdf.setValue('title', title + ' (report)')
        self.req.hdf.setValue('report.title', title)
        self.req.hdf.setValue('report.id', str(id))

        # Convert the header info to HDF-format
        idx = 0
        for col in self.cols:
            title=col[0].capitalize()
            prefix = 'report.headers.%d' % idx
            if title[:2] == '__' and title[-2:] == '__':
                continue
            elif title[0] == '_' and title[-1] == '_':
                title = title[1:-1].capitalize()
                self.req.hdf.setValue(prefix + '.fullrow', '1')
            elif title[0] == '_':
                continue
            elif title[-1] == '_':
                title = title[:-1]
                self.req.hdf.setValue(prefix + '.breakrow', '1')
            self.req.hdf.setValue(prefix, title)
            idx = idx + 1

        # Convert the rows and cells to HDF-format
        row_idx = 0
        for row in self.rows:
            col_idx = 0
            numrows = len(row)
            for cell in row:
                cell = str(cell)
                column = self.cols[col_idx][0]
                value = {}
                # Special columns begin and end with '__'
                if column[:2] == '__' and column[-2:] == '__':
                    value['hidden'] = 1
                elif (column[0] == '_' and column[-1] == '_'):
                    value['fullrow'] = 1
                    column = column[1:-1]
                    self.req.hdf.setValue(prefix + '.breakrow', '1')
                elif column[-1] == '_':
                    value['breakrow'] = 1
                    value['breakafter'] = 1
                    column = column[:-1]
                elif column[0] == '_':
                    value['hidehtml'] = 1
                    column = column[1:]
                if column in ['ticket', '#']:
                    value['ticket_href'] = self.href.ticket(cell)
                elif column == 'description':
                    value['parsed'] = wiki_to_html(cell, self.req.hdf,
                                                   self.href)
                elif column == 'report':
                    value['report_href'] = self.href.report(cell)
                elif column in ['time', 'date','changetime', 'created', 'modified']:
                    t = time.localtime(int(cell))
                    value['date'] = time.strftime('%x', t)
                    value['time'] = time.strftime('%X', t)
                    value['datetime'] = time.strftime('%c', t)
                    value['gmt'] = time.strftime('%a, %d %b %Y %H:%M:%S GMT',
                                                 time.gmtime(int(cell)))
                prefix = 'report.items.%d.%s' % (row_idx, str(column))
                self.req.hdf.setValue(prefix, escape(str(cell)))
                for key in value.keys():
                    self.req.hdf.setValue(prefix + '.' + key, str(value[key]))

                col_idx += 1
            row_idx += 1

    def get_var_args(self):
        report_args = {}
        for arg in self.args.keys():
            if not arg == arg.upper():
                continue
            m = re.search(dynvars_disallowed_var_chars_re, arg)
            if m:
                raise ValueError("The character '%s' is not allowed "
                                 " in variable names." % m.group())
            val = self.args[arg]
            m = re.search(dynvars_disallowed_value_chars_re, val)
            if m:
                raise ValueError("The character '%s' is not allowed "
                                 " in variable data." % m.group())
            report_args[arg] = val

        # Set some default dynamic variables
        if hasattr(self,'authname'):  # FIXME: Is authname always there? - dln
            report_args['USER'] = self.req.authname

        return report_args

    def render(self):
        self.perm.assert_permission(perm.REPORT_VIEW)
        # did the user ask for any special report?
        id = int(self.args.get('id', -1))
        action = self.args.get('action', 'list')

        try:
            report_args = self.get_var_args()
        except ValueError,e:
            self.req.hdf.setValue('report.message', 'report failed: %s' % e)
            return

        if action == 'create':
            self.create_report(self.args['title'], self.args['sql'])
        elif action == 'delete':
            self.delete_report(id)
        elif action == 'commit':
            self.commit_changes(id)
        elif action == 'new':
            self.render_report_editor(-1, 'create')
        elif action == 'copy':
            self.render_report_editor(id, 'create', 1)
        elif action == 'edit':
            self.render_report_editor(id, 'commit')
        else:
            self.render_report_list(id, report_args)

    def display_rss(self):
        self.req.display(self.template_rss_name, 'text/xml')

    def display_csv(self,sep=','):
        self.req.send_response(200)
        self.req.send_header('Content-Type', 'text/plain')
        self.req.end_headers()
        titles = ''
        if self.error:
            self.req.write('Report failed: %s' % self.error)
            return
        self.req.write(sep.join([c[0] for c in self.cols]) + '\r\n')
        for row in self.rows:
            self.req.write(sep.join([str(c).replace(sep,"_").replace('\n',' ').replace('\r',' ') for c in row]) + '\r\n')

    def display_tab(self):
        self.display_csv('\t')

<commit_msg>Escape tags from RSS titles.<commit_after># -*- coding: iso8859-1 -*-
#
# Copyright (C) 2003, 2004 Edgewall Software
# Copyright (C) 2003, 2004 Jonas Borgstrm <jonas@edgewall.com>
#
# Trac is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Trac is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# Author: Jonas Borgstrm <jonas@edgewall.com>

import os,os.path
import time
import re
import cgi

from util import *
from Module import Module
from Wiki import wiki_to_html
import perm

dynvars_re = re.compile('\$([A-Z]+)')
dynvars_disallowed_var_chars_re = re.compile('[^A-Z0-9_]')
dynvars_disallowed_value_chars_re = re.compile('[^a-zA-Z0-9-_@.,]')

class Report (Module):
    template_name = 'report.cs'
    template_rss_name = 'report_rss.cs'
    template_csv_name = 'report_csv.cs'

    def sql_sub_vars(self, sql, args):
        m = re.search(dynvars_re, sql)
        if not m:
            return sql
        aname=m.group()[1:]
        try:
            arg = args[aname]
        except KeyError:
            raise TracError("Dynamic variable '$%s' not defined." % aname)
        self.req.hdf.setValue('report.var.'+aname , arg)
        sql = m.string[:m.start()] + arg + m.string[m.end():]
        return self.sql_sub_vars(sql, args)

    def get_info(self, id, args):
        cursor = self.db.cursor()

        if id == -1:
            # If no particular report was requested, display
            # a list of available reports instead
            cursor.execute("SELECT id AS report, title "
                           "FROM report "
                           "ORDER BY report")
            title = 'Available reports'
        else:
            cursor.execute('SELECT title, sql from report WHERE id=%s', id)
            row = cursor.fetchone()
            try:
                if not row:
                    raise TracError('Report %d does not exist.' % id,
                                'Invalid Report Number')
                title = row[0]
                sql   = self.sql_sub_vars(row[1], args)
                cursor.execute(sql)
            except Exception, e:
                self.error = e
                self.req.hdf.setValue('report.message',
                                      'report failed: %s' % e)
                return None

        # FIXME: fetchall should probably not be used.
        info = cursor.fetchall()
        cols = cursor.rs.col_defs
        # Escape the values so that they are safe to have as html parameters
#        info = map(lambda row: map(lambda x: escape(x), row), info)
        return [cols, info, title]

    def create_report(self, title, sql):
        self.perm.assert_permission(perm.REPORT_CREATE)

        cursor = self.db.cursor()

        cursor.execute('INSERT INTO report (id, title, sql)'
                        'VALUES (NULL, %s, %s)', title, sql)
        id = self.db.db.sqlite_last_insert_rowid()
        self.db.commit()
        self.req.redirect(self.href.report(id))

    def delete_report(self, id):
        self.perm.assert_permission(perm.REPORT_DELETE)

        cursor = self.db.cursor ()
        cursor.execute('DELETE FROM report WHERE id=%s', id)
        self.db.commit()
        self.req.redirect(self.href.report())

    def commit_changes(self, id):
        """
        saves report changes to the database
        """
        self.perm.assert_permission(perm.REPORT_MODIFY)

        cursor = self.db.cursor()
        title = self.args['title']
        sql   = self.args['sql']

        cursor.execute('UPDATE report SET title=%s, sql=%s WHERE id=%s',
                       title, sql, id)
        self.db.commit()
        self.req.redirect(self.href.report(id))

    def render_report_editor(self, id, action='commit', copy=0):
        self.perm.assert_permission(perm.REPORT_MODIFY)
        cursor = self.db.cursor()

        if id == -1:
            title = sql = ""
        else:
            cursor.execute('SELECT title, sql FROM report WHERE id=%s', id)
            row = cursor.fetchone()
            sql = row[1]
            title = row[0]

        if copy:
            title += ' copy'

        self.req.hdf.setValue('report.mode', 'editor')
        self.req.hdf.setValue('report.title', title)
        self.req.hdf.setValue('report.id', str(id))
        self.req.hdf.setValue('report.action', action)
        self.req.hdf.setValue('report.sql', sql)

    def render_report_list(self, id, args={}):
        """
        uses a user specified sql query to extract some information
        from the database and presents it as a html table.
        """
        if self.perm.has_permission(perm.REPORT_CREATE):
            self.req.hdf.setValue('report.create_href',
                                  self.href.report(None, 'new'))

        if id != -1:
            if self.perm.has_permission(perm.REPORT_MODIFY):
                self.req.hdf.setValue('report.edit_href',
                                      self.href.report(id, 'edit'))
            if self.perm.has_permission(perm.REPORT_CREATE):
                self.req.hdf.setValue('report.copy_href',
                                      self.href.report(id, 'copy'))
            if self.perm.has_permission(perm.REPORT_DELETE):
                self.req.hdf.setValue('report.delete_href',
                                      self.href.report(id, 'delete'))

        self.req.hdf.setValue('report.mode', 'list')
        info = self.get_info(id, args)
        if not info:
            return
        [self.cols, self.rows, title] = info
        self.error = None

        self.req.hdf.setValue('title', title + ' (report)')
        self.req.hdf.setValue('report.title', title)
        self.req.hdf.setValue('report.id', str(id))

        # Convert the header info to HDF-format
        idx = 0
        for col in self.cols:
            title=col[0].capitalize()
            prefix = 'report.headers.%d' % idx
            if title[:2] == '__' and title[-2:] == '__':
                continue
            elif title[0] == '_' and title[-1] == '_':
                title = title[1:-1].capitalize()
                self.req.hdf.setValue(prefix + '.fullrow', '1')
            elif title[0] == '_':
                continue
            elif title[-1] == '_':
                title = title[:-1]
                self.req.hdf.setValue(prefix + '.breakrow', '1')
            self.req.hdf.setValue(prefix, title)
            idx = idx + 1

        # Convert the rows and cells to HDF-format
        row_idx = 0
        for row in self.rows:
            col_idx = 0
            numrows = len(row)
            for cell in row:
                cell = str(cell)
                column = self.cols[col_idx][0]
                value = {}
                # Special columns begin and end with '__'
                if column[:2] == '__' and column[-2:] == '__':
                    value['hidden'] = 1
                elif (column[0] == '_' and column[-1] == '_'):
                    value['fullrow'] = 1
                    column = column[1:-1]
                    self.req.hdf.setValue(prefix + '.breakrow', '1')
                elif column[-1] == '_':
                    value['breakrow'] = 1
                    value['breakafter'] = 1
                    column = column[:-1]
                elif column[0] == '_':
                    value['hidehtml'] = 1
                    column = column[1:]
                if column in ['ticket', '#']:
                    value['ticket_href'] = self.href.ticket(cell)
                elif column == 'description':
                    value['parsed'] = wiki_to_html(cell, self.req.hdf,
                                                   self.href)
                elif column == 'report':
                    value['report_href'] = self.href.report(cell)
                elif column in ['time', 'date','changetime', 'created', 'modified']:
                    t = time.localtime(int(cell))
                    value['date'] = time.strftime('%x', t)
                    value['time'] = time.strftime('%X', t)
                    value['datetime'] = time.strftime('%c', t)
                    value['gmt'] = time.strftime('%a, %d %b %Y %H:%M:%S GMT',
                                                 time.gmtime(int(cell)))
                prefix = 'report.items.%d.%s' % (row_idx, str(column))
                self.req.hdf.setValue(prefix, escape(str(cell)))
                for key in value.keys():
                    self.req.hdf.setValue(prefix + '.' + key, str(value[key]))

                col_idx += 1
            row_idx += 1

    def get_var_args(self):
        report_args = {}
        for arg in self.args.keys():
            if not arg == arg.upper():
                continue
            m = re.search(dynvars_disallowed_var_chars_re, arg)
            if m:
                raise ValueError("The character '%s' is not allowed "
                                 " in variable names." % m.group())
            val = self.args[arg]
            m = re.search(dynvars_disallowed_value_chars_re, val)
            if m:
                raise ValueError("The character '%s' is not allowed "
                                 " in variable data." % m.group())
            report_args[arg] = val

        # Set some default dynamic variables
        if hasattr(self,'authname'):  # FIXME: Is authname always there? - dln
            report_args['USER'] = self.req.authname

        return report_args

    def render(self):
        self.perm.assert_permission(perm.REPORT_VIEW)
        # did the user ask for any special report?
        id = int(self.args.get('id', -1))
        action = self.args.get('action', 'list')

        try:
            report_args = self.get_var_args()
        except ValueError,e:
            self.req.hdf.setValue('report.message', 'report failed: %s' % e)
            return

        if action == 'create':
            self.create_report(self.args['title'], self.args['sql'])
        elif action == 'delete':
            self.delete_report(id)
        elif action == 'commit':
            self.commit_changes(id)
        elif action == 'new':
            self.render_report_editor(-1, 'create')
        elif action == 'copy':
            self.render_report_editor(id, 'create', 1)
        elif action == 'edit':
            self.render_report_editor(id, 'commit')
        else:
            self.render_report_list(id, report_args)

    def display_rss(self):
        item = self.req.hdf.getObj('report.items')
        item = item.child()
        while item:
            nodename = 'report.items.%s.summary' % item.name()
            summary = self.req.hdf.getValue(nodename, '')
            self.req.hdf.setValue(nodename, escape(summary))
            item = item.next()
        self.req.display(self.template_rss_name, 'text/xml')

    def display_csv(self,sep=','):
        self.req.send_response(200)
        self.req.send_header('Content-Type', 'text/plain')
        self.req.end_headers()
        titles = ''
        if self.error:
            self.req.write('Report failed: %s' % self.error)
            return
        self.req.write(sep.join([c[0] for c in self.cols]) + '\r\n')
        for row in self.rows:
            self.req.write(sep.join([str(c).replace(sep,"_").replace('\n',' ').replace('\r',' ') for c in row]) + '\r\n')

    def display_tab(self):
        self.display_csv('\t')

<|endoftext|>