<commit_before>__author__ = 'gremorian'

import sys
import time
from datetime import date, datetime
from .types import OrientRecordLink, OrientRecord, OrientBinaryObject

class ORecordDecoder(object):

    def __init__(self, content):
        self.className = None
        self.data = {}

        if not isinstance(content, str):
            content = content.decode()
        self.__decode( content )

    def __decode( self, content ):
        """
         Deserialize a record.
           :param content str The input to un-serialize.
           :return: dict|None The un-serialized document, or None if the input is empty.
        """
        if not content:
            return

        content = content.strip()

        chunk = self.parse_first_key( content )

        if chunk[2]:
            # this is actually a class name.
            self.className = chunk[0]
            content = chunk[1]
            chunk = self.parse_key(content)
            key = chunk[0]
            content = chunk[1]
        else:
            key = chunk[0]
            content = chunk[1]

        if not key and not content:
            return

        chunk = self.parse_value(content)
        value = chunk[0]
        content = chunk[1]

        self.data[key] = value

        while len(content) != 0:
            if content[0] == ',':
                content = content[1:]
            else:
                break

            chunk = self.parse_key(content)
            key = chunk[0]
            content = chunk[1]
            if len(content) > 0:
                chunk = self.parse_value(content)
                value = chunk[0]
                content = chunk[1]
                self.data[key] = value
            else:
                self.data[key] = None

    def parse_first_key(self, content):
        """
         Consume the first field key, which could be a class name.
              :param content str The input to consume
              :return: list The collected string and any remaining content,
               followed by a boolean indicating whether this is a class name.
        """
        length = len(content)
        collected = ''
        is_class_name = False
        if content[0] == '"':
            result = self.parse_string(content[1:])
            return [result[0], result[1][1:]]

        i = 0
        for i in range(0, length):
            c = content[i]
            if c == '@':
                is_class_name = True
                break
            elif c == ':':
                break
            else:
                collected += c

        return [collected, content[( i + 1 ):], is_class_name]

    def parse_key( self, content ):
        """
          Consume a field key, which may or may not be quoted.
            :param content str The input to consume
            :return: dict The collected string and any remaining content.
        """
        length = len(content)
        collected = ''
        if content[ 0 ] == '"':
            result = self.parse_string( content[1:] )
            return [ result[ 0 ], result[1][1:] ]

        i = 0
        for i in range(0, length):
            c = content[i]
            if c == ':':
                break
            else:
                collected += c

        return [ collected, content[( i + 1 ):] ]

    def parse_value( self, content ):
        """
          Consume a field value.
            :param: content str The input to consume
            :return: list The collected value and any remaining content.
        """
        c = ''
        content = content.lstrip( " " )
        try:
            c = content[ 0 ]  # string index out of range 0
        except IndexError:
            pass

        if len( content ) == 0 or c == ',':
            return [ None, content ]
        elif c == '"':
            return self.parse_string( content[1:] )
        elif c == '#':
            return self.parse_rid( content[1:] )
        elif c == '[':
            return self.parse_collection( content[1:] )
        elif c == '<':
            return self.parse_set( content[1:] )
        elif c == '{':
            return self.parse_map( content[1:] )
        elif c == '(':
            return self.parse_record( content[1:] )
        elif c == '%':
            return self.parse_bag( content[1:] )
        elif c == '_':
            return self.parse_binary( content[1:] )
        elif c == '-' or self.is_numeric( c ):
            return self.parse_number( content )
        elif c == 'n' and content[ 0:4 ] == 'null':
            return [ None, content[ 4: ] ]
        elif c == 't' and content[ 0:4 ] == 'true':
            return [ True, content[ 4: ] ]
        elif c == 'f' and content[ 0:5 ] == 'false':
            return [ False, content[ 5: ] ]
        else:
            return [ None, content ]

    @staticmethod
    def is_numeric( content ):
        try:
            float( content )
            return True
        except ValueError:
            return False

    @staticmethod
    def parse_string( content ):
        """
          Consume a string.
            :param content str The input to consume
            :return: list The collected string and any remaining content.
        """
        length = len( content )
        collected = ''
        i = 0
        while i < length:
            c = content[ i ]
            if c == '\\':
                # escape, skip to the next character
                i += 1
                collected += content[ i ]
                # increment again to pass over
                i += 1
                continue
            elif c == '"':
                break
            else:
                i += 1
                collected += c

        return [ collected, content[ ( i + 1 ): ] ]

    def parse_number(self, content):
        """
           Consume a number.
           If the number has a suffix, consume it also and instantiate the
            right type, e.g. for dates
           :param content str The content to consume
           :return: list The collected number and any remaining content.
        """
        length = len(content)
        collected = ''
        is_float = False
        i = 0
        for i in range(0, length):
            c = content[i]
            if c == '-' or self.is_numeric(c):
                collected += c
            elif c == '.':
                is_float = True
                collected += c
            elif c == 'E' and is_float:
                collected += c
            else:
                break

        content = content[i:]
        c = ''
        try:
            c = content[ 0 ]  # string index out of range 0
        except IndexError:
            pass

        if c == 'a':
            collected = date.fromtimestamp(float(collected) / 1000)
            content = content[1:]
        elif c == 't':
            # date
            collected = datetime.fromtimestamp(float(collected) / 1000)
            content = content[1:]
        elif c == 'f' or c == 'c' or c == 'd':
            # float # decimal (signed32 bit floating point) # double
            collected = float(collected)
            content = content[1:]
        elif c == 'b' or c == 's':
            collected = int(collected)
            content = content[1:]
        elif c == 'l':
            if sys.version_info[0] < 3:
                collected = long(collected)  # python 2.x long type
            else:
                collected = int(collected)
            content = content[1:]
        elif is_float:
            collected = float(collected)
        else:
            collected = int(collected)

        return [collected, content]

    def parse_rid(self, content):
        """
          Consume a Record ID.
          :param content str The input to consume
          :return: list The collected RID and any remaining content.
        """
        length = len(content)
        collected = ''
        cluster = None
        i = 0
        for i in range(0, length):
            c = content[i]
            if cluster is None and c == ':':
                cluster = collected
                collected = ''
            elif self.is_numeric(c):
                collected += c
            else:
                break

        return [ OrientRecordLink( cluster + ":" + collected ), content[i:]]

    def parse_collection(self, content):
        """
            Consume an array of values.
            :param content str The input to consume
            :return: list The collected array and any remaining content.
        """
        collection = []
        while len(content) != 0:
            c = content[0]
            if c == ',':
                content = content[1:]
            elif c == ']':
                content = content[1:]
                break

            chunk = self.parse_value(content)
            collection.append(chunk[0])
            content = chunk[1]

        return [collection, content]

    def parse_set(self, content):
        """
           Consume a set of values.
           :param content str The input to consume
           :return: list The collected set and any remaining content.
        """
        list_set = []
        while len(content) != 0:
            c = content[0]
            if c == ',':
                content = content[1:]
            elif c == '>':
                content = content[1:]
                break

            chunk = self.parse_value(content)
            list_set.append(chunk[0])
            content = chunk[1]

        return [list_set, content]

    def parse_map( self, content ):
        """
            Consume a map of keys to values.
            :param content str The input to consume
            :return: list The collected map and any remaining content.
        """
        _map = {}
        content = content.lstrip(' ')
        while len(content) != 0:
            c = content[0]
            if c == ' ':
                content = content[1:].lstrip(' ')
                continue
            elif c == ',':
                content = content[1:].lstrip(' ')
            elif c == '}':
                content = content[1:]
                break

            chunk = self.parse_key(content)
            key = chunk[0]
            content = chunk[1].lstrip(' ')
            if len(content) != 0:
                chunk = self.parse_value(content)
                _map[key] = chunk[0]
                content = chunk[1].lstrip(' ')
            else:
                _map[key] = None
                break

        return [_map, content]

    def parse_record(self, content):
        """
          Consume an embedded record.
           :param content str The content to unserialize.
           :return: list The collected record and any remaining content.
        """
        record = {}

        content = content.lstrip(' ')
        if content[0] == ')':
            # this is an empty record.
            return [record, content[1:]]

        chunk = self.parse_first_key(content)
        if chunk[2]:
            # this is actually a class name.
            record['o_class'] = chunk[0]
            content = chunk[1].lstrip(' ')
            if content[0] == ')':
                return [record, content[1:]]

            chunk = self.parse_key(content)
            key = chunk[0]
            content = chunk[1]
        else:
            key = chunk[0]
            content = chunk[1]

        chunk = self.parse_key(content)
        value = chunk[0]
        content = chunk[1].lstrip(' ')

        record[key] = value

        while len(content) > 0:
            if content[0] == ',':
                content = content[1:].lstrip(' ')
            elif content[0] == ')':
                content = content[1:].lstrip(' ')
                break

            chunk = self.parse_key(content)
            key = chunk[0]
            content = chunk[1].lstrip(' ')
            if len(content) > 0:
                chunk = self.parse_value(content)
                value = chunk[0]
                content = chunk[1]
                record[key] = value
            else:
                record[key] = None

        return [record, content]

    @staticmethod
    def parse_bag(content):
        """
         Consume a record id bag.
           :param content str The content to consume
           :return: list The collected record id bag and any remaining content.
        """
        length = len(content)
        collected = ''
        i = 0
        for i in range(0, length):
            c = content[i]
            if c == ';':
                break
            else:
                collected += c

        return [OrientBinaryObject(collected), content[( i + 1 ):]]

    @staticmethod
    def parse_binary(content):
        """
          Consume a binary field.
            :param content str The content to consume
            :return: list The collected binary and any remaining content.
        """
        length = len(content)
        collected = ''
        i = 0
        for i in range(0, length):
            c = content[i]
            if c == '_' \
                    or c == ',' \
                    or c == ')' \
                    or c == '>' \
                    or c == '}' \
                    or c == ']':
                break
            else:
                collected += c

        return [collected, content[( i + 1 ):]]

