<commit_before>import logging
import urlparse
import xml.sax
import re
import feedparser
import urllib2
import httplib
import socket
from flexget.feed import Entry
from flexget.plugin import *
from flexget.utils.log import log_once
from flexget.plugins.cached_input import cached
from flexget.utils.tools import urlopener

log = logging.getLogger('rss')

class InputRSS(object):
    """
        Parses RSS feed.

        Hazzlefree configuration for public rss feeds:

        rss: <url>

        Configuration with basic http authentication:

        rss:
          url: <url>
          username: <name>
          password: <password>

        Advanced usages:

        You may wish to clean up the entry by stripping out all non-ascii characters.
        This can be done by setting ascii value to yes.

        Example:

        rss:
          url: <url>
          ascii: yes

        Incase RSS-feed uses some nonstandard field for urls and automatic detection fails
        you can configure plugin to use url from any feedparser entry attribute.

        Example:

        rss:
          url: <url>
          link: guid

        You can disable few possibly annoying warnings by setting silent value to
        yes on feeds where there are frequently invalid items.

        Example:

        rss:
          url: <url>
          silent: yes

        You can group all the links of an item, to make the download plugin tolerant
        to broken urls: it will try to download each url until one works.
        Links are enclosures plus item fields given by the link value, in that order.
        The value to set is "group links".

        Example:

        rss:
          url: <url>
          group links: yes
    """

    def validator(self):
        from flexget import validator
        root = validator.factory()
        root.accept('url')
        root.accept('file')
        advanced = root.accept('dict')
        advanced.accept('url', key='url', required=True)
        advanced.accept('file', key='url')
        advanced.accept('text', key='username')
        advanced.accept('text', key='password')
        advanced.accept('text', key='link')
        advanced.accept('list', key='link').accept('text')
        advanced.accept('boolean', key='silent')
        advanced.accept('boolean', key='ascii')
        advanced.accept('boolean', key='filename')
        advanced.accept('boolean', key='group links')
        return root

    def get_config(self, feed):
        config = feed.config['rss']
        if isinstance(config, basestring):
            config = {'url': config}
        if isinstance(config.get('link'), basestring):
            config['link'] = [config['link']]
        if config.get('link'):
            config['link'] = [link.replace(':', '_').lower() for link in config['link']]
        # set the default link fields to 'link' and 'guid'
        if not config.get('link') or 'auto' in config['link']:
            config['link'] = ['link', 'guid']
        # set default value for group links as deactivated
        if not config.get('group links') or config['group links'] == 'auto':
            config['group links'] = False
        # use basic auth when needed
        if 'username' in config and 'password' in config:
            config['url'] = self.passwordize(config['url'], config['username'], config['password'])
        return config

    def passwordize(self, url, user, password):
        """Add username and password to url"""
        parts = list(urlparse.urlsplit(url))
        parts[1] = user + ':' + password + '@' + parts[1]
        url = urlparse.urlunsplit(parts)
        return url

    @cached('rss', 'url')
    @internet(log)
    def on_feed_input(self, feed):
        config = self.get_config(feed)

        log.debug('Checking feed %s (%s)' % (feed.name, config['url']))

        # check etags and last modified -headers
        # let's not, flexget works better when feed contains all entries all the time ?
        etag = None
        modified = None
        """
        etag = feed.cache.get('etag', None)
        if etag:
            log.debug('Sending etag %s for feed %s' % (etag, feed.name))
        modified = feed.cache.get('modified', None)
        if modified:
            log.debug('Sending last-modified %s for feed %s' % (etag, feed.name))
        """

        # set timeout to one minute
        orig_timout = socket.getdefaulttimeout()
        socket.setdefaulttimeout(60)

        # get the feed & parse
        if urllib2._opener:
            rss = feedparser.parse(config['url'], etag=etag, modified=modified, handlers=urllib2._opener.handlers)
        else:
            rss = feedparser.parse(config['url'], etag=etag, modified=modified)

        # restore original timeout
        socket.setdefaulttimeout(orig_timout)

        # status checks
        status = rss.get('status', False)
        if not status:
            log.debug('RSS does not have status (normal if processing a file)')
        elif status == 304:
            log.debug('Feed %s hasn\'t changed, skipping' % feed.name)
            return
        elif status == 401:
            raise PluginError('Authentication needed for feed %s: %s' % \
                (feed.name, rss.headers['www-authenticate']), log)
        elif status == 404:
            raise PluginError('RSS Feed %s not found' % feed.name, log)
        elif status == 500:
            raise PluginError('Internal server exception on feed %s' % feed.name, log)

        # check for bozo
        ex = rss.get('bozo_exception', False)
        ignore = False
        if ex:
            if isinstance(ex, feedparser.NonXMLContentType):
                # see: http://www.feedparser.org/docs/character-encoding.html#advanced.encoding.nonxml
                log.debug('ignoring feedparser.NonXMLContentType')
                ignore = True
            elif isinstance(ex, feedparser.CharacterEncodingOverride):
                # see: ticket 88
                log.debug('ignoring feedparser.CharacterEncodingOverride')
                ignore = True
            elif isinstance(ex, xml.sax._exceptions.SAXParseException):
                if len(rss.entries) == 0:
                    # save invalid data for review, this is a bit ugly but users seem to really confused when
                    # html pages (login pages) are received
                    log.critical('Invalid XML received from feed %s' % feed.name)
                    req = urlopener(config['url'], log)
                    data = req.read()
                    req.close()
                    ext = 'xml'
                    if '<html>' in data.lower():
                        log.critical('Received content is HTML page, not an RSS feed')
                        ext = 'html'
                    if 'login' in data.lower() or 'username' in data.lower():
                        log.critical('Received content looks a bit like login page')
                    if 'error' in data.lower():
                        log.critical('Received content looks a bit like error page')
                    import os
                    received = os.path.join(feed.manager.config_base, 'received')
                    if not os.path.isdir(received):
                        os.mkdir(received)
                    filename = os.path.join(received, '%s.%s' % (feed.name, ext))
                    f = open(filename, 'w')
                    f.write(data)
                    f.close()
                    log.critical('I have saved the invalid content to %s for you to view' % filename)
                    raise PluginError('Received invalid RSS content')
                else:
                    msg = 'Invalid XML received. However feedparser still produced entries. Ignoring the error ...'
                    if not config.get('silent', False):
                        log.info(msg)
                    else:
                        log.debug(msg)
                    ignore = True
            elif isinstance(ex, httplib.BadStatusLine) or \
                 isinstance(ex, IOError):
                raise ex # let the @internet decorator handle
            else:
                # all other bozo errors
                # TODO: refactor the dumping into a own method, DUPLICATED CODE
                if len(rss.entries) == 0:
                    log.critical('Invalid RSS received from feed %s' % feed.name)
                    req = urlopener(config['url'], log)
                    data = req.read()
                    req.close()
                    ext = 'xml'
                    import os
                    received = os.path.join(feed.manager.config_base, 'received')
                    if not os.path.isdir(received):
                        os.mkdir(received)
                    filename = os.path.join(received, '%s.%s' % (feed.name, ext))
                    f = open(filename, 'w')
                    f.write(data)
                    f.close()
                    log.critical('I have saved the invalid content to %s for you to view' % filename)
                    raise PluginError('Unhandled bozo_exception. Type: %s (feed: %s)' % \
                        (ex.__class__.__name__, feed.name), log)
                else:
                    msg = 'Invalid RSS received. However feedparser still produced entries. Ignoring the error ...'
                    if not config.get('silent', False):
                        log.info(msg)
                    else:
                        log.debug(msg)

        if rss['bozo'] and not ignore:
            log.error(rss)
            log.error('Bozo feed exception on %s' % feed.name)
            return

        log.debug('encoding %s' % rss.encoding)

        # update etag, use last modified if no etag exists
        """
        if 'etag' in rss and type(rss['etag']) != feedparser.types.NoneType:
            etag = rss.etag.replace("'", '').replace('"', '')
            feed.cache.store('etag', etag, 90)
            log.debug('etag %s saved for feed %s' % (etag, feed.name))
        elif hasattr(rss, 'headers'):
            if 'last-modified' in rss.headers:
                feed.cache.store('modified', rss.modified, 90)
                log.debug('last modified saved for feed %s', feed.name)
        """

        # field name for url can be configured by setting link.
        # default value is auto but for example guid is used in some feeds
        ignored = 0
        for entry in rss.entries:

            # ignore entries without title
            if not entry.title:
                log.debug('skipping entry without title')
                ignored += 1
                continue

            # convert title to ascii (cleanup)
            if config.get('ascii', False):
                entry.title = entry.title.encode('ascii', 'ignore')

            # remove annoying zero width spaces
            entry.title = entry.title.replace(u'\u200B', u'')

            # helper
            # TODO: confusing? refactor into class member ...

            def add_entry(ea):
                from flexget.utils.tools import decode_html
                ea['title'] = entry.title

                # grab fields
                fields = ['author', 'description']
                for field in fields:
                    if field in entry:
                        try:
                            ea[field] = decode_html(getattr(entry, field))
                        except UnicodeDecodeError:
                            log.warning('Failed to decode entry %s field %s' % (ea['title'], field))

                # store basic auth info
                if 'username' in config and 'password' in config:
                    ea['basic_auth_username'] = config['username']
                    ea['basic_auth_password'] = config['password']
                feed.entries.append(ea)

            # create from enclosures if present
            enclosures = entry.get('enclosures', [])
            if config.get('group links'):
                enclosures_urls = []
            if enclosures:
                #log.debug('adding %i entries from enclosures' % len(enclosures))
                for enclosure in enclosures:
                    ee = Entry()
                    if not 'href' in enclosure:
                        log_once('RSS-entry %s enclosure does not have url' % entry.title, log)
                        continue
                    if config.get('group links'):
                        enclosures_urls.append(enclosure['href'])
                        continue
                    ee['url'] = enclosure['href']
                    # get optional meta-data
                    if 'length' in enclosure:
                        try:
                            ee['size'] = int(enclosure['length'])
                        except:
                            ee['size'] = 0
                    if 'type' in enclosure:
                        ee['type'] = enclosure['type']
                    # if enclosure has size OR there are multiple enclosures use filename from url
                    if ee.get('size', 0) != 0 or len(enclosures) > 1:
                        if ee['url'].rfind != -1:
                            # parse filename from enclosure url
                            # TODO: better and perhaps join/in download plugin? also see urlparse module
                            match = re.search('.*\/([^?#]*)', ee['url'])
                            if match and config.get('filename', True):
                                ee['filename'] = match.group(1)
                                log.log(5, 'filename %s from enclosure' % ee['filename'])
                    add_entry(ee)
                if not config.get('group links'):
                    continue

            # create flexget entry
            e = Entry()

            # search for known url fields
            if config.get('group links'):
                e['urls'] = enclosures_urls
                for url_field in config['link']:
                    if url_field in entry:
                        e['urls'].append(entry[url_field])

            for url_field in config['link']:
                if url_field in entry:
                    e['url'] = entry[url_field]
                    break
            else:
                log.debug('%s does not have link (%s) or enclosure' % (entry.title, ', '.join(config['link'])))
                ignored += 1
                continue

            add_entry(e)

        if ignored:
            if not config.get('silent'):
                log.warning('Skipped %s RSS-entries without required information (title, link or enclosures)' % ignored)