class ORecordEncoder(object):
    """docstring for ORecordEncoder"""

    def __init__(self, oRecord):
        self._raw = self.__encode(oRecord)

    def __encode(self, record):

        raw = ''
        o_class = getattr(record, 'o_class', False)
        if o_class:
            raw = o_class + '@'

        fields = list(record.oRecordData)

        for idx, key in enumerate(fields):
            raw += key + ':'
            value = record.oRecordData[key]
            raw += self.parse_value(value)

            if idx < len(list(fields)) - 1:
                # not last element
                raw += ','

        return raw

    def parse_value(self, value):

        if isinstance(value, str):
            ret = '"' + value + '"'
        elif isinstance(value, float):
            ret = str(value) + 'f'

        elif sys.version_info[0] >= 3 and isinstance(value, int):
            if value > 2147483647:
                ret = str(value) + 'l'
            else:
                ret = str(value)

        elif sys.version_info[0] < 3 and isinstance(value, long):
            ret = str(value) + 'l'
        elif isinstance(value, int):
            ret = str(value)

        elif isinstance(value, datetime):
            ret = str(int(time.mktime(value.timetuple())) * 1000) + 't'
        elif isinstance(value, date):
            ret = str(int(time.mktime(value.timetuple())) * 1000) + 'a'
        elif isinstance(value, list):
            try:
                ret = "[" + ','.join(
                    map(
                        lambda elem: self.parse_value(type(value[0])(elem))
                        if not isinstance(value[0], OrientRecordLink)
                        else elem.get_hash(),
                        value
                    )) + ']'
            except ValueError as e:
                raise Exception("wrong type commistion")
        elif isinstance(value, dict):
            ret = "{" + ','.join(map(
                lambda elem: '"' + elem + '":' + self.parse_value(value[elem]),
                value)) + '}'
        elif isinstance(value, OrientRecord):
            ret = "(" + self.__encode(value) + ")"
        elif isinstance(value, OrientRecordLink):
            ret = value.get_hash()
        elif isinstance(value, OrientBinaryObject):
            ret = value.getRaw()
        else:
            ret = ''
        return ret

    def get_raw(self):
        return self._raw
<commit_msg>This fixes selecting nodes without keys.<commit_after>__author__ = 'gremorian'

import sys
import time
from datetime import date, datetime
from .types import OrientRecordLink, OrientRecord, OrientBinaryObject

class ORecordDecoder(object):

    def __init__(self, content):
        self.className = None
        self.data = {}

        if not isinstance(content, str):
            content = content.decode()
        self.__decode( content )

    def __decode( self, content ):
        """
         Deserialize a record.
           :param content str The input to un-serialize.
           :return: dict|None The un-serialized document, or None if the input is empty.
        """
        if not content:
            return

        content = content.strip()

        chunk = self.parse_first_key( content )

        if chunk[2]:
            # this is actually a class name.
            self.className = chunk[0]
            content = chunk[1]
            chunk = self.parse_key(content)
            key = chunk[0]
            content = chunk[1]
        else:
            key = chunk[0]
            content = chunk[1]

        if not key and not content:
            return

        chunk = self.parse_value(content)
        value = chunk[0]
        content = chunk[1]

        self.data[key] = value

        while len(content) != 0:
            if content[0] == ',':
                content = content[1:]
            else:
                break

            chunk = self.parse_key(content)
            key = chunk[0]
            content = chunk[1]
            if len(content) > 0:
                chunk = self.parse_value(content)
                value = chunk[0]
                content = chunk[1]
                self.data[key] = value
            else:
                self.data[key] = None

    def parse_first_key(self, content):
        """
         Consume the first field key, which could be a class name.
              :param content str The input to consume
              :return: list The collected string and any remaining content,
               followed by a boolean indicating whether this is a class name.
        """
        length = len(content)
        collected = ''
        is_class_name = False
        if content[0] == '"':
            result = self.parse_string(content[1:])
            return [result[0], result[1][1:]]

        i = 0
        for i in range(0, length):
            c = content[i]
            if c == '@':
                is_class_name = True
                break
            elif c == ':':
                break
            else:
                collected += c

        return [collected, content[( i + 1 ):], is_class_name]

    def parse_key( self, content ):
        """
          Consume a field key, which may or may not be quoted.
            :param content str The input to consume
            :return: dict The collected string and any remaining content.
        """
        length = len(content)
        if length == 0:
            return [None, None]
        collected = ''
        if content[ 0 ] == '"':
            result = self.parse_string( content[1:] )
            return [ result[ 0 ], result[1][1:] ]

        i = 0
        for i in range(0, length):
            c = content[i]
            if c == ':':
                break
            else:
                collected += c

        return [ collected, content[( i + 1 ):] ]

    def parse_value( self, content ):
        """
          Consume a field value.
            :param: content str The input to consume
            :return: list The collected value and any remaining content.
        """
        c = ''
        content = content.lstrip( " " )
        try:
            c = content[ 0 ]  # string index out of range 0
        except IndexError:
            pass

        if len( content ) == 0 or c == ',':
            return [ None, content ]
        elif c == '"':
            return self.parse_string( content[1:] )
        elif c == '#':
            return self.parse_rid( content[1:] )
        elif c == '[':
            return self.parse_collection( content[1:] )
        elif c == '<':
            return self.parse_set( content[1:] )
        elif c == '{':
            return self.parse_map( content[1:] )
        elif c == '(':
            return self.parse_record( content[1:] )
        elif c == '%':
            return self.parse_bag( content[1:] )
        elif c == '_':
            return self.parse_binary( content[1:] )
        elif c == '-' or self.is_numeric( c ):
            return self.parse_number( content )
        elif c == 'n' and content[ 0:4 ] == 'null':
            return [ None, content[ 4: ] ]
        elif c == 't' and content[ 0:4 ] == 'true':
            return [ True, content[ 4: ] ]
        elif c == 'f' and content[ 0:5 ] == 'false':
            return [ False, content[ 5: ] ]
        else:
            return [ None, content ]

    @staticmethod
    def is_numeric( content ):
        try:
            float( content )
            return True
        except ValueError:
            return False

    @staticmethod
    def parse_string( content ):
        """
          Consume a string.
            :param content str The input to consume
            :return: list The collected string and any remaining content.
        """
        length = len( content )
        collected = ''
        i = 0
        while i < length:
            c = content[ i ]
            if c == '\\':
                # escape, skip to the next character
                i += 1
                collected += content[ i ]
                # increment again to pass over
                i += 1
                continue
            elif c == '"':
                break
            else:
                i += 1
                collected += c

        return [ collected, content[ ( i + 1 ): ] ]

    def parse_number(self, content):
        """
           Consume a number.
           If the number has a suffix, consume it also and instantiate the
            right type, e.g. for dates
           :param content str The content to consume
           :return: list The collected number and any remaining content.
        """
        length = len(content)
        collected = ''
        is_float = False
        i = 0
        for i in range(0, length):
            c = content[i]
            if c == '-' or self.is_numeric(c):
                collected += c
            elif c == '.':
                is_float = True
                collected += c
            elif c == 'E' and is_float:
                collected += c
            else:
                break

        content = content[i:]
        c = ''
        try:
            c = content[ 0 ]  # string index out of range 0
        except IndexError:
            pass

        if c == 'a':
            collected = date.fromtimestamp(float(collected) / 1000)
            content = content[1:]
        elif c == 't':
            # date
            collected = datetime.fromtimestamp(float(collected) / 1000)
            content = content[1:]
        elif c == 'f' or c == 'c' or c == 'd':
            # float # decimal (signed32 bit floating point) # double
            collected = float(collected)
            content = content[1:]
        elif c == 'b' or c == 's':
            collected = int(collected)
            content = content[1:]
        elif c == 'l':
            if sys.version_info[0] < 3:
                collected = long(collected)  # python 2.x long type
            else:
                collected = int(collected)
            content = content[1:]
        elif is_float:
            collected = float(collected)
        else:
            collected = int(collected)

        return [collected, content]

    def parse_rid(self, content):
        """
          Consume a Record ID.
          :param content str The input to consume
          :return: list The collected RID and any remaining content.
        """
        length = len(content)
        collected = ''
        cluster = None
        i = 0
        for i in range(0, length):
            c = content[i]
            if cluster is None and c == ':':
                cluster = collected
                collected = ''
            elif self.is_numeric(c):
                collected += c
            else:
                break

        return [ OrientRecordLink( cluster + ":" + collected ), content[i:]]

    def parse_collection(self, content):
        """
            Consume an array of values.
            :param content str The input to consume
            :return: list The collected array and any remaining content.
        """
        collection = []
        while len(content) != 0:
            c = content[0]
            if c == ',':
                content = content[1:]
            elif c == ']':
                content = content[1:]
                break

            chunk = self.parse_value(content)
            collection.append(chunk[0])
            content = chunk[1]

        return [collection, content]

    def parse_set(self, content):
        """
           Consume a set of values.
           :param content str The input to consume
           :return: list The collected set and any remaining content.
        """
        list_set = []
        while len(content) != 0:
            c = content[0]
            if c == ',':
                content = content[1:]
            elif c == '>':
                content = content[1:]
                break

            chunk = self.parse_value(content)
            list_set.append(chunk[0])
            content = chunk[1]

        return [list_set, content]

    def parse_map( self, content ):
        """
            Consume a map of keys to values.
            :param content str The input to consume
            :return: list The collected map and any remaining content.
        """
        _map = {}
        content = content.lstrip(' ')
        while len(content) != 0:
            c = content[0]
            if c == ' ':
                content = content[1:].lstrip(' ')
                continue
            elif c == ',':
                content = content[1:].lstrip(' ')
            elif c == '}':
                content = content[1:]
                break

            chunk = self.parse_key(content)
            key = chunk[0]
            content = chunk[1].lstrip(' ')
            if len(content) != 0:
                chunk = self.parse_value(content)
                _map[key] = chunk[0]
                content = chunk[1].lstrip(' ')
            else:
                _map[key] = None
                break

        return [_map, content]

    def parse_record(self, content):
        """
          Consume an embedded record.
           :param content str The content to unserialize.
           :return: list The collected record and any remaining content.
        """
        record = {}

        content = content.lstrip(' ')
        if content[0] == ')':
            # this is an empty record.
            return [record, content[1:]]

        chunk = self.parse_first_key(content)
        if chunk[2]:
            # this is actually a class name.
            record['o_class'] = chunk[0]
            content = chunk[1].lstrip(' ')
            if content[0] == ')':
                return [record, content[1:]]

            chunk = self.parse_key(content)
            key = chunk[0]
            content = chunk[1]
        else:
            key = chunk[0]
            content = chunk[1]

        chunk = self.parse_key(content)
        value = chunk[0]
        content = chunk[1].lstrip(' ')

        record[key] = value

        while len(content) > 0:
            if content[0] == ',':
                content = content[1:].lstrip(' ')
            elif content[0] == ')':
                content = content[1:].lstrip(' ')
                break

            chunk = self.parse_key(content)
            key = chunk[0]
            content = chunk[1].lstrip(' ')
            if len(content) > 0:
                chunk = self.parse_value(content)
                value = chunk[0]
                content = chunk[1]
                record[key] = value
            else:
                record[key] = None

        return [record, content]

    @staticmethod
    def parse_bag(content):
        """
         Consume a record id bag.
           :param content str The content to consume
           :return: list The collected record id bag and any remaining content.
        """
        length = len(content)
        collected = ''
        i = 0
        for i in range(0, length):
            c = content[i]
            if c == ';':
                break
            else:
                collected += c

        return [OrientBinaryObject(collected), content[( i + 1 ):]]

    @staticmethod
    def parse_binary(content):
        """
          Consume a binary field.
            :param content str The content to consume
            :return: list The collected binary and any remaining content.
        """
        length = len(content)
        collected = ''
        i = 0
        for i in range(0, length):
            c = content[i]
            if c == '_' \
                    or c == ',' \
                    or c == ')' \
                    or c == '>' \
                    or c == '}' \
                    or c == ']':
                break
            else:
                collected += c

        return [collected, content[( i + 1 ):]]