register_plugin(InputRSS, 'rss')
<commit_msg>Fixes #601. Decoding error is ignored if feed produces entries.<commit_after>import logging
import urlparse
import xml.sax
import re
import feedparser
import urllib2
import httplib
import socket
from flexget.feed import Entry
from flexget.plugin import *
from flexget.utils.log import log_once
from flexget.plugins.cached_input import cached
from flexget.utils.tools import urlopener

log = logging.getLogger('rss')

class InputRSS(object):
    """
        Parses RSS feed.

        Hazzlefree configuration for public rss feeds:

        rss: <url>

        Configuration with basic http authentication:

        rss:
          url: <url>
          username: <name>
          password: <password>

        Advanced usages:

        You may wish to clean up the entry by stripping out all non-ascii characters.
        This can be done by setting ascii value to yes.

        Example:

        rss:
          url: <url>
          ascii: yes

        Incase RSS-feed uses some nonstandard field for urls and automatic detection fails
        you can configure plugin to use url from any feedparser entry attribute.

        Example:

        rss:
          url: <url>
          link: guid

        You can disable few possibly annoying warnings by setting silent value to
        yes on feeds where there are frequently invalid items.

        Example:

        rss:
          url: <url>
          silent: yes

        You can group all the links of an item, to make the download plugin tolerant
        to broken urls: it will try to download each url until one works.
        Links are enclosures plus item fields given by the link value, in that order.
        The value to set is "group links".

        Example:

        rss:
          url: <url>
          group links: yes
    """

    def validator(self):
        from flexget import validator
        root = validator.factory()
        root.accept('url')
        root.accept('file')
        advanced = root.accept('dict')
        advanced.accept('url', key='url', required=True)
        advanced.accept('file', key='url')
        advanced.accept('text', key='username')
        advanced.accept('text', key='password')
        advanced.accept('text', key='link')
        advanced.accept('list', key='link').accept('text')
        advanced.accept('boolean', key='silent')
        advanced.accept('boolean', key='ascii')
        advanced.accept('boolean', key='filename')
        advanced.accept('boolean', key='group links')
        return root

    def get_config(self, feed):
        config = feed.config['rss']
        if isinstance(config, basestring):
            config = {'url': config}
        if isinstance(config.get('link'), basestring):
            config['link'] = [config['link']]
        if config.get('link'):
            config['link'] = [link.replace(':', '_').lower() for link in config['link']]
        # set the default link fields to 'link' and 'guid'
        if not config.get('link') or 'auto' in config['link']:
            config['link'] = ['link', 'guid']
        # set default value for group links as deactivated
        if not config.get('group links') or config['group links'] == 'auto':
            config['group links'] = False
        # use basic auth when needed
        if 'username' in config and 'password' in config:
            config['url'] = self.passwordize(config['url'], config['username'], config['password'])
        return config

    def passwordize(self, url, user, password):
        """Add username and password to url"""
        parts = list(urlparse.urlsplit(url))
        parts[1] = user + ':' + password + '@' + parts[1]
        url = urlparse.urlunsplit(parts)
        return url

    @cached('rss', 'url')
    @internet(log)
    def on_feed_input(self, feed):
        config = self.get_config(feed)

        log.debug('Checking feed %s (%s)' % (feed.name, config['url']))

        # check etags and last modified -headers
        # let's not, flexget works better when feed contains all entries all the time ?
        etag = None
        modified = None
        """
        etag = feed.cache.get('etag', None)
        if etag:
            log.debug('Sending etag %s for feed %s' % (etag, feed.name))
        modified = feed.cache.get('modified', None)
        if modified:
            log.debug('Sending last-modified %s for feed %s' % (etag, feed.name))
        """

        # set timeout to one minute
        orig_timout = socket.getdefaulttimeout()
        socket.setdefaulttimeout(60)

        # get the feed & parse
        if urllib2._opener:
            rss = feedparser.parse(config['url'], etag=etag, modified=modified, handlers=urllib2._opener.handlers)
        else:
            rss = feedparser.parse(config['url'], etag=etag, modified=modified)

        # restore original timeout
        socket.setdefaulttimeout(orig_timout)

        # status checks
        status = rss.get('status', False)
        if not status:
            log.debug('RSS does not have status (normal if processing a file)')
        elif status == 304:
            log.debug('Feed %s hasn\'t changed, skipping' % feed.name)
            return
        elif status == 401:
            raise PluginError('Authentication needed for feed %s: %s' % \
                (feed.name, rss.headers['www-authenticate']), log)
        elif status == 404:
            raise PluginError('RSS Feed %s not found' % feed.name, log)
        elif status == 500:
            raise PluginError('Internal server exception on feed %s' % feed.name, log)

        # check for bozo
        ex = rss.get('bozo_exception', False)
        ignore = False
        if ex:
            if isinstance(ex, feedparser.NonXMLContentType):
                # see: http://www.feedparser.org/docs/character-encoding.html#advanced.encoding.nonxml
                log.debug('ignoring feedparser.NonXMLContentType')
                ignore = True
            elif isinstance(ex, feedparser.CharacterEncodingOverride):
                # see: ticket 88
                log.debug('ignoring feedparser.CharacterEncodingOverride')
                ignore = True
            elif isinstance(ex, UnicodeEncodeError):
                if len(rss.entries):
                    log.info('Feed has UnicodeEncodeError but seems to produce entries, ignoring the error ...')
                    ignore = True
            elif isinstance(ex, xml.sax._exceptions.SAXParseException):
                if len(rss.entries) == 0:
                    # save invalid data for review, this is a bit ugly but users seem to really confused when
                    # html pages (login pages) are received
                    log.critical('Invalid XML received from feed %s' % feed.name)
                    req = urlopener(config['url'], log)
                    data = req.read()
                    req.close()
                    ext = 'xml'
                    if '<html>' in data.lower():
                        log.critical('Received content is HTML page, not an RSS feed')
                        ext = 'html'
                    if 'login' in data.lower() or 'username' in data.lower():
                        log.critical('Received content looks a bit like login page')
                    if 'error' in data.lower():
                        log.critical('Received content looks a bit like error page')
                    import os
                    received = os.path.join(feed.manager.config_base, 'received')
                    if not os.path.isdir(received):
                        os.mkdir(received)
                    filename = os.path.join(received, '%s.%s' % (feed.name, ext))
                    f = open(filename, 'w')
                    f.write(data)
                    f.close()
                    log.critical('I have saved the invalid content to %s for you to view' % filename)
                    raise PluginError('Received invalid RSS content')
                else:
                    msg = 'Invalid XML received. However feedparser still produced entries. Ignoring the error ...'
                    if not config.get('silent', False):
                        log.info(msg)
                    else:
                        log.debug(msg)
                    ignore = True
            elif isinstance(ex, httplib.BadStatusLine) or \
                 isinstance(ex, IOError):
                raise ex # let the @internet decorator handle
            else:
                # all other bozo errors
                # TODO: refactor the dumping into a own method, DUPLICATED CODE
                if len(rss.entries) == 0:
                    log.critical('Invalid RSS received from feed %s' % feed.name)
                    req = urlopener(config['url'], log)
                    data = req.read()
                    req.close()
                    ext = 'xml'
                    import os
                    received = os.path.join(feed.manager.config_base, 'received')
                    if not os.path.isdir(received):
                        os.mkdir(received)
                    filename = os.path.join(received, '%s.%s' % (feed.name, ext))
                    f = open(filename, 'w')
                    f.write(data)
                    f.close()
                    log.critical('I have saved the invalid content to %s for you to view' % filename)
                    raise PluginError('Unhandled bozo_exception. Type: %s (feed: %s)' % \
                        (ex.__class__.__name__, feed.name), log)
                else:
                    msg = 'Invalid RSS received. However feedparser still produced entries. Ignoring the error ...'
                    if not config.get('silent', False):
                        log.info(msg)
                    else:
                        log.debug(msg)

        if rss['bozo'] and not ignore:
            log.error(rss)
            log.error('Bozo exception %s on feed %s' % (type(ex), feed.name))
            return

        log.debug('encoding %s' % rss.encoding)

        # update etag, use last modified if no etag exists
        """
        if 'etag' in rss and type(rss['etag']) != feedparser.types.NoneType:
            etag = rss.etag.replace("'", '').replace('"', '')
            feed.cache.store('etag', etag, 90)
            log.debug('etag %s saved for feed %s' % (etag, feed.name))
        elif hasattr(rss, 'headers'):
            if 'last-modified' in rss.headers:
                feed.cache.store('modified', rss.modified, 90)
                log.debug('last modified saved for feed %s', feed.name)
        """

        # field name for url can be configured by setting link.
        # default value is auto but for example guid is used in some feeds
        ignored = 0
        for entry in rss.entries:

            # ignore entries without title
            if not entry.title:
                log.debug('skipping entry without title')
                ignored += 1
                continue

            # convert title to ascii (cleanup)
            if config.get('ascii', False):
                entry.title = entry.title.encode('ascii', 'ignore')

            # remove annoying zero width spaces
            entry.title = entry.title.replace(u'\u200B', u'')

            # helper
            # TODO: confusing? refactor into class member ...

            def add_entry(ea):
                from flexget.utils.tools import decode_html
                ea['title'] = entry.title

                # grab fields
                fields = ['author', 'description']
                for field in fields:
                    if field in entry:
                        try:
                            ea[field] = decode_html(getattr(entry, field))
                        except UnicodeDecodeError:
                            log.warning('Failed to decode entry %s field %s' % (ea['title'], field))

                # store basic auth info
                if 'username' in config and 'password' in config:
                    ea['basic_auth_username'] = config['username']
                    ea['basic_auth_password'] = config['password']
                feed.entries.append(ea)

            # create from enclosures if present
            enclosures = entry.get('enclosures', [])
            if config.get('group links'):
                enclosures_urls = []
            if enclosures:
                #log.debug('adding %i entries from enclosures' % len(enclosures))
                for enclosure in enclosures:
                    ee = Entry()
                    if not 'href' in enclosure:
                        log_once('RSS-entry %s enclosure does not have url' % entry.title, log)
                        continue
                    if config.get('group links'):
                        enclosures_urls.append(enclosure['href'])
                        continue
                    ee['url'] = enclosure['href']
                    # get optional meta-data
                    if 'length' in enclosure:
                        try:
                            ee['size'] = int(enclosure['length'])
                        except:
                            ee['size'] = 0
                    if 'type' in enclosure:
                        ee['type'] = enclosure['type']
                    # if enclosure has size OR there are multiple enclosures use filename from url
                    if ee.get('size', 0) != 0 or len(enclosures) > 1:
                        if ee['url'].rfind != -1:
                            # parse filename from enclosure url
                            # TODO: better and perhaps join/in download plugin? also see urlparse module
                            match = re.search('.*\/([^?#]*)', ee['url'])
                            if match and config.get('filename', True):
                                ee['filename'] = match.group(1)
                                log.log(5, 'filename %s from enclosure' % ee['filename'])
                    add_entry(ee)
                if not config.get('group links'):
                    continue

            # create flexget entry
            e = Entry()

            # search for known url fields
            if config.get('group links'):
                e['urls'] = enclosures_urls
                for url_field in config['link']:
                    if url_field in entry:
                        e['urls'].append(entry[url_field])

            for url_field in config['link']:
                if url_field in entry:
                    e['url'] = entry[url_field]
                    break
            else:
                log.debug('%s does not have link (%s) or enclosure' % (entry.title, ', '.join(config['link'])))
                ignored += 1
                continue

            add_entry(e)

        if ignored:
            if not config.get('silent'):
                log.warning('Skipped %s RSS-entries without required information (title, link or enclosures)' % ignored)