class ORecordEncoder(object):
    """docstring for ORecordEncoder"""

    def __init__(self, oRecord):
        self._raw = self.__encode(oRecord)

    def __encode(self, record):

        raw = ''
        o_class = getattr(record, 'o_class', False)
        if o_class:
            raw = o_class + '@'

        fields = list(record.oRecordData)

        for idx, key in enumerate(fields):
            raw += key + ':'
            value = record.oRecordData[key]
            raw += self.parse_value(value)

            if idx < len(list(fields)) - 1:
                # not last element
                raw += ','

        return raw

    def parse_value(self, value):

        if isinstance(value, str):
            ret = '"' + value + '"'
        elif isinstance(value, float):
            ret = str(value) + 'f'

        elif sys.version_info[0] >= 3 and isinstance(value, int):
            if value > 2147483647:
                ret = str(value) + 'l'
            else:
                ret = str(value)

        elif sys.version_info[0] < 3 and isinstance(value, long):
            ret = str(value) + 'l'
        elif isinstance(value, int):
            ret = str(value)

        elif isinstance(value, datetime):
            ret = str(int(time.mktime(value.timetuple())) * 1000) + 't'
        elif isinstance(value, date):
            ret = str(int(time.mktime(value.timetuple())) * 1000) + 'a'
        elif isinstance(value, list):
            try:
                ret = "[" + ','.join(
                    map(
                        lambda elem: self.parse_value(type(value[0])(elem))
                        if not isinstance(value[0], OrientRecordLink)
                        else elem.get_hash(),
                        value
                    )) + ']'
            except ValueError as e:
                raise Exception("wrong type commistion")
        elif isinstance(value, dict):
            ret = "{" + ','.join(map(
                lambda elem: '"' + elem + '":' + self.parse_value(value[elem]),
                value)) + '}'
        elif isinstance(value, OrientRecord):
            ret = "(" + self.__encode(value) + ")"
        elif isinstance(value, OrientRecordLink):
            ret = value.get_hash()
        elif isinstance(value, OrientBinaryObject):
            ret = value.getRaw()
        else:
            ret = ''
        return ret

    def get_raw(self):
        return self._raw
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#The MIT License (MIT)
#
#Copyright (c) <2013-2014> <Colin Duquesnoy and others, see AUTHORS.txt>
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in
#all copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
#
"""
This package contains python specific modes, panels and editor.
"""
import re
import sys
import weakref
import pyqode.core
from pyqode.qt import QtCore, QtGui

# public API
from pyqode.python.bootstrapper import Bootstrapper
from pyqode.python.modes import PyAutoCompleteMode
from pyqode.python.modes import CalltipsMode
from pyqode.python.modes import CommentsMode
from pyqode.python.modes import PyCodeCompletionMode, JediCompletionProvider
from pyqode.python.modes import PEP8CheckerMode
from pyqode.python.modes import PyAutoIndentMode
from pyqode.python.modes import PyFlakesCheckerMode
from pyqode.python.modes import PyHighlighterMode
from pyqode.python.modes import PyIndenterMode
from pyqode.python.modes import DEFAULT_DARK_STYLES
from pyqode.python.modes import DEFAULT_LIGHT_STYLES
from pyqode.python.modes import GoToAssignmentsMode
from pyqode.python.modes import DocumentAnalyserMode
from pyqode.python.panels import PreLoadPanel
from pyqode.python.panels import SymbolBrowserPanel
from pyqode.python.panels import QuickDocPanel

#: pyqode-python version
__version__ = "1.3.0"

import pyqode.python.ui.pyqode_python_icons_rc

class QPythonCodeEdit(pyqode.core.QCodeEdit):
    """
    Extends QCodeEdit with a hardcoded set of modes and panels specifics to
    a python code editor widget.

    **Panels:**
        * :class:`pyqode.core.FoldingPanel`
        * :class:`pyqode.core.LineNumberPanel`
        * :class:`pyqode.core.MarkerPanel`
        * :class:`pyqode.core.SearchAndReplacePanel`

    **Modes:**
        * :class:`pyqode.core.CaretLineHighlighterMode`
        * :class:`pyqode.core.RightMarginMode`
        * :class:`pyqode.core.CodeCompletionMode` + :class:`pyqode.python.JediCompletionProvider` and :class:`pyqode.core.DocumentWordCompletionProvider`
        * :class:`pyqode.core.ZoomMode`
        * :class:`pyqode.core.SymbolMatcherMode`
        * :class:`pyqode.python.PyAutoCompleteMode`
        * :class:`pyqode.python.PyHighlighterMode`
        * :class:`pyqode.python.PyAutoIndentMode`
        * :class:`pyqode.python.PyFlakesCheckerMode`
        * :class:`pyqode.python.PEP8CheckerMode`
        * :class:`pyqode.python.CalltipsMode`
        * :class:`pyqode.python.PyIndenterMode`

    It also implements utility methods to switch from a white style to a dark
    style and inversely.

    .. note:: This code editor widget use PEP 0263 to detect file encoding.
              If the opened file does not respects the PEP 0263,
              :py:func:`sys.getfilesystemencoding` is used as the default
              encoding.
    """
    DARK_STYLE = 0
    LIGHT_STYLE = 1

    def __init__(self, parent=None, addToPath=True, modulesToPreload=None):
        """
        :param addToPath: True to add the open file's parent directory to
                          sys.path so that jedi can complete sibling modules.

        :param modulesToPreload: The list of modules to preload.
        """
        pyqode.core.start_server(slots=["default", "__server__", "jedi"])
        super(QPythonCodeEdit, self).__init__(parent)
        self.setLineWrapMode(self.NoWrap)
        self.setWindowTitle("pyQode - Python Editor")

        # install those modes first as they are required by other modes/panels
        self.installMode(DocumentAnalyserMode())

        self.installPanel(pyqode.core.FoldingPanel())
        self.installPanel(pyqode.core.LineNumberPanel(),
                          pyqode.core.PanelPosition.LEFT)
        self.installPanel(pyqode.core.MarkerPanel())
        self.installPanel(pyqode.core.SearchAndReplacePanel(),
                          pyqode.core.PanelPosition.BOTTOM)
        self.installPanel(SymbolBrowserPanel(), pyqode.core.PanelPosition.TOP)
        #self.installPanel(PreLoadPanel(), pyqode.core.PanelPosition.TOP)
        #self.preLoadPanel.setVisible(False)
        self.installMode(pyqode.core.CaretLineHighlighterMode())
        self.installMode(pyqode.core.RightMarginMode())
        self.installMode(PyCodeCompletionMode())
        provider = JediCompletionProvider(addToPath=addToPath,
                                   modules=modulesToPreload)
        self.codeCompletionMode.addCompletionProvider(provider)
        self._cc_provider = weakref.ref(provider)
        self.codeCompletionMode.addCompletionProvider(
            pyqode.core.DocumentWordCompletionProvider())
        self.installMode(pyqode.core.ZoomMode())
        #self.installMode(pyqode.core.FileWatcherMode())
        self.installMode(pyqode.core.SymbolMatcherMode())
        self.installMode(pyqode.core.WordClickMode())
        self.installMode(PyHighlighterMode(self.document()))
        self.installMode(PyAutoCompleteMode())
        self.installMode(PyAutoIndentMode())
        self.installMode(PyFlakesCheckerMode())
        self.installMode(PEP8CheckerMode())
        self.installMode(CalltipsMode())
        self.installMode(PyIndenterMode())
        self.installMode(GoToAssignmentsMode())
        self.installPanel(QuickDocPanel(), pyqode.core.PanelPosition.BOTTOM)
        self.installMode(CommentsMode())

    def setModulesToPreload(self, modules=None):
        """
        Sets the list of modules to preload. This must be called before opening
        the first file with pyqode. (you can also pass the list of modules to
        the constructor using the modulesToPreload argument).
        """
        self._cc_provider().modules = modules

    @QtCore.Slot()
    def useDarkStyle(self, use=True):
        """
        Changes the editor style to a dark color scheme similar to pycharm's
        darcula color scheme.
        """
        if not use:
            return
        setDarkColorScheme(self)

    @QtCore.Slot()
    def useLightStyle(self, use=True):
        """
        Changes the editor style to a dark color scheme similar to QtCreator's
        default color scheme.
        """
        if not use:
            return
        setLightColorScheme(self)

    def detectEncoding(self, data):
        """
        Detects encoding based on PEP 0263
        """
        encoding = self.getDefaultEncoding()
        if sys.version_info[0] == 3:
            data = str(data.decode("utf-8"))
        for l in data.splitlines():
            regexp = re.compile(r"#.*coding[:=]\s*([-\w.]+)")
            match = regexp.match(l)
            if match:
                encoding = match.groups()[0]
        return encoding

def setDarkColorScheme(codeEdit):
    """
    Set a dark scheme on a :class:`pyqode.core.QCodeEdit`.

    The color scheme is similar to pycharm's darcula color scheme.

    .. note:: This function will work only if a
              :class:`pyqode.python.PyHighlighterMode` has been installed on the
              codeEdit instance

    :param codeEdit: QCodeEdit instance
    :type codeEdit: pyqode.core.QCodeEdit
    """
    style = codeEdit.style.clone()
    for k, v in DEFAULT_DARK_STYLES.items():
        style.setValue(k, v, "Python")
    style.setValue("background", QtGui.QColor("#252525"))
    style.setValue("foreground", QtGui.QColor("#A9B7C6"))
    style.setValue("caretLineBackground", QtGui.QColor("#2d2d2d"))
    style.setValue("whiteSpaceForeground", QtGui.QColor('#404040'))
    style.setValue("matchedBraceBackground", None)
    style.setValue("matchedBraceForeground", QtGui.QColor("#FF8647"))
    codeEdit.style = style

def setLightColorScheme(codeEdit):
    """
    Set a light scheme on a :class:`pyqode.core.QCodeEdit`.

    The color scheme is similar to the qt creator's default color scheme.

    .. note:: This function will work only if a
              :class:`pyqode.python.PyHighlighterMode` has been installed on the
              codeEdit instance

    :param codeEdit: QCodeEdit instance
    :type codeEdit: pyqode.core.QCodeEdit
    """
    style = codeEdit.style.clone()
    for k, v in DEFAULT_LIGHT_STYLES.items():
        style.setValue(k, v, "Python")
    style.setValue("background", QtGui.QColor("#FFFFFF"))
    style.setValue("foreground", QtGui.QColor("#000000"))
    style.setValue("caretLineBackground", QtGui.QColor("#E4EDF8"))
    style.setValue("whiteSpaceForeground",
                   pyqode.core.constants.EDITOR_WS_FOREGROUND)
    style.setValue("matchedBraceBackground", QtGui.QColor("#B4EEB4"))
    style.setValue("matchedBraceForeground", QtGui.QColor("#FF0000"))
    codeEdit.style = style

__all__ = ["PyCodeCompletionMode", "PEP8CheckerMode", 'PyHighlighterMode',
           'PyAutoIndentMode', "GoToAssignmentsMode", "CommentsMode",
           "CalltipsMode", "JediCompletionProvider", "PyFlakesCheckerMode",
           "PyIndenterMode", "PreLoadPanel", "__version__", "QPythonCodeEdit",
           "setLightColorScheme", "setDarkColorScheme", "Bootstrapper",
           "SymbolBrowserPanel",
           "DEFAULT_LIGHT_STYLES", "DEFAULT_DARK_STYLES"]
<commit_msg>Add filewatcher to QPythonCodeEdit<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#The MIT License (MIT)
#
#Copyright (c) <2013-2014> <Colin Duquesnoy and others, see AUTHORS.txt>
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in
#all copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#THE SOFTWARE.
#
"""
This package contains python specific modes, panels and editor.
"""
import re
import sys
import weakref
import pyqode.core
from pyqode.qt import QtCore, QtGui

# public API
from pyqode.python.bootstrapper import Bootstrapper
from pyqode.python.modes import PyAutoCompleteMode
from pyqode.python.modes import CalltipsMode
from pyqode.python.modes import CommentsMode
from pyqode.python.modes import PyCodeCompletionMode, JediCompletionProvider
from pyqode.python.modes import PEP8CheckerMode
from pyqode.python.modes import PyAutoIndentMode
from pyqode.python.modes import PyFlakesCheckerMode
from pyqode.python.modes import PyHighlighterMode
from pyqode.python.modes import PyIndenterMode
from pyqode.python.modes import DEFAULT_DARK_STYLES
from pyqode.python.modes import DEFAULT_LIGHT_STYLES
from pyqode.python.modes import GoToAssignmentsMode
from pyqode.python.modes import DocumentAnalyserMode
from pyqode.python.panels import PreLoadPanel
from pyqode.python.panels import SymbolBrowserPanel
from pyqode.python.panels import QuickDocPanel

#: pyqode-python version
__version__ = "1.3.0"

import pyqode.python.ui.pyqode_python_icons_rc