register_plugin(InputRSS, 'rss')
<|endoftext|>


<commit_before># coding=utf-8
# lifxlan.py
# Author: Meghan Clark

from random import randint
from socket import AF_INET, SOCK_DGRAM, SOL_SOCKET, SO_BROADCAST, SO_REUSEADDR, socket, timeout
from time import sleep, time

from .device import DEFAULT_ATTEMPTS, DEFAULT_TIMEOUT, Device, UDP_BROADCAST_IP, UDP_BROADCAST_PORT
from .errors import InvalidParameterException, WorkflowException
from .light import Light
from .message import BROADCAST_MAC
from .msgtypes import Acknowledgement, GetService, LightGet, LightGetPower, LightSetColor, LightSetPower, \
    LightSetWaveform, LightState, LightStatePower, StateService
from .multizonelight import MultiZoneLight
from .unpack import unpack_lifx_message

class LifxLAN:
    def __init__(self, num_lights=None, verbose=False):
        self.source_id = randint(0, (2**32)-1)
        self.num_devices = num_lights
        self.num_lights = num_lights
        self.devices = None
        self.lights = None
        self.verbose = verbose

    ############################################################################
    #                                                                          #
    #                         LAN (Broadcast) API Methods                      #
    #                                                                          #
    ############################################################################

    def get_devices(self):
        if self.devices == None:
            self.discover_devices()
        return self.devices

    def get_lights(self):
        if self.lights == None:
            self.discover_devices()
        return self.lights

    # more of an internal helper function
    # forces a refresh of the internal list of available devices
    def discover_devices(self):
        self.lights = []
        self.devices = []
        if self.num_lights == None:
            responses = self.discover()
        else:
            try:
                responses = self.broadcast_with_resp(GetService, StateService)
            except WorkflowException as e:
                raise
                #return None
        for r in responses:
            device = Device(r.target_addr, r.ip_addr, r.service, r.port, self.source_id, self.verbose)
            if device.supports_multizone():
                device = MultiZoneLight(r.target_addr, r.ip_addr, r.service, r.port, self.source_id, self.verbose)
            else:
                device = Light(r.target_addr, r.ip_addr, r.service, r.port, self.source_id, self.verbose)
            self.lights.append(device)
            self.devices.append(device)
        self.num_lights = len(self.lights)
        self.num_devices = len(self.lights)

    def get_multizone_lights(self):
        multizone_lights = []
        all_lights = self.get_lights()
        for l in all_lights:
            if l.supports_multizone():
                multizone_lights.append(l)
        return multizone_lights

    def get_infrared_lights(self):
        infrared_lights = []
        all_lights = self.get_lights()
        for l in all_lights:
            if l.supports_infrared():
                infrared_lights.append(l)
        return infrared_lights

    def get_color_lights(self):
        color_lights = []
        all_lights = self.get_lights()
        for l in all_lights:
            if l.supports_color():
                color_lights.append(l)
        return color_lights

    def get_device_by_name(self, name):
        device = None
        all_devices = self.get_devices()
        for d in all_devices:
            if d.get_label() == name:
                device = d
        if device == None:               # didn't find it?
            self.discover_devices()      # update list in case it is out of date
            all_devices = self.get_devices()
            for d in all_devices:            # and try again
                if d.get_label() == name:
                    device = d
        return device

    # takes in list of strings, returns list of devices
    def get_devices_by_name(self, names):
        devices = []
        all_devices = self.get_devices()
        for d in all_devices:
            if d.get_label() in names:
                devices.append(d)
        if len(devices) != len(names):  # didn't find everything?
            self.discover_devices()     # update list in case it is out of date
            all_devices = self.get_devices()
            for d in all_devices:       # and try again
                if d.get_label() in names:
                    devices.append(d)
        return devices

    def get_devices_by_group(self, group):
        pass

    def get_devices_by_location(self, location):
        pass

    # returns dict of Light: power_level pairs
    def get_power_all_lights(self):
        responses = self.broadcast_with_resp(LightGetPower, LightStatePower)
        power_states = {}
        for light in self.lights:
            for response in responses:
                if light.mac_addr == response.target_addr:
                    power_states[light] = response.power_level
        return power_states

    def set_power_all_lights(self, power_level, duration=0, rapid=False):
        on = [True, 1, "on", 65535]
        off = [False, 0, "off"]
        try:
            if power_level in on and not rapid:
                self.broadcast_with_ack(LightSetPower, {"power_level": 65535, "duration": duration})
            elif power_level in on and rapid:
                self.broadcast_fire_and_forget(LightSetPower, {"power_level": 65535, "duration": duration}, num_repeats=5)
            elif power_level in off and not rapid:
                self.broadcast_with_ack(LightSetPower, {"power_level": 0, "duration": duration})
            elif power_level in off and rapid:
                self.broadcast_fire_and_forget(LightSetPower, {"power_level": 0, "duration": duration}, num_repeats=5)
            else:
                raise InvalidParameterException("{} is not a valid power level.".format(power_level))
        except WorkflowException as e:
            raise

    def get_color_all_lights(self):
        responses = self.broadcast_with_resp(LightGet, LightState)
        colors = {}
        for light in self.lights:
            for response in responses:
                if light.mac_addr == response.target_addr:
                    colors[light] = response.color
        return colors

    def set_color_all_lights(self, color, duration=0, rapid=False):
        if len(color) == 4:
            try:
                if rapid:
                    self.broadcast_fire_and_forget(LightSetColor, {"color": color, "duration": duration}, num_repeats=5)
                else:
                    self.broadcast_with_ack(LightSetColor, {"color": color, "duration": duration})
            except WorkflowException as e:
                raise
        else:
            raise InvalidParameterException("{} is not a valid color.".format(color))

    def set_waveform_all_lights(self, is_transient, color, period, cycles, duty_cycle, waveform, rapid=False):
        if len(color) == 4:
            try:
                if rapid:
                    self.broadcast_fire_and_forget(LightSetWaveform, {"transient": is_transient, "color": color, "period": period, "cycles": cycles, "duty_cycle": duty_cycle, "waveform": waveform}, num_repeats=5)
                else:
                    self.broadcast_with_ack(LightSetWaveform, {"transient": is_transient, "color": color, "period": period, "cycles": cycles, "duty_cycle": duty_cycle, "waveform": waveform})
            except WorkflowException as e:
                raise
        else:
            raise InvalidParameterException("{} is not a valid color.".format(color))

    ############################################################################
    #                                                                          #
    #                            Workflow Methods                              #
    #                                                                          #
    ############################################################################

    def discover(self, timeout_secs=0.3, num_repeats=3):
        self.initialize_socket(timeout_secs)
        msg = GetService(BROADCAST_MAC, self.source_id, seq_num=0, payload={}, ack_requested=False, response_requested=True)
        responses = []
        addr_seen = []
        num_devices_seen = 0
        attempts = 0
        while attempts < num_repeats:
            sent = False
            start_time = time()
            timedout = False
            while not timedout:
                if not sent:
                    self.sock.sendto(msg.packed_message, (UDP_BROADCAST_IP, UDP_BROADCAST_PORT))
                    sent = True
                    if self.verbose:
                        print("SEND: " + str(msg))
                try:
                    data, (ip_addr, port) = self.sock.recvfrom(1024)
                    response = unpack_lifx_message(data)
                    response.ip_addr = ip_addr
                    if self.verbose:
                        print("RECV: " + str(response))
                    if type(response) == StateService and response.source_id == self.source_id:
                        if response.target_addr not in addr_seen and response.target_addr != BROADCAST_MAC:
                            addr_seen.append(response.target_addr)
                            num_devices_seen += 1
                            responses.append(response)
                except timeout:
                    pass
                elapsed_time = time() - start_time
                timedout = True if elapsed_time > timeout_secs else False
            attempts += 1
        self.close_socket()
        return responses

    def broadcast_fire_and_forget(self, msg_type, payload={}, timeout_secs=DEFAULT_TIMEOUT, num_repeats=DEFAULT_ATTEMPTS):
        self.initialize_socket(timeout_secs)
        msg = msg_type(BROADCAST_MAC, self.source_id, seq_num=0, payload=payload, ack_requested=False, response_requested=False)
        sent_msg_count = 0
        sleep_interval = 0.05 if num_repeats > 20 else 0
        while(sent_msg_count < num_repeats):
            self.sock.sendto(msg.packed_message, (UDP_BROADCAST_IP, UDP_BROADCAST_PORT))
            if self.verbose:
                print("SEND: " + str(msg))
            sent_msg_count += 1
            sleep(sleep_interval) # Max num of messages device can handle is 20 per second.
        self.close_socket()

    def broadcast_with_resp(self, msg_type, response_type, payload={}, timeout_secs=DEFAULT_TIMEOUT, max_attempts=DEFAULT_ATTEMPTS):
        if self.lights == None:
            self.get_lights()
        success = False
        self.initialize_socket(timeout_secs)
        if response_type == Acknowledgement:
            msg = msg_type(BROADCAST_MAC, self.source_id, seq_num=0, payload=payload, ack_requested=True, response_requested=False)
        else:
            msg = msg_type(BROADCAST_MAC, self.source_id, seq_num=0, payload=payload, ack_requested=False, response_requested=True)
        responses = []
        addr_seen = []
        num_devices_seen = 0
        attempts = 0
        while num_devices_seen < self.num_devices and attempts < max_attempts:
            sent = False
            start_time = time()
            timedout = False
            while num_devices_seen < self.num_devices and not timedout:
                if not sent:
                    self.sock.sendto(msg.packed_message, (UDP_BROADCAST_IP, UDP_BROADCAST_PORT))
                    sent = True
                    if self.verbose:
                        print("SEND: " + str(msg))
                try:
                    data, (ip_addr, port) = self.sock.recvfrom(1024)
                    response = unpack_lifx_message(data)
                    response.ip_addr = ip_addr
                    if self.verbose:
                        print("RECV: " + str(response))
                    if type(response) == response_type and response.source_id == self.source_id:
                        if response.target_addr not in addr_seen and response.target_addr != BROADCAST_MAC:
                            addr_seen.append(response.target_addr)
                            num_devices_seen += 1
                            responses.append(response)
                            if num_devices_seen >= self.num_devices:
                                success = True
                except timeout:
                    pass
                elapsed_time = time() - start_time
                timedout = True if elapsed_time > timeout_secs else False
            attempts += 1
        if success == False:
            self.close_socket()
            raise WorkflowException("Did not receive {} in response to {}".format(str(response_type), str(msg_type)))
        else:
            self.close_socket()
        return responses

    def broadcast_with_ack(self, msg_type, payload={}, timeout_secs=DEFAULT_TIMEOUT+0.5, max_attempts=DEFAULT_ATTEMPTS):
        self.broadcast_with_resp(msg_type, Acknowledgement, payload, timeout_secs, max_attempts)

    # Not currently implemented, although the LIFX LAN protocol supports this kind of workflow natively
    def broadcast_with_ack_resp(self, msg_type, response_type, payload={}, timeout_secs=DEFAULT_TIMEOUT+0.5, max_attempts=DEFAULT_ATTEMPTS):
        pass

    ############################################################################
    #                                                                          #
    #                              Socket Methods                              #
    #                                                                          #
    ############################################################################

    def initialize_socket(self, timeout):
        self.sock = socket(AF_INET, SOCK_DGRAM)
        self.sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        self.sock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
        self.sock.settimeout(timeout)
        port = UDP_BROADCAST_PORT
        self.sock.bind(("", port))

    def close_socket(self):
        self.sock.close()