class QPythonCodeEdit(pyqode.core.QCodeEdit):
    """
    Extends QCodeEdit with a hardcoded set of modes and panels specifics to
    a python code editor widget.

    **Panels:**
        * :class:`pyqode.core.FoldingPanel`
        * :class:`pyqode.core.LineNumberPanel`
        * :class:`pyqode.core.MarkerPanel`
        * :class:`pyqode.core.SearchAndReplacePanel`

    **Modes:**
        * :class:`pyqode.core.CaretLineHighlighterMode`
        * :class:`pyqode.core.RightMarginMode`
        * :class:`pyqode.core.CodeCompletionMode` + :class:`pyqode.python.JediCompletionProvider` and :class:`pyqode.core.DocumentWordCompletionProvider`
        * :class:`pyqode.core.ZoomMode`
        * :class:`pyqode.core.SymbolMatcherMode`
        * :class:`pyqode.python.PyAutoCompleteMode`
        * :class:`pyqode.python.PyHighlighterMode`
        * :class:`pyqode.python.PyAutoIndentMode`
        * :class:`pyqode.python.PyFlakesCheckerMode`
        * :class:`pyqode.python.PEP8CheckerMode`
        * :class:`pyqode.python.CalltipsMode`
        * :class:`pyqode.python.PyIndenterMode`

    It also implements utility methods to switch from a white style to a dark
    style and inversely.

    .. note:: This code editor widget use PEP 0263 to detect file encoding.
              If the opened file does not respects the PEP 0263,
              :py:func:`sys.getfilesystemencoding` is used as the default
              encoding.
    """
    DARK_STYLE = 0
    LIGHT_STYLE = 1

    def __init__(self, parent=None, addToPath=True, modulesToPreload=None):
        """
        :param addToPath: True to add the open file's parent directory to
                          sys.path so that jedi can complete sibling modules.

        :param modulesToPreload: The list of modules to preload.
        """
        pyqode.core.start_server(slots=["default", "__server__", "jedi"])
        super(QPythonCodeEdit, self).__init__(parent)
        self.setLineWrapMode(self.NoWrap)
        self.setWindowTitle("pyQode - Python Editor")

        # install those modes first as they are required by other modes/panels
        self.installMode(DocumentAnalyserMode())

        self.installPanel(pyqode.core.FoldingPanel())
        self.installPanel(pyqode.core.LineNumberPanel(),
                          pyqode.core.PanelPosition.LEFT)
        self.installPanel(pyqode.core.MarkerPanel())
        self.installPanel(pyqode.core.SearchAndReplacePanel(),
                          pyqode.core.PanelPosition.BOTTOM)
        self.installPanel(SymbolBrowserPanel(), pyqode.core.PanelPosition.TOP)
        #self.installPanel(PreLoadPanel(), pyqode.core.PanelPosition.TOP)
        #self.preLoadPanel.setVisible(False)
        self.installMode(pyqode.core.CaretLineHighlighterMode())
        self.installMode(pyqode.core.RightMarginMode())
        self.installMode(PyCodeCompletionMode())
        provider = JediCompletionProvider(addToPath=addToPath,
                                   modules=modulesToPreload)
        self.codeCompletionMode.addCompletionProvider(provider)
        self._cc_provider = weakref.ref(provider)
        self.codeCompletionMode.addCompletionProvider(
            pyqode.core.DocumentWordCompletionProvider())
        self.installMode(pyqode.core.ZoomMode())
        #self.installMode(pyqode.core.FileWatcherMode())
        self.installMode(pyqode.core.SymbolMatcherMode())
        self.installMode(pyqode.core.WordClickMode())
        self.installMode(PyHighlighterMode(self.document()))
        self.installMode(PyAutoCompleteMode())
        self.installMode(PyAutoIndentMode())
        self.installMode(PyFlakesCheckerMode())
        self.installMode(PEP8CheckerMode())
        self.installMode(CalltipsMode())
        self.installMode(PyIndenterMode())
        self.installMode(GoToAssignmentsMode())
        self.installPanel(QuickDocPanel(), pyqode.core.PanelPosition.BOTTOM)
        self.installMode(CommentsMode())
        self.installMode(pyqode.core.FileWatcherMode())

    def setModulesToPreload(self, modules=None):
        """
        Sets the list of modules to preload. This must be called before opening
        the first file with pyqode. (you can also pass the list of modules to
        the constructor using the modulesToPreload argument).
        """
        self._cc_provider().modules = modules

    @QtCore.Slot()
    def useDarkStyle(self, use=True):
        """
        Changes the editor style to a dark color scheme similar to pycharm's
        darcula color scheme.
        """
        if not use:
            return
        setDarkColorScheme(self)

    @QtCore.Slot()
    def useLightStyle(self, use=True):
        """
        Changes the editor style to a dark color scheme similar to QtCreator's
        default color scheme.
        """
        if not use:
            return
        setLightColorScheme(self)

    def detectEncoding(self, data):
        """
        Detects encoding based on PEP 0263
        """
        encoding = self.getDefaultEncoding()
        if sys.version_info[0] == 3:
            data = str(data.decode("utf-8"))
        for l in data.splitlines():
            regexp = re.compile(r"#.*coding[:=]\s*([-\w.]+)")
            match = regexp.match(l)
            if match:
                encoding = match.groups()[0]
        return encoding

def setDarkColorScheme(codeEdit):
    """
    Set a dark scheme on a :class:`pyqode.core.QCodeEdit`.

    The color scheme is similar to pycharm's darcula color scheme.

    .. note:: This function will work only if a
              :class:`pyqode.python.PyHighlighterMode` has been installed on the
              codeEdit instance

    :param codeEdit: QCodeEdit instance
    :type codeEdit: pyqode.core.QCodeEdit
    """
    style = codeEdit.style.clone()
    for k, v in DEFAULT_DARK_STYLES.items():
        style.setValue(k, v, "Python")
    style.setValue("background", QtGui.QColor("#252525"))
    style.setValue("foreground", QtGui.QColor("#A9B7C6"))
    style.setValue("caretLineBackground", QtGui.QColor("#2d2d2d"))
    style.setValue("whiteSpaceForeground", QtGui.QColor('#404040'))
    style.setValue("matchedBraceBackground", None)
    style.setValue("matchedBraceForeground", QtGui.QColor("#FF8647"))
    codeEdit.style = style

def setLightColorScheme(codeEdit):
    """
    Set a light scheme on a :class:`pyqode.core.QCodeEdit`.

    The color scheme is similar to the qt creator's default color scheme.

    .. note:: This function will work only if a
              :class:`pyqode.python.PyHighlighterMode` has been installed on the
              codeEdit instance

    :param codeEdit: QCodeEdit instance
    :type codeEdit: pyqode.core.QCodeEdit
    """
    style = codeEdit.style.clone()
    for k, v in DEFAULT_LIGHT_STYLES.items():
        style.setValue(k, v, "Python")
    style.setValue("background", QtGui.QColor("#FFFFFF"))
    style.setValue("foreground", QtGui.QColor("#000000"))
    style.setValue("caretLineBackground", QtGui.QColor("#E4EDF8"))
    style.setValue("whiteSpaceForeground",
                   pyqode.core.constants.EDITOR_WS_FOREGROUND)
    style.setValue("matchedBraceBackground", QtGui.QColor("#B4EEB4"))
    style.setValue("matchedBraceForeground", QtGui.QColor("#FF0000"))
    codeEdit.style = style

__all__ = ["PyCodeCompletionMode", "PEP8CheckerMode", 'PyHighlighterMode',
           'PyAutoIndentMode', "GoToAssignmentsMode", "CommentsMode",
           "CalltipsMode", "JediCompletionProvider", "PyFlakesCheckerMode",
           "PyIndenterMode", "PreLoadPanel", "__version__", "QPythonCodeEdit",
           "setLightColorScheme", "setDarkColorScheme", "Bootstrapper",
           "SymbolBrowserPanel",
           "DEFAULT_LIGHT_STYLES", "DEFAULT_DARK_STYLES"]
<|endoftext|>


<commit_before>from __future__ import absolute_import, print_function, division

import os.path
import inspect
import pkg_resources

from pyramid.decorator import reify
from pyramid.settings import aslist
from pyramid.path import DottedNameResolver

from .templating.lookup import SuperTemplateLookup
from .templating.renderer import (mako_renderer_factory,
                                  mako_renderer_factory_nofilters)

static_dir = pkg_resources.resource_filename('pyramid_frontend', 'static')

default_sentinel = object()

class Theme(object):
    template_dir = 'templates'
    static_dir = 'static'
    assets = {}
    image_filters = []
    require_config_path = '/_pfe/require_config.js'
    require_path = '/_pfe/require.js'
    less_path = '/_pfe/less.js'
    includes = []

    def __init__(self, settings):
        self.settings = settings
        self._compiled_asset_cache = {}

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.key)

    @classmethod
    def qualify_path(cls, path):
        return os.path.join(os.path.dirname(inspect.getfile(cls)), path)

    @classmethod
    def traverse_attributes(cls, name, qualify_paths=False):
        assert len(cls.__bases__) == 1, \
            "multiple inheritance not allowed for themes"
        while cls != Theme:
            el = getattr(cls, name)
            if qualify_paths:
                el = cls.qualify_path(el)
            yield cls.key, el
            cls = cls.__bases__[0]

    @reify
    def template_dirs(self):
        dirs = []
        for key, dir in self.__class__.traverse_attributes(
                'template_dir', qualify_paths=True):
            dirs.append(dir)
        return dirs

    @reify
    def lookup(self):
        return self._make_lookup()

    @reify
    def lookup_nofilters(self):
        return self._make_lookup(clear_default_filters=True)

    def _make_lookup(self, clear_default_filters=False):
        default_filters = ['str'] if clear_default_filters else ['escape']
        template_imports = [
            'from webhelpers.html import escape',
        ]
        template_imports.extend(aslist(
            self.settings.get('pyramid_frontend.template_imports', ''),
            flatten=False))
        return SuperTemplateLookup(directories=self.template_dirs,
                                   input_encoding='utf-8',
                                   output_encoding='utf-8',
                                   imports=template_imports,
                                   default_filters=default_filters)

    @reify
    def stacked_image_filters(self):
        filters = {}
        collected = self.__class__.traverse_attributes('image_filters')
        for key, class_list in reversed(list(collected)):
            class_dict = {chain.suffix: chain for chain in class_list}
            filters.update(class_dict)
        return filters.values()

    @reify
    def stacked_assets(self):
        assets = {}
        collected = self.__class__.traverse_attributes('assets')
        for key, class_dict in reversed(list(collected)):
            assets.update(class_dict)
        return assets

    @reify
    def stacked_includes(self):
        includes = []
        collected = self.__class__.traverse_attributes('includes')
        for key, these in reversed(list(collected)):
            includes.extend(these)
        return includes

    @reify
    def keyed_static_dirs(self):
        cls = self.__class__
        stack = list(cls.traverse_attributes('static_dir', qualify_paths=True))
        stack.append(('pfe', static_dir))
        return stack

    def compiled_asset_path(self, key):
        if key in self._compiled_asset_cache:
            return self._compiled_asset_cache[key]
        else:
            map_path = os.path.join(
                self.settings['pyramid_frontend.compiled_asset_dir'],
                self.key,
                '%s.map' % key)
            with open(map_path) as f:
                self._compiled_asset_cache[key] = compiled_path = f.read()
                return compiled_path

    def static_url_to_filesystem_path(self, url):
        """
        Given a URL of the structure /_<theme key>/<path>, locate the static
        dir which corresponds to the theme key and re-qualify the <path> to
        that directory.
        """
        assert url.startswith('/_')
        theme_key, path = url[2:].split('/', 1)
        theme_dirs = dict(self.keyed_static_dirs)
        base_dir = theme_dirs[theme_key]
        return os.path.join(base_dir, path)

    def opt(self, key, default=default_sentinel):
        if default is default_sentinel:
            return getattr(self, key)
        else:
            return getattr(self, key, default)

    def static(self, path):
        for key, static_dir in self.keyed_static_dirs:
            if os.path.exists(os.path.join(static_dir, path)):
                return '/_%s/%s' % (key, path)
        raise IOError('path %r does not exist in any static dirs' % path)