def test():
    pass

if __name__=="__main__":
    test()
<commit_msg>implemented first pass of get_devices_by_group and get_devices_by_location functions<commit_after># coding=utf-8
# lifxlan.py
# Author: Meghan Clark

from random import randint
from socket import AF_INET, SOCK_DGRAM, SOL_SOCKET, SO_BROADCAST, SO_REUSEADDR, socket, timeout
from time import sleep, time

from .device import DEFAULT_ATTEMPTS, DEFAULT_TIMEOUT, Device, UDP_BROADCAST_IP, UDP_BROADCAST_PORT
from .errors import InvalidParameterException, WorkflowException
from .light import Light
from .message import BROADCAST_MAC
from .msgtypes import Acknowledgement, GetService, LightGet, LightGetPower, LightSetColor, LightSetPower, \
    LightSetWaveform, LightState, LightStatePower, StateService
from .multizonelight import MultiZoneLight
from .unpack import unpack_lifx_message
from .group import Group

class LifxLAN:
    def __init__(self, num_lights=None, verbose=False):
        self.source_id = randint(0, (2**32)-1)
        self.num_devices = num_lights
        self.num_lights = num_lights
        self.devices = None
        self.lights = None
        self.verbose = verbose

    ############################################################################
    #                                                                          #
    #                         LAN (Broadcast) API Methods                      #
    #                                                                          #
    ############################################################################

    def get_devices(self):
        if self.devices == None:
            self.discover_devices()
        return self.devices

    def get_lights(self):
        if self.lights == None:
            self.discover_devices()
        return self.lights

    # more of an internal helper function
    # forces a refresh of the internal list of available devices
    def discover_devices(self):
        self.lights = []
        self.devices = []
        if self.num_lights == None:
            responses = self.discover()
        else:
            try:
                responses = self.broadcast_with_resp(GetService, StateService)
            except WorkflowException as e:
                raise
                #return None
        for r in responses:
            device = Device(r.target_addr, r.ip_addr, r.service, r.port, self.source_id, self.verbose)
            if device.supports_multizone():
                device = MultiZoneLight(r.target_addr, r.ip_addr, r.service, r.port, self.source_id, self.verbose)
            else:
                device = Light(r.target_addr, r.ip_addr, r.service, r.port, self.source_id, self.verbose)
            self.lights.append(device)
            self.devices.append(device)
        self.num_lights = len(self.lights)
        self.num_devices = len(self.lights)

    def get_multizone_lights(self):
        multizone_lights = []
        all_lights = self.get_lights()
        for l in all_lights:
            if l.supports_multizone():
                multizone_lights.append(l)
        return multizone_lights

    def get_infrared_lights(self):
        infrared_lights = []
        all_lights = self.get_lights()
        for l in all_lights:
            if l.supports_infrared():
                infrared_lights.append(l)
        return infrared_lights

    def get_color_lights(self):
        color_lights = []
        all_lights = self.get_lights()
        for l in all_lights:
            if l.supports_color():
                color_lights.append(l)
        return color_lights

    def get_device_by_name(self, name):
        device = None
        all_devices = self.get_devices()
        for d in all_devices:
            if d.get_label() == name:
                device = d
        if device == None:               # didn't find it?
            self.discover_devices()      # update list in case it is out of date
            all_devices = self.get_devices()
            for d in all_devices:            # and try again
                if d.get_label() == name:
                    device = d
        return device

    # takes in list of strings, returns list of devices
    def get_devices_by_name(self, names):
        devices = []
        all_devices = self.get_devices()
        for d in all_devices:
            if d.get_label() in names:
                devices.append(d)
        if len(devices) != len(names):  # didn't find everything?
            self.discover_devices()     # update list in case it is out of date
            all_devices = self.get_devices()
            for d in all_devices:       # and try again
                if d.get_label() in names:
                    devices.append(d)
        return devices

    def get_devices_by_group(self, group):
        devices = []
        all_devices = self.get_devices()
        for d in all_devices:
            if d.get_group() == group:
                devices.append(d)
        return Group(devices)

    def get_devices_by_location(self, location):
        devices = []
        all_devices = self.get_devices()
        for d in all_devices:
            if d.get_location() == location:
                devices.append(d)
        return Group(devices)

    # returns dict of Light: power_level pairs
    def get_power_all_lights(self):
        responses = self.broadcast_with_resp(LightGetPower, LightStatePower)
        power_states = {}
        for light in self.lights:
            for response in responses:
                if light.mac_addr == response.target_addr:
                    power_states[light] = response.power_level
        return power_states

    def set_power_all_lights(self, power_level, duration=0, rapid=False):
        on = [True, 1, "on", 65535]
        off = [False, 0, "off"]
        try:
            if power_level in on and not rapid:
                self.broadcast_with_ack(LightSetPower, {"power_level": 65535, "duration": duration})
            elif power_level in on and rapid:
                self.broadcast_fire_and_forget(LightSetPower, {"power_level": 65535, "duration": duration}, num_repeats=5)
            elif power_level in off and not rapid:
                self.broadcast_with_ack(LightSetPower, {"power_level": 0, "duration": duration})
            elif power_level in off and rapid:
                self.broadcast_fire_and_forget(LightSetPower, {"power_level": 0, "duration": duration}, num_repeats=5)
            else:
                raise InvalidParameterException("{} is not a valid power level.".format(power_level))
        except WorkflowException as e:
            raise

    def get_color_all_lights(self):
        responses = self.broadcast_with_resp(LightGet, LightState)
        colors = {}
        for light in self.lights:
            for response in responses:
                if light.mac_addr == response.target_addr:
                    colors[light] = response.color
        return colors

    def set_color_all_lights(self, color, duration=0, rapid=False):
        if len(color) == 4:
            try:
                if rapid:
                    self.broadcast_fire_and_forget(LightSetColor, {"color": color, "duration": duration}, num_repeats=5)
                else:
                    self.broadcast_with_ack(LightSetColor, {"color": color, "duration": duration})
            except WorkflowException as e:
                raise
        else:
            raise InvalidParameterException("{} is not a valid color.".format(color))

    def set_waveform_all_lights(self, is_transient, color, period, cycles, duty_cycle, waveform, rapid=False):
        if len(color) == 4:
            try:
                if rapid:
                    self.broadcast_fire_and_forget(LightSetWaveform, {"transient": is_transient, "color": color, "period": period, "cycles": cycles, "duty_cycle": duty_cycle, "waveform": waveform}, num_repeats=5)
                else:
                    self.broadcast_with_ack(LightSetWaveform, {"transient": is_transient, "color": color, "period": period, "cycles": cycles, "duty_cycle": duty_cycle, "waveform": waveform})
            except WorkflowException as e:
                raise
        else:
            raise InvalidParameterException("{} is not a valid color.".format(color))

    ############################################################################
    #                                                                          #
    #                            Workflow Methods                              #
    #                                                                          #
    ############################################################################

    def discover(self, timeout_secs=0.3, num_repeats=3):
        self.initialize_socket(timeout_secs)
        msg = GetService(BROADCAST_MAC, self.source_id, seq_num=0, payload={}, ack_requested=False, response_requested=True)
        responses = []
        addr_seen = []
        num_devices_seen = 0
        attempts = 0
        while attempts < num_repeats:
            sent = False
            start_time = time()
            timedout = False
            while not timedout:
                if not sent:
                    self.sock.sendto(msg.packed_message, (UDP_BROADCAST_IP, UDP_BROADCAST_PORT))
                    sent = True
                    if self.verbose:
                        print("SEND: " + str(msg))
                try:
                    data, (ip_addr, port) = self.sock.recvfrom(1024)
                    response = unpack_lifx_message(data)
                    response.ip_addr = ip_addr
                    if self.verbose:
                        print("RECV: " + str(response))
                    if type(response) == StateService and response.source_id == self.source_id:
                        if response.target_addr not in addr_seen and response.target_addr != BROADCAST_MAC:
                            addr_seen.append(response.target_addr)
                            num_devices_seen += 1
                            responses.append(response)
                except timeout:
                    pass
                elapsed_time = time() - start_time
                timedout = True if elapsed_time > timeout_secs else False
            attempts += 1
        self.close_socket()
        return responses

    def broadcast_fire_and_forget(self, msg_type, payload={}, timeout_secs=DEFAULT_TIMEOUT, num_repeats=DEFAULT_ATTEMPTS):
        self.initialize_socket(timeout_secs)
        msg = msg_type(BROADCAST_MAC, self.source_id, seq_num=0, payload=payload, ack_requested=False, response_requested=False)
        sent_msg_count = 0
        sleep_interval = 0.05 if num_repeats > 20 else 0
        while(sent_msg_count < num_repeats):
            self.sock.sendto(msg.packed_message, (UDP_BROADCAST_IP, UDP_BROADCAST_PORT))
            if self.verbose:
                print("SEND: " + str(msg))
            sent_msg_count += 1
            sleep(sleep_interval) # Max num of messages device can handle is 20 per second.
        self.close_socket()

    def broadcast_with_resp(self, msg_type, response_type, payload={}, timeout_secs=DEFAULT_TIMEOUT, max_attempts=DEFAULT_ATTEMPTS):
        if self.lights == None:
            self.get_lights()
        success = False
        self.initialize_socket(timeout_secs)
        if response_type == Acknowledgement:
            msg = msg_type(BROADCAST_MAC, self.source_id, seq_num=0, payload=payload, ack_requested=True, response_requested=False)
        else:
            msg = msg_type(BROADCAST_MAC, self.source_id, seq_num=0, payload=payload, ack_requested=False, response_requested=True)
        responses = []
        addr_seen = []
        num_devices_seen = 0
        attempts = 0
        while num_devices_seen < self.num_devices and attempts < max_attempts:
            sent = False
            start_time = time()
            timedout = False
            while num_devices_seen < self.num_devices and not timedout:
                if not sent:
                    self.sock.sendto(msg.packed_message, (UDP_BROADCAST_IP, UDP_BROADCAST_PORT))
                    sent = True
                    if self.verbose:
                        print("SEND: " + str(msg))
                try:
                    data, (ip_addr, port) = self.sock.recvfrom(1024)
                    response = unpack_lifx_message(data)
                    response.ip_addr = ip_addr
                    if self.verbose:
                        print("RECV: " + str(response))
                    if type(response) == response_type and response.source_id == self.source_id:
                        if response.target_addr not in addr_seen and response.target_addr != BROADCAST_MAC:
                            addr_seen.append(response.target_addr)
                            num_devices_seen += 1
                            responses.append(response)
                            if num_devices_seen >= self.num_devices:
                                success = True
                except timeout:
                    pass
                elapsed_time = time() - start_time
                timedout = True if elapsed_time > timeout_secs else False
            attempts += 1
        if success == False:
            self.close_socket()
            raise WorkflowException("Did not receive {} in response to {}".format(str(response_type), str(msg_type)))
        else:
            self.close_socket()
        return responses

    def broadcast_with_ack(self, msg_type, payload={}, timeout_secs=DEFAULT_TIMEOUT+0.5, max_attempts=DEFAULT_ATTEMPTS):
        self.broadcast_with_resp(msg_type, Acknowledgement, payload, timeout_secs, max_attempts)

    # Not currently implemented, although the LIFX LAN protocol supports this kind of workflow natively
    def broadcast_with_ack_resp(self, msg_type, response_type, payload={}, timeout_secs=DEFAULT_TIMEOUT+0.5, max_attempts=DEFAULT_ATTEMPTS):
        pass

    ############################################################################
    #                                                                          #
    #                              Socket Methods                              #
    #                                                                          #
    ############################################################################

    def initialize_socket(self, timeout):
        self.sock = socket(AF_INET, SOCK_DGRAM)
        self.sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        self.sock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
        self.sock.settimeout(timeout)
        port = UDP_BROADCAST_PORT
        self.sock.bind(("", port))

    def close_socket(self):
        self.sock.close()

def test():
    pass

if __name__=="__main__":
    test()
<|endoftext|>


<commit_before>from datetime import datetime, timedelta

import re
from wye.base.constants import WorkshopStatus
# from wye.workshops.models import Workshop
from .. import factories as f

def test_workshop_wrong_action(base_url, browser, outbox):
    tutor_type = f.create_usertype(slug='tutor', display_name='tutor')
    poc_type = f.create_usertype(slug='poc', display_name='poc')

    user = f.create_user()
    user.set_password('123123')
    user.save()
    url = base_url + '/workshop/'
    browser.visit(url)
    browser.fill('login', user.email)
    browser.fill('password', '123123')
    browser.find_by_css('[type=submit]')[0].click()
    assert len(outbox) == 1
    mail = outbox[0]
    confirm_link = re.findall(r'http.*/accounts/.*/', mail.body)
    assert confirm_link
    browser.visit(confirm_link[0])
    assert browser.title, "Confirm E-mail Address"
    browser.find_by_css('[type=submit]')[0].click()

    user.profile.usertype.clear()
    user.profile.usertype.add(poc_type)
    user.save()
    org = f.create_organisation()
    org.user.add(user)
    user.profile.interested_locations.add(org.location)
    user.profile.location = org.location
    user.profile.save()
    org.save()

    workshop = f.create_workshop(requester=org)

    workshop.expected_date = datetime.now() + timedelta(days=20)
    workshop.status = WorkshopStatus.REQUESTED
    workshop.location = org.location
    workshop.save()
    url = base_url + '/workshop/update/{}/'.format(workshop.id)
    browser.visit(url)
    browser.fill('login', user.email)
    browser.fill('password', '123123')
    browser.find_by_css('[type=submit]')[0].click()

    section1 = f.create_workshop_section(name='section1')
    location = org.location
    state = f.create_state(name='state2')

    user.profile.usertype.clear()
    user.profile.usertype.add(tutor_type)

    user.profile.location = location
    user.profile.interested_states.add(state)
    user.profile.mobile = '1234567890'
    user.profile.interested_sections.add(section1)
    user.profile.interested_level = 1
    user.profile.github = 'https://github.com'
    user.profile.save()
    user.save()

    url = base_url + '/workshop/' + 'feedback/000/'
    browser.visit(url)

    url = base_url + '/workshop/feedback/{}/'.format(workshop.id)
    browser.visit(url)

def test_workshop_flow(base_url, browser, outbox):
    tutor_type = f.create_usertype(slug='tutor', display_name='tutor')
    poc_type = f.create_usertype(slug='poc', display_name='poc')
    f.create_workshop_rating()
    f.create_workshop_rating()
    f.create_workshop_rating()
    f.create_workshop_rating()
    state1 = f.create_state(name='state1')
    state2 = f.create_state(name='state2')

    user = f.create_user()
    user.set_password('123123')
    user.save()
    url = base_url + '/workshop/'
    browser.visit(url)
    browser.fill('login', user.email)
    browser.fill('password', '123123')
    browser.find_by_css('[type=submit]')[0].click()
    assert len(outbox) == 1
    mail = outbox[0]
    confirm_link = re.findall(r'http.*/accounts/.*/', mail.body)
    assert confirm_link
    browser.visit(confirm_link[0])
    assert browser.title, "Confirm E-mail Address"
    browser.find_by_css('[type=submit]')[0].click()

    user.profile.usertype.clear()
    user.profile.usertype.add(poc_type)
    user.save()
    location = f.create_locaiton(state=state1)
    org = f.create_organisation(location=location)
    org.user.add(user)
    user.profile.interested_locations.add(org.location)
    user.profile.location = org.location
    user.profile.save()
    org.save()

    workshop = f.create_workshop(
        requester=org,
        location=org.location,
        status=WorkshopStatus.REQUESTED)

    workshop.expected_date = datetime.now() + timedelta(days=20)
    workshop.presenter.add(user)
    workshop.save()
    url = base_url + '/workshop/update/{}/'.format(workshop.id)
    browser.visit(url)
    browser.fill('login', user.email)
    browser.fill('password', '123123')
    browser.find_by_css('[type=submit]')[0].click()

    section1 = f.create_workshop_section(name='section1')
    location = org.location

    user.profile.usertype.clear()
    user.profile.usertype.add(tutor_type)

    user.profile.location = location
    user.profile.interested_states.add(state2)
    user.profile.interested_states.add(state1)
    user.profile.mobile = '1234567890'
    user.profile.interested_sections.add(section1)
    user.profile.interested_level = 1
    user.profile.github = 'https://github.com'
    user.profile.save()
    user.save()

    url = base_url + '/workshop/'
    browser.visit(url)

    accept_workshop_link = browser.find_by_text('Accept')[0]
    assert accept_workshop_link
    accept_workshop_link.click()

    user.profile.usertype.clear()
    user.profile.usertype.add(poc_type)
    user.profile.save()
    user.save()