def add_theme(config, cls):
    """
    Initialize and register a theme for use.
    """
    resolved_cls = config.maybe_dotted(cls)

    settings = config.registry.settings
    theme = resolved_cls(settings)

    # Call includes
    package = inspect.getmodule(resolved_cls)
    resolver = DottedNameResolver(package=package)
    for include in theme.stacked_includes:
        print("INCLUDING %r" % (include,))
        config.include(resolver.maybe_resolve(include))

    # Register static dirs.
    static_dirs = settings.setdefault('pyramid_frontend.static_registry',
                                      set())
    for key, dir in theme.keyed_static_dirs:
        print("REGISTERING key %r, dir %r" % (key, dir))
        if (key, dir) not in static_dirs:
            print("  ADDING")
            static_dirs.add((key, dir))
            config.add_static_view('_%s' % key, path=dir)
        else:
            print("  SKIPPING")

    # Update global image filter registry as well, and ensure there are no
    # conflicts.
    for chain in theme.stacked_image_filters:
        config.add_image_filter(chain, with_theme=theme)

    def register(theme):
        themes = settings.setdefault('pyramid_frontend.theme_registry', {})
        themes[theme.key] = theme

    intr = config.introspectable(category_name='themes',
                                 discriminator=theme.key,
                                 title=theme.key,
                                 type_name=None)
    intr['theme'] = theme

    config.action(('theme', theme.key),
                  register,
                  args=(theme,),
                  introspectables=(intr,))

def set_theme_strategy(config, strategy_func):
    settings = config.registry.settings
    settings['pyramid_frontend.theme_strategy'] = strategy_func

def theme(self):
    settings = self.registry.settings
    strategy = settings.get('pyramid_frontend.theme_strategy')
    if strategy:
        key = strategy(self)
    else:
        key = settings['pyramid_frontend.theme']
    themes = settings.setdefault('pyramid_frontend.theme_registry', {})
    return themes[key]

def includeme(config):
    config.include('.images')
    config.include('.assets')
    config.add_directive('add_theme', add_theme)
    config.add_directive('set_theme_strategy', set_theme_strategy)
    config.add_request_method(theme, 'theme', reify=True)

    config.add_renderer(name='.html', factory=mako_renderer_factory)
    config.add_renderer(name='.txt', factory=mako_renderer_factory_nofilters)
<commit_msg>Remove debugging print statements<commit_after>from __future__ import absolute_import, print_function, division

import os.path
import inspect
import pkg_resources

from pyramid.decorator import reify
from pyramid.settings import aslist
from pyramid.path import DottedNameResolver

from .templating.lookup import SuperTemplateLookup
from .templating.renderer import (mako_renderer_factory,
                                  mako_renderer_factory_nofilters)

static_dir = pkg_resources.resource_filename('pyramid_frontend', 'static')

default_sentinel = object()

class Theme(object):
    template_dir = 'templates'
    static_dir = 'static'
    assets = {}
    image_filters = []
    require_config_path = '/_pfe/require_config.js'
    require_path = '/_pfe/require.js'
    less_path = '/_pfe/less.js'
    includes = []

    def __init__(self, settings):
        self.settings = settings
        self._compiled_asset_cache = {}

    def __repr__(self):
        return '<%s: %s>' % (self.__class__.__name__, self.key)

    @classmethod
    def qualify_path(cls, path):
        return os.path.join(os.path.dirname(inspect.getfile(cls)), path)

    @classmethod
    def traverse_attributes(cls, name, qualify_paths=False):
        assert len(cls.__bases__) == 1, \
            "multiple inheritance not allowed for themes"
        while cls != Theme:
            el = getattr(cls, name)
            if qualify_paths:
                el = cls.qualify_path(el)
            yield cls.key, el
            cls = cls.__bases__[0]

    @reify
    def template_dirs(self):
        dirs = []
        for key, dir in self.__class__.traverse_attributes(
                'template_dir', qualify_paths=True):
            dirs.append(dir)
        return dirs

    @reify
    def lookup(self):
        return self._make_lookup()

    @reify
    def lookup_nofilters(self):
        return self._make_lookup(clear_default_filters=True)

    def _make_lookup(self, clear_default_filters=False):
        default_filters = ['str'] if clear_default_filters else ['escape']
        template_imports = [
            'from webhelpers.html import escape',
        ]
        template_imports.extend(aslist(
            self.settings.get('pyramid_frontend.template_imports', ''),
            flatten=False))
        return SuperTemplateLookup(directories=self.template_dirs,
                                   input_encoding='utf-8',
                                   output_encoding='utf-8',
                                   imports=template_imports,
                                   default_filters=default_filters)

    @reify
    def stacked_image_filters(self):
        filters = {}
        collected = self.__class__.traverse_attributes('image_filters')
        for key, class_list in reversed(list(collected)):
            class_dict = {chain.suffix: chain for chain in class_list}
            filters.update(class_dict)
        return filters.values()

    @reify
    def stacked_assets(self):
        assets = {}
        collected = self.__class__.traverse_attributes('assets')
        for key, class_dict in reversed(list(collected)):
            assets.update(class_dict)
        return assets

    @reify
    def stacked_includes(self):
        includes = []
        collected = self.__class__.traverse_attributes('includes')
        for key, these in reversed(list(collected)):
            includes.extend(these)
        return includes

    @reify
    def keyed_static_dirs(self):
        cls = self.__class__
        stack = list(cls.traverse_attributes('static_dir', qualify_paths=True))
        stack.append(('pfe', static_dir))
        return stack

    def compiled_asset_path(self, key):
        if key in self._compiled_asset_cache:
            return self._compiled_asset_cache[key]
        else:
            map_path = os.path.join(
                self.settings['pyramid_frontend.compiled_asset_dir'],
                self.key,
                '%s.map' % key)
            with open(map_path) as f:
                self._compiled_asset_cache[key] = compiled_path = f.read()
                return compiled_path

    def static_url_to_filesystem_path(self, url):
        """
        Given a URL of the structure /_<theme key>/<path>, locate the static
        dir which corresponds to the theme key and re-qualify the <path> to
        that directory.
        """
        assert url.startswith('/_')
        theme_key, path = url[2:].split('/', 1)
        theme_dirs = dict(self.keyed_static_dirs)
        base_dir = theme_dirs[theme_key]
        return os.path.join(base_dir, path)

    def opt(self, key, default=default_sentinel):
        if default is default_sentinel:
            return getattr(self, key)
        else:
            return getattr(self, key, default)

    def static(self, path):
        for key, static_dir in self.keyed_static_dirs:
            if os.path.exists(os.path.join(static_dir, path)):
                return '/_%s/%s' % (key, path)
        raise IOError('path %r does not exist in any static dirs' % path)

def add_theme(config, cls):
    """
    Initialize and register a theme for use.
    """
    resolved_cls = config.maybe_dotted(cls)

    settings = config.registry.settings
    theme = resolved_cls(settings)

    # Call includes
    package = inspect.getmodule(resolved_cls)
    resolver = DottedNameResolver(package=package)
    for include in theme.stacked_includes:
        config.include(resolver.maybe_resolve(include))

    # Register static dirs.
    static_dirs = settings.setdefault('pyramid_frontend.static_registry',
                                      set())
    for key, dir in theme.keyed_static_dirs:
        if (key, dir) not in static_dirs:
            static_dirs.add((key, dir))
            config.add_static_view('_%s' % key, path=dir)

    # Update global image filter registry as well, and ensure there are no
    # conflicts.
    for chain in theme.stacked_image_filters:
        config.add_image_filter(chain, with_theme=theme)

    def register(theme):
        themes = settings.setdefault('pyramid_frontend.theme_registry', {})
        themes[theme.key] = theme

    intr = config.introspectable(category_name='themes',
                                 discriminator=theme.key,
                                 title=theme.key,
                                 type_name=None)
    intr['theme'] = theme

    config.action(('theme', theme.key),
                  register,
                  args=(theme,),
                  introspectables=(intr,))