#   checking to move requested workshop in hold state
    url = base_url + '/workshop/'
    browser.visit(url)
    hold_workshop_link = browser.find_by_text('Hold')[0]
    assert hold_workshop_link
    hold_workshop_link.click()

#   checking to move on hold workshop into requested state
    publish_workshop_link = browser.find_by_text('Publish/Request')[0]
    assert publish_workshop_link
    publish_workshop_link.click()

# #   checking declined state
#     browser.visit(url)
#     decline_workshop_link = browser.find_by_text('Decline')[0]
#     assert decline_workshop_link
#     decline_workshop_link.click()

#     workshop.status = WorkshopStatus.HOLD
#     workshop.is_active = True
#     workshop.save()

    browser.visit(url)
    print(browser.html)
    publish_workshop_link = browser.find_by_text('Publish/Request')[0]
    assert publish_workshop_link
    publish_workshop_link.click()

    browser.visit(url)
    accept_workshop_link = browser.find_by_text('Accept')[0]
    assert accept_workshop_link
    accept_workshop_link.click()

    browser.visit(url)
    accept_workshop_link = browser.find_by_text('Reject')[0]
    assert accept_workshop_link
    accept_workshop_link.click()

    browser.visit(url)
    accept_workshop_link = browser.find_by_text('Accept')[0]
    assert accept_workshop_link
    accept_workshop_link.click()

    # print(datetime.now() + timedelta(days=-10))
    workshop.expected_date = datetime.now() + timedelta(days=-60)
    # workshop.status = WorkshopStatus.FEEDBACK_PENDING
    workshop.save()
    # url = base_url + '/workshop/'
    # browser.visit(url)
    # browser.reload()

    browser.visit(url)
    browser.screenshot()
    # print(browser.html)
    # publish_workshop_link = browser.find_by_text('Share Feedback')[0]
    # assert publish_workshop_link
    # publish_workshop_link.click()
    url = base_url + '/workshop/feedback/{}'.format(workshop.id)
    browser.visit(url)
    browser.check('1-1')
    browser.check('3-1')
    browser.fill('comment', "Testing comments")

    browser.find_by_css('[type=submit]')[0].click()
<commit_msg>Testcase Changed for workshop flow<commit_after>from datetime import datetime, timedelta

import re
from wye.base.constants import WorkshopStatus
# from wye.workshops.models import Workshop
from .. import factories as f

def test_workshop_wrong_action(base_url, browser, outbox):
    tutor_type = f.create_usertype(slug='tutor', display_name='tutor')
    poc_type = f.create_usertype(slug='poc', display_name='poc')

    user = f.create_user()
    user.set_password('123123')
    user.save()
    url = base_url + '/workshop/'
    browser.visit(url)
    browser.fill('login', user.email)
    browser.fill('password', '123123')
    browser.find_by_css('[type=submit]')[0].click()
    assert len(outbox) == 1
    mail = outbox[0]
    confirm_link = re.findall(r'http.*/accounts/.*/', mail.body)
    assert confirm_link
    browser.visit(confirm_link[0])
    assert browser.title, "Confirm E-mail Address"
    browser.find_by_css('[type=submit]')[0].click()

    user.profile.usertype.clear()
    user.profile.usertype.add(poc_type)
    user.save()
    org = f.create_organisation()
    org.user.add(user)
    user.profile.interested_locations.add(org.location)
    user.profile.location = org.location
    user.profile.save()
    org.save()

    workshop = f.create_workshop(requester=org)

    workshop.expected_date = datetime.now() + timedelta(days=20)
    workshop.status = WorkshopStatus.REQUESTED
    workshop.location = org.location
    workshop.save()
    url = base_url + '/workshop/update/{}/'.format(workshop.id)
    browser.visit(url)
    browser.fill('login', user.email)
    browser.fill('password', '123123')
    browser.find_by_css('[type=submit]')[0].click()

    section1 = f.create_workshop_section(name='section1')
    location = org.location
    state = f.create_state(name='state2')

    user.profile.usertype.clear()
    user.profile.usertype.add(tutor_type)

    user.profile.location = location
    user.profile.interested_states.add(state)
    user.profile.mobile = '1234567890'
    user.profile.interested_sections.add(section1)
    user.profile.interested_level = 1
    user.profile.github = 'https://github.com'
    user.profile.save()
    user.save()

    url = base_url + '/workshop/' + 'feedback/000/'
    browser.visit(url)

    url = base_url + '/workshop/feedback/{}/'.format(workshop.id)
    browser.visit(url)

def test_workshop_flow(base_url, browser, outbox):
    tutor_type = f.create_usertype(slug='tutor', display_name='tutor')
    poc_type = f.create_usertype(slug='poc', display_name='poc')
    f.create_workshop_rating()
    f.create_workshop_rating()
    f.create_workshop_rating()
    f.create_workshop_rating()
    state1 = f.create_state(name='state1')
    state2 = f.create_state(name='state2')

    user = f.create_user()
    user.set_password('123123')
    user.save()
    url = base_url + '/workshop/'
    browser.visit(url)
    browser.fill('login', user.email)
    browser.fill('password', '123123')
    browser.find_by_css('[type=submit]')[0].click()
    assert len(outbox) == 1
    mail = outbox[0]
    confirm_link = re.findall(r'http.*/accounts/.*/', mail.body)
    assert confirm_link
    browser.visit(confirm_link[0])
    assert browser.title, "Confirm E-mail Address"
    browser.find_by_css('[type=submit]')[0].click()

    user.profile.usertype.clear()
    user.profile.usertype.add(poc_type)
    user.save()
    location = f.create_locaiton(state=state1)
    org = f.create_organisation(location=location)
    org.user.add(user)
    user.profile.interested_locations.add(org.location)
    user.profile.location = org.location
    user.profile.save()
    org.save()

    workshop = f.create_workshop(
        requester=org,
        location=org.location,
        status=WorkshopStatus.REQUESTED)

    workshop.expected_date = datetime.now() + timedelta(days=20)
    workshop.presenter.add(user)
    workshop.save()
    url = base_url + '/workshop/update/{}/'.format(workshop.id)
    browser.visit(url)
    browser.fill('login', user.email)
    browser.fill('password', '123123')
    browser.find_by_css('[type=submit]')[0].click()

    section1 = f.create_workshop_section(name='section1')
    location = org.location

    user.profile.usertype.clear()
    user.profile.usertype.add(tutor_type)

    user.profile.location = location
    user.profile.interested_states.add(state2)
    user.profile.interested_states.add(state1)
    user.profile.mobile = '1234567890'
    user.profile.interested_sections.add(section1)
    user.profile.interested_level = 1
    user.profile.github = 'https://github.com'
    user.profile.save()
    user.save()

    url = base_url + '/workshop/'
    browser.visit(url)

    accept_workshop_link = browser.find_by_text('Accept')[0]
    assert accept_workshop_link
    accept_workshop_link.click()

    user.profile.usertype.clear()
    user.profile.usertype.add(poc_type)
    user.profile.save()
    user.save()

#   checking to move requested workshop in hold state
    url = base_url + '/workshop/'
    browser.visit(url)
    hold_workshop_link = browser.find_by_text('Hold')[0]
    assert hold_workshop_link
    hold_workshop_link.click()

#   checking to move on hold workshop into requested state
    publish_workshop_link = browser.find_by_text('Publish/Request')[0]
    assert publish_workshop_link
    publish_workshop_link.click()
    # hold_workshop_link = browser.find_by_text('Hold')[0]
    # assert hold_workshop_link
    # hold_workshop_link.click()

#   checking declined state
    # browser.visit(url)
    # decline_workshop_link = browser.find_by_text('Decline')[0]
    # assert decline_workshop_link
    # decline_workshop_link.click()

    browser.visit(url)
    hold_workshop_link = browser.find_by_text('Hold')[0]
    assert hold_workshop_link
    hold_workshop_link.click()

    browser.visit(url)
    publish_workshop_link = browser.find_by_text('Publish/Request')[0]
    assert publish_workshop_link
    publish_workshop_link.click()

    # hold_workshop_link = browser.find_by_text('Hold')[0]
    # assert hold_workshop_link
    # hold_workshop_link.click()

    browser.visit(url)
    accept_workshop_link = browser.find_by_text('Accept')[0]
    assert accept_workshop_link
    accept_workshop_link.click()

    # print(datetime.now() + timedelta(days=-10))
    workshop.expected_date = datetime.now() + timedelta(days=-60)
    # workshop.status = WorkshopStatus.FEEDBACK_PENDING
    workshop.save()
    # url = base_url + '/workshop/'
    # browser.visit(url)
    # browser.reload()

    browser.visit(url)
    browser.screenshot()
    # print(browser.html)
    # publish_workshop_link = browser.find_by_text('Share Feedback')[0]
    # assert publish_workshop_link
    # publish_workshop_link.click()
    url = base_url + '/workshop/feedback/{}'.format(workshop.id)
    browser.visit(url)
    browser.check('1-1')
    browser.check('3-1')
    browser.fill('comment', "Testing comments")

    browser.find_by_css('[type=submit]')[0].click()<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
'''
Tests for the fileserver runner
'''
# Import Python libs
from __future__ import absolute_import
import contextlib

# Import Salt Testing libs
from salttesting.helpers import ensure_in_syspath
ensure_in_syspath('../../')

# Import salt libs
import integration

class FileserverTest(integration.ShellCase):
    '''
    Test the fileserver runner
    '''
    def test_dir_list(self):
        '''
        fileserver.dir_list
        '''
        ret = self.run_run_plus(fun='fileserver.dir_list')
        self.assertIsInstance(ret['return'], list)
        self.assertTrue('_modules' in ret['return'])

        # Backend submitted as a string
        ret = self.run_run_plus(fun='fileserver.dir_list', backend='roots')
        self.assertIsInstance(ret['return'], list)
        self.assertTrue('_modules' in ret['return'])

        # Backend submitted as a list
        ret = self.run_run_plus(fun='fileserver.dir_list', backend=['roots'])
        self.assertIsInstance(ret['return'], list)
        self.assertTrue('_modules' in ret['return'])

    def test_empty_dir_list(self):
        '''
        fileserver.empty_dir_list
        '''
        ret = self.run_run_plus(fun='fileserver.empty_dir_list')
        self.assertIsInstance(ret['return'], list)
        self.assertEqual(ret['return'], [])

        # Backend submitted as a string
        ret = self.run_run_plus(
            fun='fileserver.empty_dir_list',
            backend='roots')
        self.assertIsInstance(ret['return'], list)
        self.assertEqual(ret['return'], [])

        # Backend submitted as a list
        ret = self.run_run_plus(
            fun='fileserver.empty_dir_list',
            backend=['roots'])
        self.assertIsInstance(ret['return'], list)
        self.assertEqual(ret['return'], [])

    def test_envs(self):
        '''
        fileserver.envs
        '''
        ret = self.run_run_plus(fun='fileserver.envs')
        self.assertIsInstance(ret['return'], list)

        # Backend submitted as a string
        ret = self.run_run_plus(fun='fileserver.envs', backend='roots')
        self.assertIsInstance(ret['return'], list)

        # Backend submitted as a list
        ret = self.run_run_plus(fun='fileserver.envs', backend=['roots'])
        self.assertIsInstance(ret['return'], list)

    def test_clear_file_list_cache(self):
        '''
        fileserver.clear_file_list_cache

        If this test fails, then something may have changed in the test suite
        and we may have more than just "roots" configured in the fileserver
        backends. This assert will need to be updated accordingly.
        '''
        @contextlib.contextmanager
        def gen_cache():
            '''
            Create file_list cache so we have something to clear
            '''
            self.run_run_plus(fun='fileserver.file_list')
            yield

        # Test with no arguments
        with gen_cache():
            ret = self.run_run_plus(fun='fileserver.clear_file_list_cache')
            self.assertEqual(ret['return'], {'roots': ['base']})

        # Test with backend passed as string
        with gen_cache():
            ret = self.run_run_plus(fun='fileserver.clear_file_list_cache',
                                    backend='roots')
            self.assertEqual(ret['return'], {'roots': ['base']})

        # Test with backend passed as list
        with gen_cache():
            ret = self.run_run_plus(fun='fileserver.clear_file_list_cache',
                                    backend=['roots'])
            self.assertEqual(ret['return'], {'roots': ['base']})

        # Test with backend passed as string, but with a nonsense backend
        with gen_cache():
            ret = self.run_run_plus(fun='fileserver.clear_file_list_cache',
                                    backend='notarealbackend')
            self.assertEqual(ret['return'], {})

        # Test with saltenv passed as string
        with gen_cache():
            ret = self.run_run_plus(fun='fileserver.clear_file_list_cache',
                                    saltenv='base')
            self.assertEqual(ret['return'], {'roots': ['base']})

        # Test with saltenv passed as list
        with gen_cache():
            ret = self.run_run_plus(fun='fileserver.clear_file_list_cache',
                                    saltenv=['base'])
            self.assertEqual(ret['return'], {'roots': ['base']})

        # Test with saltenv passed as string, but with a nonsense saltenv
        with gen_cache():
            ret = self.run_run_plus(fun='fileserver.clear_file_list_cache',
                                    saltenv='notarealsaltenv')
            self.assertEqual(ret['return'], {})

        # Test with both backend and saltenv passed
        with gen_cache():
            ret = self.run_run_plus(fun='fileserver.clear_file_list_cache',
                                    backend='roots',
                                    saltenv='base')
            self.assertEqual(ret['return'], {'roots': ['base']})

    def test_file_list(self):
        '''
        fileserver.file_list
        '''
        ret = self.run_run_plus(fun='fileserver.file_list')
        self.assertIsInstance(ret['return'], list)
        self.assertTrue('grail/scene33' in ret['return'])

        # Backend submitted as a string
        ret = self.run_run_plus(fun='fileserver.file_list', backend='roots')
        self.assertIsInstance(ret['return'], list)
        self.assertTrue('grail/scene33' in ret['return'])

        # Backend submitted as a list
        ret = self.run_run_plus(fun='fileserver.file_list', backend=['roots'])
        self.assertIsInstance(ret['return'], list)
        self.assertTrue('grail/scene33' in ret['return'])

    def test_symlink_list(self):
        '''
        fileserver.symlink_list
        '''
        ret = self.run_run_plus(fun='fileserver.symlink_list')
        self.assertIsInstance(ret['return'], dict)
        self.assertTrue('dest_sym' in ret['return'])

        # Backend submitted as a string
        ret = self.run_run_plus(fun='fileserver.symlink_list', backend='roots')
        self.assertIsInstance(ret['return'], dict)
        self.assertTrue('dest_sym' in ret['return'])

        # Backend submitted as a list
        ret = self.run_run_plus(fun='fileserver.symlink_list', backend=['roots'])
        self.assertIsInstance(ret['return'], dict)
        self.assertTrue('dest_sym' in ret['return'])

    def test_update(self):
        '''
        fileserver.update
        '''
        ret = self.run_run_plus(fun='fileserver.update')
        self.assertTrue(ret['return'])

        # Backend submitted as a string
        ret = self.run_run_plus(fun='fileserver.update', backend='roots')
        self.assertTrue(ret['return'])

        # Backend submitted as a list
        ret = self.run_run_plus(fun='fileserver.update', backend=['roots'])
        self.assertTrue(ret['return'])

if __name__ == '__main__':
    from integration import run_tests
    run_tests(FileserverTest)
<commit_msg>Fix for new integration test for fileserver.clear_file_list_cache<commit_after># -*- coding: utf-8 -*-
'''
Tests for the fileserver runner
'''
# Import Python libs
from __future__ import absolute_import
import contextlib

# Import Salt Testing libs
from salttesting.helpers import ensure_in_syspath
ensure_in_syspath('../../')

# Import salt libs
import integration