def set_theme_strategy(config, strategy_func):
    settings = config.registry.settings
    settings['pyramid_frontend.theme_strategy'] = strategy_func

def theme(self):
    settings = self.registry.settings
    strategy = settings.get('pyramid_frontend.theme_strategy')
    if strategy:
        key = strategy(self)
    else:
        key = settings['pyramid_frontend.theme']
    themes = settings.setdefault('pyramid_frontend.theme_registry', {})
    return themes[key]

def includeme(config):
    config.include('.images')
    config.include('.assets')
    config.add_directive('add_theme', add_theme)
    config.add_directive('set_theme_strategy', set_theme_strategy)
    config.add_request_method(theme, 'theme', reify=True)

    config.add_renderer(name='.html', factory=mako_renderer_factory)
    config.add_renderer(name='.txt', factory=mako_renderer_factory_nofilters)
<|endoftext|>


<commit_before>#!/usr/bin/python
# Copyright 2017 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""Play an agent with an SC2 instance that isn't owned.

This can be used to play on the sc2ai.net ladder, as well as to play vs humans.

To play on ladder:
  $ python -m pysc2.bin.agent_remote --agent <import path> \
      --host_port <GamePort> --lan_port <StartPort>

To play vs humans:
  $ python -m pysc2.bin.agent_remote --human --map <MapName>
then copy the string it generates which is something similar to above

If you want to play remotely, you'll need to port forward (eg with ssh -L or -R)
the host_port from localhost on one machine to localhost on the other.

You can also set your race, observation options, etc by cmdline flags.

When playing vs humans it launches both instances on the human side. This means
you only need to port-forward a single port (ie the websocket betwen SC2 and the
agent), but you also need to transfer the entire observation, which is much
bigger than the actions transferred over the lan connection between the two SC2
instances. It also makes it easy to maintain version compatibility since they
are the same binary. Unfortunately it means higher cpu usage where the human is
playing, which on a Mac becomes problematic as OSX slows down the instance
running in the background. There can also be observation differences between
Mac/Win and Linux. For these reasons, prefer play_vs_agent which runs the
instance next to the agent, and tunnels the lan actions instead.
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import importlib
from absl import logging
import platform
import time

from absl import app
from absl import flags

from pysc2 import maps
from pysc2 import run_configs
from pysc2.env import remote_sc2_env
from pysc2.env import run_loop
from pysc2.env import sc2_env
from pysc2.lib import point_flag
from pysc2.lib import portspicker
from pysc2.lib import renderer_human

from s2clientprotocol import sc2api_pb2 as sc_pb

FLAGS = flags.FLAGS
flags.DEFINE_bool("render", platform.system() == "Linux",
                  "Whether to render with pygame.")
flags.DEFINE_bool("realtime", False, "Whether to run in realtime mode.")

flags.DEFINE_string("agent", "pysc2.agents.random_agent.RandomAgent",
                    "Which agent to run, as a python path to an Agent class.")
flags.DEFINE_enum("agent_race", "random", sc2_env.Race._member_names_,  # pylint: disable=protected-access
                  "Agent's race.")

flags.DEFINE_float("fps", 22.4, "Frames per second to run the game.")
flags.DEFINE_integer("step_mul", 8, "Game steps per agent step.")

point_flag.DEFINE_point("feature_screen_size", "84",
                        "Resolution for screen feature layers.")
point_flag.DEFINE_point("feature_minimap_size", "64",
                        "Resolution for minimap feature layers.")
point_flag.DEFINE_point("rgb_screen_size", "256",
                        "Resolution for rendered screen.")
point_flag.DEFINE_point("rgb_minimap_size", "128",
                        "Resolution for rendered minimap.")
flags.DEFINE_enum("action_space", "FEATURES",
                  sc2_env.ActionSpace._member_names_,  # pylint: disable=protected-access
                  "Which action space to use. Needed if you take both feature "
                  "and rgb observations.")
flags.DEFINE_bool("use_feature_units", False,
                  "Whether to include feature units.")

flags.DEFINE_enum("user_race", "random", sc2_env.Race._member_names_,  # pylint: disable=protected-access
                  "User's race.")

flags.DEFINE_string("host", "127.0.0.1", "Game Host")
flags.DEFINE_integer("host_port", None, "Host port")
flags.DEFINE_integer("lan_port", None, "Host port")

flags.DEFINE_string("map", None, "Name of a map to use to play.")

flags.DEFINE_bool("human", False, "Whether to host a game as a human.")

def main(unused_argv):
  if FLAGS.human:
    human()
  else:
    agent()

def agent():
  """Run the agent, connecting to a (remote) host started independently."""
  agent_module, agent_name = FLAGS.agent.rsplit(".", 1)
  agent_cls = getattr(importlib.import_module(agent_module), agent_name)

  logging.info("Starting agent:")
  with remote_sc2_env.RemoteSC2Env(
      map_name=FLAGS.map,
      host=FLAGS.host,
      host_port=FLAGS.host_port,
      lan_port=FLAGS.lan_port,
      race=sc2_env.Race[FLAGS.agent_race],
      step_mul=FLAGS.step_mul,
      agent_interface_format=sc2_env.parse_agent_interface_format(
          feature_screen=FLAGS.feature_screen_size,
          feature_minimap=FLAGS.feature_minimap_size,
          rgb_screen=FLAGS.rgb_screen_size,
          rgb_minimap=FLAGS.rgb_minimap_size,
          action_space=FLAGS.action_space,
          use_feature_units=FLAGS.use_feature_units),
      visualize=FLAGS.render) as env:
    agents = [agent_cls()]
    logging.info("Connected, starting run_loop.")
    try:
      run_loop.run_loop(agents, env)
    except remote_sc2_env.RestartException:
      pass
  logging.info("Done.")

def human():
  """Run a host which expects one player to connect remotely."""
  run_config = run_configs.get()

  map_inst = maps.get(FLAGS.map)

  if not FLAGS.rgb_screen_size or not FLAGS.rgb_minimap_size:
    logging.info("Use --rgb_screen_size and --rgb_minimap_size if you want rgb "
                 "observations.")

  ports = portspicker.pick_contiguous_unused_ports(4)  # 2 * num_players
  host_proc = run_config.start(extra_ports=ports, host=FLAGS.host,
                               timeout_seconds=300, window_loc=(50, 50))
  client_proc = run_config.start(extra_ports=ports, host=FLAGS.host,
                                 connect=False, window_loc=(700, 50))

  create = sc_pb.RequestCreateGame(
      realtime=FLAGS.realtime, local_map=sc_pb.LocalMap(map_path=map_inst.path))
  create.player_setup.add(type=sc_pb.Participant)
  create.player_setup.add(type=sc_pb.Participant)

  controller = host_proc.controller
  controller.save_map(map_inst.path, map_inst.data(run_config))
  controller.create_game(create)

  print("-" * 80)
  print("Join host: agent_remote --map %s --host %s --host_port %s "
        "--lan_port %s" % (FLAGS.map, FLAGS.host, client_proc.port, ports[0]))
  print("-" * 80)

  join = sc_pb.RequestJoinGame()
  join.shared_port = 0  # unused
  join.server_ports.game_port = ports.pop(0)
  join.server_ports.base_port = ports.pop(0)
  join.client_ports.add(game_port=ports.pop(0), base_port=ports.pop(0))

  join.race = sc2_env.Race[FLAGS.user_race]
  if FLAGS.render:
    join.options.raw = True
    join.options.score = True
    if FLAGS.feature_screen_size and FLAGS.feature_minimap_size:
      fl = join.options.feature_layer
      fl.width = 24
      FLAGS.feature_screen_size.assign_to(fl.resolution)
      FLAGS.feature_minimap_size.assign_to(fl.minimap_resolution)
    if FLAGS.rgb_screen_size and FLAGS.rgb_minimap_size:
      FLAGS.rgb_screen_size.assign_to(join.options.render.resolution)
      FLAGS.rgb_minimap_size.assign_to(join.options.render.minimap_resolution)
  controller.join_game(join)

  if FLAGS.render:
    renderer = renderer_human.RendererHuman(
        fps=FLAGS.fps, render_feature_grid=False)
    renderer.run(run_configs.get(), controller, max_episodes=1)
  else:  # Still step forward so the Mac/Windows renderer works.
    try:
      while True:
        frame_start_time = time.time()
        if not FLAGS.realtime:
          controller.step()
        obs = controller.observe()

        if obs.player_result:
          break
        time.sleep(max(0, frame_start_time - time.time() + 1 / FLAGS.fps))
    except KeyboardInterrupt:
      pass

  for p in [host_proc, client_proc]:
    p.close()

  portspicker.return_ports(ports)

def entry_point():  # Needed so setup.py scripts work.
  app.run(main)

if __name__ == "__main__":
  app.run(main)