class FileserverTest(integration.ShellCase):
    '''
    Test the fileserver runner
    '''
    def test_dir_list(self):
        '''
        fileserver.dir_list
        '''
        ret = self.run_run_plus(fun='fileserver.dir_list')
        self.assertIsInstance(ret['return'], list)
        self.assertTrue('_modules' in ret['return'])

        # Backend submitted as a string
        ret = self.run_run_plus(fun='fileserver.dir_list', backend='roots')
        self.assertIsInstance(ret['return'], list)
        self.assertTrue('_modules' in ret['return'])

        # Backend submitted as a list
        ret = self.run_run_plus(fun='fileserver.dir_list', backend=['roots'])
        self.assertIsInstance(ret['return'], list)
        self.assertTrue('_modules' in ret['return'])

    def test_empty_dir_list(self):
        '''
        fileserver.empty_dir_list
        '''
        ret = self.run_run_plus(fun='fileserver.empty_dir_list')
        self.assertIsInstance(ret['return'], list)
        self.assertEqual(ret['return'], [])

        # Backend submitted as a string
        ret = self.run_run_plus(
            fun='fileserver.empty_dir_list',
            backend='roots')
        self.assertIsInstance(ret['return'], list)
        self.assertEqual(ret['return'], [])

        # Backend submitted as a list
        ret = self.run_run_plus(
            fun='fileserver.empty_dir_list',
            backend=['roots'])
        self.assertIsInstance(ret['return'], list)
        self.assertEqual(ret['return'], [])

    def test_envs(self):
        '''
        fileserver.envs
        '''
        ret = self.run_run_plus(fun='fileserver.envs')
        self.assertIsInstance(ret['return'], list)

        # Backend submitted as a string
        ret = self.run_run_plus(fun='fileserver.envs', backend='roots')
        self.assertIsInstance(ret['return'], list)

        # Backend submitted as a list
        ret = self.run_run_plus(fun='fileserver.envs', backend=['roots'])
        self.assertIsInstance(ret['return'], list)

    def test_clear_file_list_cache(self):
        '''
        fileserver.clear_file_list_cache

        If this test fails, then something may have changed in the test suite
        and we may have more than just "roots" configured in the fileserver
        backends. This assert will need to be updated accordingly.
        '''
        saltenvs = sorted(self.run_run_plus(fun='fileserver.envs')['return'])

        @contextlib.contextmanager
        def gen_cache():
            '''
            Create file_list cache so we have something to clear
            '''
            for saltenv in saltenvs:
                self.run_run_plus(fun='fileserver.file_list', saltenv=saltenv)
            yield

        # Test with no arguments
        with gen_cache():
            ret = self.run_run_plus(fun='fileserver.clear_file_list_cache')
            ret['return']['roots'].sort()
            self.assertEqual(ret['return'], {'roots': saltenvs})

        # Test with backend passed as string
        with gen_cache():
            ret = self.run_run_plus(fun='fileserver.clear_file_list_cache',
                                    backend='roots')
            ret['return']['roots'].sort()
            self.assertEqual(ret['return'], {'roots': saltenvs})

        # Test with backend passed as list
        with gen_cache():
            ret = self.run_run_plus(fun='fileserver.clear_file_list_cache',
                                    backend=['roots'])
            ret['return']['roots'].sort()
            self.assertEqual(ret['return'], {'roots': saltenvs})

        # Test with backend passed as string, but with a nonsense backend
        with gen_cache():
            ret = self.run_run_plus(fun='fileserver.clear_file_list_cache',
                                    backend='notarealbackend')
            self.assertEqual(ret['return'], {})

        # Test with saltenv passed as string
        with gen_cache():
            ret = self.run_run_plus(fun='fileserver.clear_file_list_cache',
                                    saltenv='base')
            self.assertEqual(ret['return'], {'roots': ['base']})

        # Test with saltenv passed as list
        with gen_cache():
            ret = self.run_run_plus(fun='fileserver.clear_file_list_cache',
                                    saltenv=['base'])
            self.assertEqual(ret['return'], {'roots': ['base']})

        # Test with saltenv passed as string, but with a nonsense saltenv
        with gen_cache():
            ret = self.run_run_plus(fun='fileserver.clear_file_list_cache',
                                    saltenv='notarealsaltenv')
            self.assertEqual(ret['return'], {})

        # Test with both backend and saltenv passed
        with gen_cache():
            ret = self.run_run_plus(fun='fileserver.clear_file_list_cache',
                                    backend='roots',
                                    saltenv='base')
            self.assertEqual(ret['return'], {'roots': ['base']})

    def test_file_list(self):
        '''
        fileserver.file_list
        '''
        ret = self.run_run_plus(fun='fileserver.file_list')
        self.assertIsInstance(ret['return'], list)
        self.assertTrue('grail/scene33' in ret['return'])

        # Backend submitted as a string
        ret = self.run_run_plus(fun='fileserver.file_list', backend='roots')
        self.assertIsInstance(ret['return'], list)
        self.assertTrue('grail/scene33' in ret['return'])

        # Backend submitted as a list
        ret = self.run_run_plus(fun='fileserver.file_list', backend=['roots'])
        self.assertIsInstance(ret['return'], list)
        self.assertTrue('grail/scene33' in ret['return'])

    def test_symlink_list(self):
        '''
        fileserver.symlink_list
        '''
        ret = self.run_run_plus(fun='fileserver.symlink_list')
        self.assertIsInstance(ret['return'], dict)
        self.assertTrue('dest_sym' in ret['return'])

        # Backend submitted as a string
        ret = self.run_run_plus(fun='fileserver.symlink_list', backend='roots')
        self.assertIsInstance(ret['return'], dict)
        self.assertTrue('dest_sym' in ret['return'])

        # Backend submitted as a list
        ret = self.run_run_plus(fun='fileserver.symlink_list', backend=['roots'])
        self.assertIsInstance(ret['return'], dict)
        self.assertTrue('dest_sym' in ret['return'])

    def test_update(self):
        '''
        fileserver.update
        '''
        ret = self.run_run_plus(fun='fileserver.update')
        self.assertTrue(ret['return'])

        # Backend submitted as a string
        ret = self.run_run_plus(fun='fileserver.update', backend='roots')
        self.assertTrue(ret['return'])

        # Backend submitted as a list
        ret = self.run_run_plus(fun='fileserver.update', backend=['roots'])
        self.assertTrue(ret['return'])

if __name__ == '__main__':
    from integration import run_tests
    run_tests(FileserverTest)
<|endoftext|>


<commit_before>import re

MIDI_NOTE_VALUES = {
    "c": 0,
    "d": 2,
    "e": 4,
    "f": 5,
    "g": 7,
    "a": 9,
    "b": 11,
}

token_pattern = re.compile(r"""^\s*                 # INITIAL WHITESPACE
    (
        (                                           # NOTE
            (
                (?P<note>[abcdefg])                     # NOTE NAME
                (?P<octave>'+|,+) ?                     # OCTAVE ?
                ((?P<sharp>(is)+)|(?P<flat>(es)+)) ?    # ACCIDENTALS ?
                |                                       # |
                (?P<rest>r)                             # REST
            )
            (?P<duration>\d+\.*) ?                      # DURATION ?
        )
        |
        (?P<tie>~)                                  # TIE
        |
        \\(?P<command>(relative))                   # COMMANDS
        |
        (?P<open_brace>{) | (?P<close_brace>})      # { or }
    )
    """,
    re.VERBOSE
)

def tokenize(s):
    while True:
        if s:
            m = token_pattern.match(s)
            if m:
                yield m.groupdict()
            else:
                raise Exception("unknown token at: '%s'" % s[:20])
            s = s[m.end():]
        else:
            raise StopIteration

def parse_duration(duration_marker):
    if "." in duration_marker:
        first_dot = duration_marker.find(".")
        core = int(duration_marker[:first_dot])
        # this doesn't actually check they are all dots, but regex wouldn't
        # match in the first place otherwise
        dots = len(duration_marker[first_dot:])
    else:
        core = int(duration_marker)
        dots = 0

    duration = (2 - (2**-dots)) * 64 / core

    return duration

def parse(s):
    duration = 16
    curr_octave = 4
    offset = 0

    for token_dict in tokenize(s):
        command = token_dict["command"]
        open_brace = token_dict["open_brace"]
        close_brace = token_dict["close_brace"]
        tie = token_dict["tie"]

        if command:
            pass # @@@ NYI
        elif open_brace:
            pass # @@@ NYI
        elif close_brace:
            pass # @@@ NYI
        elif tie:
            pass # @@@ NYI
        else:
            note = token_dict["note"]
            octave_marker = token_dict["octave"]
            duration_marker = token_dict["duration"]
            accidental_sharp = token_dict["sharp"]
            accidental_flat = token_dict["flat"]
            rest = token_dict["rest"]
            accidental_change = 0

            if duration_marker is None:
                pass # leave duration the way it was
            else:
                duration = parse_duration(duration_marker)
            if not rest:
                if octave_marker is None:
                    octave = curr_octave
                elif "'" in octave_marker:
                    octave = curr_octave + len(octave_marker)
                elif "," in octave_marker:
                    octave = curr_octave - len(octave_marker)
                if accidental_sharp:
                    accidental_change += len(accidental_sharp) / 2
                if accidental_flat:
                    accidental_change -= len(accidental_flat) / 2

                note_value = MIDI_NOTE_VALUES[note] + (12 * octave) + accidental_change

                yield (offset, note_value, duration)
            offset += duration
<commit_msg>implemented ties by make tie part of previous note token and deferring that note<commit_after>import re

MIDI_NOTE_VALUES = {
    "c": 0,
    "d": 2,
    "e": 4,
    "f": 5,
    "g": 7,
    "a": 9,
    "b": 11,
}

token_pattern = re.compile(r"""^\s*                 # INITIAL WHITESPACE
    (
        (                                           # NOTE
            (
                (?P<note>[abcdefg])                     # NOTE NAME
                (?P<octave>'+|,+) ?                     # OCTAVE ?
                ((?P<sharp>(is)+)|(?P<flat>(es)+)) ?    # ACCIDENTALS ?
                |                                       # |
                (?P<rest>r)                             # REST
            )
            (?P<duration>\d+\.*) ?                      # DURATION ?
            (\s*(?P<tie>~)) ?                           # TIE ?
        )
        |
        \\(?P<command>(relative))                   # COMMANDS
        |
        (?P<open_brace>{) | (?P<close_brace>})      # { or }
    )
    """,
    re.VERBOSE
)

def tokenize(s):
    while True:
        if s:
            m = token_pattern.match(s)
            if m:
                yield m.groupdict()
            else:
                raise Exception("unknown token at: '%s'" % s[:20])
            s = s[m.end():]
        else:
            raise StopIteration

def parse_duration(duration_marker):
    if "." in duration_marker:
        first_dot = duration_marker.find(".")
        core = int(duration_marker[:first_dot])
        # this doesn't actually check they are all dots, but regex wouldn't
        # match in the first place otherwise
        dots = len(duration_marker[first_dot:])
    else:
        core = int(duration_marker)
        dots = 0

    duration = (2 - (2**-dots)) * 64 / core

    return duration

def parse(s):
    duration = 16
    curr_octave = 4
    offset = 0
    tie_deferred_note = None

    for token_dict in tokenize(s):
        command = token_dict["command"]
        open_brace = token_dict["open_brace"]
        close_brace = token_dict["close_brace"]

        if command:
            pass # @@@ NYI
        elif open_brace:
            pass # @@@ NYI
        elif close_brace:
            pass # @@@ NYI
        else:
            note = token_dict["note"]
            octave_marker = token_dict["octave"]
            duration_marker = token_dict["duration"]
            accidental_sharp = token_dict["sharp"]
            accidental_flat = token_dict["flat"]
            rest = token_dict["rest"]
            tie = token_dict["tie"]
            accidental_change = 0

            if duration_marker is None:
                pass # leave duration the way it was
            else:
                duration = parse_duration(duration_marker)

            if not rest:
                if octave_marker is None:
                    octave = curr_octave
                elif "'" in octave_marker:
                    octave = curr_octave + len(octave_marker)
                elif "," in octave_marker:
                    octave = curr_octave - len(octave_marker)
                if accidental_sharp:
                    accidental_change += len(accidental_sharp) / 2
                if accidental_flat:
                    accidental_change -= len(accidental_flat) / 2

                note_value = MIDI_NOTE_VALUES[note] + (12 * octave) + accidental_change

                if tie_deferred_note:
                    # if the previous note was deferred due to a tie
                    if note_value != tie_deferred_note[0]:
                        raise Exception("ties are only supported between notes of same pitch")
                    duration += tie_deferred_note[1]
                    tie_deferred_note = None

                if tie:
                    # if there is a tie following this note, we defer it
                    tie_deferred_note = (note_value, duration)
                else:
                    yield (offset, note_value, duration)

            if not tie_deferred_note:
                offset += duration
<|endoftext|>