<commit_msg>Flush the connection detail output of agent_remote explicitly.<commit_after>#!/usr/bin/python
# Copyright 2017 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""Play an agent with an SC2 instance that isn't owned.

This can be used to play on the sc2ai.net ladder, as well as to play vs humans.

To play on ladder:
  $ python -m pysc2.bin.agent_remote --agent <import path> \
      --host_port <GamePort> --lan_port <StartPort>

To play vs humans:
  $ python -m pysc2.bin.agent_remote --human --map <MapName>
then copy the string it generates which is something similar to above

If you want to play remotely, you'll need to port forward (eg with ssh -L or -R)
the host_port from localhost on one machine to localhost on the other.

You can also set your race, observation options, etc by cmdline flags.

When playing vs humans it launches both instances on the human side. This means
you only need to port-forward a single port (ie the websocket betwen SC2 and the
agent), but you also need to transfer the entire observation, which is much
bigger than the actions transferred over the lan connection between the two SC2
instances. It also makes it easy to maintain version compatibility since they
are the same binary. Unfortunately it means higher cpu usage where the human is
playing, which on a Mac becomes problematic as OSX slows down the instance
running in the background. There can also be observation differences between
Mac/Win and Linux. For these reasons, prefer play_vs_agent which runs the
instance next to the agent, and tunnels the lan actions instead.
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import importlib
from absl import logging
import platform
import sys
import time

from absl import app
from absl import flags

from pysc2 import maps
from pysc2 import run_configs
from pysc2.env import remote_sc2_env
from pysc2.env import run_loop
from pysc2.env import sc2_env
from pysc2.lib import point_flag
from pysc2.lib import portspicker
from pysc2.lib import renderer_human

from s2clientprotocol import sc2api_pb2 as sc_pb

FLAGS = flags.FLAGS
flags.DEFINE_bool("render", platform.system() == "Linux",
                  "Whether to render with pygame.")
flags.DEFINE_bool("realtime", False, "Whether to run in realtime mode.")

flags.DEFINE_string("agent", "pysc2.agents.random_agent.RandomAgent",
                    "Which agent to run, as a python path to an Agent class.")
flags.DEFINE_enum("agent_race", "random", sc2_env.Race._member_names_,  # pylint: disable=protected-access
                  "Agent's race.")

flags.DEFINE_float("fps", 22.4, "Frames per second to run the game.")
flags.DEFINE_integer("step_mul", 8, "Game steps per agent step.")

point_flag.DEFINE_point("feature_screen_size", "84",
                        "Resolution for screen feature layers.")
point_flag.DEFINE_point("feature_minimap_size", "64",
                        "Resolution for minimap feature layers.")
point_flag.DEFINE_point("rgb_screen_size", "256",
                        "Resolution for rendered screen.")
point_flag.DEFINE_point("rgb_minimap_size", "128",
                        "Resolution for rendered minimap.")
flags.DEFINE_enum("action_space", "FEATURES",
                  sc2_env.ActionSpace._member_names_,  # pylint: disable=protected-access
                  "Which action space to use. Needed if you take both feature "
                  "and rgb observations.")
flags.DEFINE_bool("use_feature_units", False,
                  "Whether to include feature units.")

flags.DEFINE_enum("user_race", "random", sc2_env.Race._member_names_,  # pylint: disable=protected-access
                  "User's race.")

flags.DEFINE_string("host", "127.0.0.1", "Game Host")
flags.DEFINE_integer("host_port", None, "Host port")
flags.DEFINE_integer("lan_port", None, "Host port")

flags.DEFINE_string("map", None, "Name of a map to use to play.")

flags.DEFINE_bool("human", False, "Whether to host a game as a human.")

def main(unused_argv):
  if FLAGS.human:
    human()
  else:
    agent()

def agent():
  """Run the agent, connecting to a (remote) host started independently."""
  agent_module, agent_name = FLAGS.agent.rsplit(".", 1)
  agent_cls = getattr(importlib.import_module(agent_module), agent_name)

  logging.info("Starting agent:")
  with remote_sc2_env.RemoteSC2Env(
      map_name=FLAGS.map,
      host=FLAGS.host,
      host_port=FLAGS.host_port,
      lan_port=FLAGS.lan_port,
      race=sc2_env.Race[FLAGS.agent_race],
      step_mul=FLAGS.step_mul,
      agent_interface_format=sc2_env.parse_agent_interface_format(
          feature_screen=FLAGS.feature_screen_size,
          feature_minimap=FLAGS.feature_minimap_size,
          rgb_screen=FLAGS.rgb_screen_size,
          rgb_minimap=FLAGS.rgb_minimap_size,
          action_space=FLAGS.action_space,
          use_feature_units=FLAGS.use_feature_units),
      visualize=FLAGS.render) as env:
    agents = [agent_cls()]
    logging.info("Connected, starting run_loop.")
    try:
      run_loop.run_loop(agents, env)
    except remote_sc2_env.RestartException:
      pass
  logging.info("Done.")

def human():
  """Run a host which expects one player to connect remotely."""
  run_config = run_configs.get()

  map_inst = maps.get(FLAGS.map)

  if not FLAGS.rgb_screen_size or not FLAGS.rgb_minimap_size:
    logging.info("Use --rgb_screen_size and --rgb_minimap_size if you want rgb "
                 "observations.")

  ports = portspicker.pick_contiguous_unused_ports(4)  # 2 * num_players
  host_proc = run_config.start(extra_ports=ports, host=FLAGS.host,
                               timeout_seconds=300, window_loc=(50, 50))
  client_proc = run_config.start(extra_ports=ports, host=FLAGS.host,
                                 connect=False, window_loc=(700, 50))

  create = sc_pb.RequestCreateGame(
      realtime=FLAGS.realtime, local_map=sc_pb.LocalMap(map_path=map_inst.path))
  create.player_setup.add(type=sc_pb.Participant)
  create.player_setup.add(type=sc_pb.Participant)

  controller = host_proc.controller
  controller.save_map(map_inst.path, map_inst.data(run_config))
  controller.create_game(create)

  print("-" * 80)
  print("Join host: agent_remote --map %s --host %s --host_port %s "
        "--lan_port %s" % (FLAGS.map, FLAGS.host, client_proc.port, ports[0]))
  print("-" * 80)
  sys.stdout.flush()

  join = sc_pb.RequestJoinGame()
  join.shared_port = 0  # unused
  join.server_ports.game_port = ports.pop(0)
  join.server_ports.base_port = ports.pop(0)
  join.client_ports.add(game_port=ports.pop(0), base_port=ports.pop(0))

  join.race = sc2_env.Race[FLAGS.user_race]
  if FLAGS.render:
    join.options.raw = True
    join.options.score = True
    if FLAGS.feature_screen_size and FLAGS.feature_minimap_size:
      fl = join.options.feature_layer
      fl.width = 24
      FLAGS.feature_screen_size.assign_to(fl.resolution)
      FLAGS.feature_minimap_size.assign_to(fl.minimap_resolution)
    if FLAGS.rgb_screen_size and FLAGS.rgb_minimap_size:
      FLAGS.rgb_screen_size.assign_to(join.options.render.resolution)
      FLAGS.rgb_minimap_size.assign_to(join.options.render.minimap_resolution)
  controller.join_game(join)

  if FLAGS.render:
    renderer = renderer_human.RendererHuman(
        fps=FLAGS.fps, render_feature_grid=False)
    renderer.run(run_configs.get(), controller, max_episodes=1)
  else:  # Still step forward so the Mac/Windows renderer works.
    try:
      while True:
        frame_start_time = time.time()
        if not FLAGS.realtime:
          controller.step()
        obs = controller.observe()

        if obs.player_result:
          break
        time.sleep(max(0, frame_start_time - time.time() + 1 / FLAGS.fps))
    except KeyboardInterrupt:
      pass

  for p in [host_proc, client_proc]:
    p.close()

  portspicker.return_ports(ports)

def entry_point():  # Needed so setup.py scripts work.
  app.run(main)

if __name__ == "__main__":
  app.run(main)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
'''
    :codeauthor: :email:`Pedro Algarvio (pedro@algarvio.me)`
    :copyright: © 2015 by the SaltStack Team, see AUTHORS for more details.
    :license: Apache 2.0, see LICENSE for more details.

    pytest_tempdir.version
    ~~~~~~~~~~~~~~~~~~~~~~

    pytest tempdir plugin version information
'''

# Import Python Libs
from __future__ import absolute_import

__version_info__ = (2015, 11, 4)
__version__ = '{0}.{1}.{2}'.format(*__version_info__)
<commit_msg>Bump version for release<commit_after># -*- coding: utf-8 -*-
'''
    :codeauthor: :email:`Pedro Algarvio (pedro@algarvio.me)`
    :copyright: © 2015 by the SaltStack Team, see AUTHORS for more details.
    :license: Apache 2.0, see LICENSE for more details.

    pytest_tempdir.version
    ~~~~~~~~~~~~~~~~~~~~~~

    pytest tempdir plugin version information
'''

# Import Python Libs
from __future__ import absolute_import

__version_info__ = (2015, 11, 6)
__version__ = '{0}.{1}.{2}'.format(*__version_info__)
<|endoftext|>