<commit_before>"""
Tools for file and directory management.
"""

import re

from invoke.vendor import six

from .util import set_runner

@set_runner
def directory(c, runner, path, user=None, group=None, mode=None):
    """
    Ensure a directory exists and has given user and/or mode
    """
    runner("mkdir -p {}".format(path))
    if user is not None:
        group = group or user
        runner("chown {}:{} {}".format(user, group, path))
    if mode is not None:
        runner("chmod {} {}".format(mode, path))

@set_runner
def exists(c, runner, path):
    """
    Return True if given path exists on the current remote host.
    """
    cmd = 'test -e "$(echo {})"'.format(path)
    return runner(cmd, hide=True, warn=True).succeeded

@set_runner
def contains(c, runner, filename, text, exact=False, escape=True):
    """
    Return True if ``filename`` contains ``text`` (which may be a regex.)

    By default, this function will consider a partial line match (i.e. where
    ``text`` only makes up part of the line it's on). Specify ``exact=True`` to
    change this behavior so that only a line containing exactly ``text``
    results in a True return value.

    This function leverages ``egrep`` on the remote end (so it may not follow
    Python regular expression syntax perfectly), and skips the usual outer
    ``env.shell`` wrapper that most commands execute with.

    If ``escape`` is False, no extra regular expression related escaping is
    performed (this includes overriding ``exact`` so that no ``^``/``$`` is
    added.)
    """
    if escape:
        text = _escape_for_regex(text)
        if exact:
            text = "^{}$".format(text)
    egrep_cmd = 'egrep "{}" "{}"'.format(text, filename)
    return runner(egrep_cmd, hide=True, warn=True).succeeded

@set_runner
def append(c, runner, filename, text, partial=False, escape=True):
    """
    Append string (or list of strings) ``text`` to ``filename``.

    When a list is given, each string inside is handled independently (but in
    the order given.)

    If ``text`` is already found in ``filename``, the append is not run, and
    None is returned immediately. Otherwise, the given text is appended to the
    end of the given ``filename`` via e.g. ``echo '$text' >> $filename``.

    The test for whether ``text`` already exists defaults to a full line match,
    e.g. ``^<text>$``, as this seems to be the most sensible approach for the
    "append lines to a file" use case. You may override this and force partial
    searching (e.g. ``^<text>``) by specifying ``partial=True``.

    Because ``text`` is single-quoted, single quotes will be transparently
    backslash-escaped. This can be disabled with ``escape=False``.
    """
    # Normalize non-list input to be a list
    if isinstance(text, six.string_types):
        text = [text]
    for line in text:
        regex = '^' + _escape_for_regex(line) + ('' if partial else '$')
        if (
            line and
            exists(c, filename, runner=runner) and
            contains(c, filename, regex, escape=False, runner=runner)
        ):
            continue
        line = line.replace("'", r"'\\''") if escape else line
        runner("echo '{}' >> {}".format(line, filename))

def _escape_for_regex(text):
    """Escape ``text`` to allow literal matching using egrep"""
    regex = re.escape(text)
    # Seems like double escaping is needed for \
    regex = regex.replace('\\\\', '\\\\\\')
    # Triple-escaping seems to be required for $ signs
    regex = regex.replace(r'\$', r'\\\$')
    # Whereas single quotes should not be escaped
    regex = regex.replace(r"\'", "'")
    return regex
<commit_msg>Replaced some calls to the removed succeeded attribute<commit_after>"""
Tools for file and directory management.
"""

import re

from invoke.vendor import six

from .util import set_runner

@set_runner
def directory(c, runner, path, user=None, group=None, mode=None):
    """
    Ensure a directory exists and has given user and/or mode
    """
    runner("mkdir -p {}".format(path))
    if user is not None:
        group = group or user
        runner("chown {}:{} {}".format(user, group, path))
    if mode is not None:
        runner("chmod {} {}".format(mode, path))

@set_runner
def exists(c, runner, path):
    """
    Return True if given path exists on the current remote host.
    """
    cmd = 'test -e "$(echo {})"'.format(path)
    return runner(cmd, hide=True, warn=True).ok

@set_runner
def contains(c, runner, filename, text, exact=False, escape=True):
    """
    Return True if ``filename`` contains ``text`` (which may be a regex.)

    By default, this function will consider a partial line match (i.e. where
    ``text`` only makes up part of the line it's on). Specify ``exact=True`` to
    change this behavior so that only a line containing exactly ``text``
    results in a True return value.

    This function leverages ``egrep`` on the remote end (so it may not follow
    Python regular expression syntax perfectly), and skips the usual outer
    ``env.shell`` wrapper that most commands execute with.

    If ``escape`` is False, no extra regular expression related escaping is
    performed (this includes overriding ``exact`` so that no ``^``/``$`` is
    added.)
    """
    if escape:
        text = _escape_for_regex(text)
        if exact:
            text = "^{}$".format(text)
    egrep_cmd = 'egrep "{}" "{}"'.format(text, filename)
    return runner(egrep_cmd, hide=True, warn=True).ok

@set_runner
def append(c, runner, filename, text, partial=False, escape=True):
    """
    Append string (or list of strings) ``text`` to ``filename``.

    When a list is given, each string inside is handled independently (but in
    the order given.)

    If ``text`` is already found in ``filename``, the append is not run, and
    None is returned immediately. Otherwise, the given text is appended to the
    end of the given ``filename`` via e.g. ``echo '$text' >> $filename``.

    The test for whether ``text`` already exists defaults to a full line match,
    e.g. ``^<text>$``, as this seems to be the most sensible approach for the
    "append lines to a file" use case. You may override this and force partial
    searching (e.g. ``^<text>``) by specifying ``partial=True``.

    Because ``text`` is single-quoted, single quotes will be transparently
    backslash-escaped. This can be disabled with ``escape=False``.
    """
    # Normalize non-list input to be a list
    if isinstance(text, six.string_types):
        text = [text]
    for line in text:
        regex = '^' + _escape_for_regex(line) + ('' if partial else '$')
        if (
            line and
            exists(c, filename, runner=runner) and
            contains(c, filename, regex, escape=False, runner=runner)
        ):
            continue
        line = line.replace("'", r"'\\''") if escape else line
        runner("echo '{}' >> {}".format(line, filename))

def _escape_for_regex(text):
    """Escape ``text`` to allow literal matching using egrep"""
    regex = re.escape(text)
    # Seems like double escaping is needed for \
    regex = regex.replace('\\\\', '\\\\\\')
    # Triple-escaping seems to be required for $ signs
    regex = regex.replace(r'\$', r'\\\$')
    # Whereas single quotes should not be escaped
    regex = regex.replace(r"\'", "'")
    return regex
<|endoftext|>


<commit_before>from fabric.api import run, settings, hide

def directory(d, user=None, group=None, mode=None, runner=run):
    """
    Ensure a directory exists and has given user and/or mode
    """
    runner("mkdir -p %s" % d)
    if user is not None:
        group = group or user
        runner("chown %s:%s %s" % (user, group, d))
    if mode is not None:
        runner("chmod %s %s" % (mode, d))

def exists(path, runner=run):
    """
    Return True if given path exists on the current remote host.
    """
    cmd = 'test -e "$(echo %s)"' % path
    with settings(hide('everything'), warn_only=True):
        return runner(cmd).succeeded
<commit_msg>Port contains/append from fabric.contrib<commit_after>import re

from fabric.api import run, settings, hide, run

def directory(d, user=None, group=None, mode=None, runner=run):
    """
    Ensure a directory exists and has given user and/or mode
    """
    runner("mkdir -p %s" % d)
    if user is not None:
        group = group or user
        runner("chown %s:%s %s" % (user, group, d))
    if mode is not None:
        runner("chmod %s %s" % (mode, d))

def exists(path, runner=run):
    """
    Return True if given path exists on the current remote host.
    """
    cmd = 'test -e "$(echo %s)"' % path
    with settings(hide('everything'), warn_only=True):
        return runner(cmd).succeeded

def contains(filename, text, exact=False, escape=True, runner=run):
    """
    Return True if ``filename`` contains ``text`` (which may be a regex.)

    By default, this function will consider a partial line match (i.e. where
    ``text`` only makes up part of the line it's on). Specify ``exact=True`` to
    change this behavior so that only a line containing exactly ``text``
    results in a True return value.

    This function leverages ``egrep`` on the remote end (so it may not follow
    Python regular expression syntax perfectly), and skips the usual outer
    ``env.shell`` wrapper that most commands execute with.

    If ``escape`` is False, no extra regular expression related escaping is
    performed (this includes overriding ``exact`` so that no ``^``/``$`` is
    added.)
    """
    if escape:
        text = _escape_for_regex(text)
        if exact:
            text = "^%s$" % text
    with settings(hide('everything'), warn_only=True):
        egrep_cmd = 'egrep "%s" "%s"' % (text, filename)
        return runner(egrep_cmd, shell=False).succeeded

def append(filename, text, partial=False, escape=True, runner=run):
    """
    Append string (or list of strings) ``text`` to ``filename``.

    When a list is given, each string inside is handled independently (but in
    the order given.)

    If ``text`` is already found in ``filename``, the append is not run, and
    None is returned immediately. Otherwise, the given text is appended to the
    end of the given ``filename`` via e.g. ``echo '$text' >> $filename``.

    The test for whether ``text`` already exists defaults to a full line match,
    e.g. ``^<text>$``, as this seems to be the most sensible approach for the
    "append lines to a file" use case. You may override this and force partial
    searching (e.g. ``^<text>``) by specifying ``partial=True``.

    Because ``text`` is single-quoted, single quotes will be transparently
    backslash-escaped. This can be disabled with ``escape=False``.
    """
    # Normalize non-list input to be a list
    if isinstance(text, basestring):
        text = [text]
    for line in text:
        regex = '^' + _escape_for_regex(line)  + ('' if partial else '$')
        if (exists(filename, runner=runner) and line
            and contains(filename, regex, escape=False, runner=runner)):
            continue
        line = line.replace("'", r"'\\''") if escape else line
        runner("echo '%s' >> %s" % (line, filename))

def _escape_for_regex(text):
    """Escape ``text`` to allow literal matching using egrep"""
    regex = re.escape(text)
    # Seems like double escaping is needed for \
    regex = regex.replace('\\\\', '\\\\\\')
    # Triple-escaping seems to be required for $ signs
    regex = regex.replace(r'\$', r'\\\$')
    # Whereas single quotes should not be escaped
    regex = regex.replace(r"\'", "'")
    return regex
<|endoftext|>


<commit_before>import numpy as np
from championinfo import championNameFromId, validChampionId

class DraftState:
    """
    Args:
        whichTeam (int) : indicator for which team we are drafting for (RED_TEAM or BLUE_TEAM)
        champIds (list(int)) : list of valid championids which are available for drafting.
        numPositions (int) : number of available positions to draft for. Default is 5 for a standard 5x5 draft.

    DraftState is the class responsible for holding and maintaining the current state of the draft. For a given champion with championid c,
    that champion's state with respect to the draft is at most one of:
        - c is banned from selection.
        - c is selected as part of the opponent's team.
        - c is selected as one of our team's position.

     The state of the draft will be stored as a (numChampions) x (numRoles+2) numPy array. If state(c,k) = 1 then:
        - k = 0 -> champion c is banned from selection.
        - k = 1 -> champion c is selected as part of the enemy team.
        - 2 <= k = numPositions+1 -> champion c is selected as position k-1 in our draft.

    Default draft positions are interpreted as:
        Position 1 -> ADC/Marksman (Primary farm)
        Position 2 -> Middle (Secondary farm)
        Position 3 -> Top (Tertiary farm)
        Position 4 -> Jungle (Farming support)
        Position 5 -> Support (Primary support)
    """
    # State codes
    BAN_SELECTED = 101
    DUPLICATE_SELECTION = 102
    DUPLICATE_ROLE = 103
    invalidStates = [BAN_SELECTED, DUPLICATE_ROLE, DUPLICATE_SELECTION]

    DRAFT_COMPLETE = 1
    #TODO (Devin): For 'BAN' draft mode,
    DRAFT_MODE = 'BAN'
    BLUE_TEAM = 0
    RED_TEAM = 1

    def __init__(self, whichTeam, champIds, numPositions = 5):
        #TODO (Devin): This should make sure that numChampions >= numPositions
        self.numChampions = len(champIds)
        self.numPositions = numPositions
        self.stateIndexToChampId = {i:k for i,k in zip(range(self.numChampions),champIds)}
        self.champIdToStateIndex = {k:i for i,k in zip(range(self.numChampions),champIds)}
        self.state = np.zeros((self.numChampions, self.numPositions+2), dtype=bool)
        self.picks = []
        self.bans = []

        #TODO (Devin): This should check for an invalid team passed
        self.team = whichTeam

    def stateIndexToChampId(self,index):
        """
        stateIndexToChampId returns the valid champion ID corresponding to the given state index. Since champion IDs are not contiguously defined or even necessarily ordered,
        this mapping will not be trivial. If index is invalid, returns -1.
        Args:
            index (int): location index in the state array of the desired champion.
        Returns:
            champId (int): champion ID corresponding to index (as defined by champIds)
        """
        if index not in self.stateIndexToChampId.keys():
            return -1
        return self.stateIndexToChampId[index]

    def champIdToStateIndex(self,champid):
        """
        champIdTostateIndex returns the state index corresponding to the given champion ID. Since champion IDs are not contiguously defined or even necessarily ordered,
        this mapping will not be trivial. If champid is invalid, returns -1.
        Args:
            champid (int): id of champion to look up
        Returns
            index (int): state index of corresponding champion id
        """
        if champid not in self.champIdToStateIndex.keys():
            return -1
        return self.champIdToStateIndex[champid]

    def formatState(self):
        """
        Format the state array into a vector so the Q-network can process it.
        Args:
            None
        Returns:
            A copy of self.state reshaped as a numpy vector of length numChampions*(numRoles+2)
        """
        return np.reshape(self.state,self.numChampions*(self.numRoles+2))

    def formatAction(self,action):
        """
        Format input action into the corresponding tuple (champid, position) which indexes the state array.
        Args:
            action (int): Action to be interpreted as an index into the state array.
        Returns:
            (championId, position) (tuple of ints): Tuple of integer values which may be passed as arguments to either
            self.addPick() or self.addBan() depending on the value of position. If position = -1 -> action is a ban otherwise action
            is a pick.
        """
        (champId,pos) = np.unravel_index(action,self.state.shape)
        champId += 1
        pos -= 1
        return (champId,pos)

    def updateState(self, championId, position):
        """
        Attempt to update the current state of the draft and pick/ban lists with a given championId.
        Returns: True is selection was successful, False otherwise
        Args:
            championId (int): Id of champion to add to pick list.
            position (int): Position of champion to be selected. The value of position determines if championId is interpreted as a pick or ban:
                position = -1 -> champion ban submitted.
                position = 0 -> champion selection submitted by the opposing team.
                0 < position <= numPositions -> champion selection submitted by our team for pos = position
        """
        #TODO: Currently getRewards() does not work correctly if invalid picks are blocked from selection. This should be fixed later.
        #if(not self.canPick(championId) or (position < -1) or (position > self.numPositions)):
        #    return False

        # Devin: As is, our input formatting of championId & position allows for submitted ally picks
        # of the form (champId, pos) to correspond with the selection champion = championId in position = pos. However, this is *not* how they are stored in the state
        # array. Furthermore this also forces bans to be given pos = -1 and enemy picks pos = 0. Finally this doesn't match indexing used for state array and action vector indexing
        # (which follow state indexing).

        if((position < -1) or (position > self.numPositions) or (not validChampionId(championId))):
            return False

        if(position == -1):
            self.bans.append(championId)
        else:
            self.picks.append(championId)
        index = self.champIdToStateIndex[championId]
        self.state[index,position+1] = True
        return True

    def displayState(self):
        #TODO (Devin): Clean up displayState to make it prettier.
        print("Currently there are {numPicks} picks and {numBans} bans completed in this draft. \n".format(numPicks=len(self.picks),numBans=len(self.bans)))

        print("Banned Champions: {0}".format(list(map(championNameFromId, self.bans))))
        enemyDraftIds = [x+1 for x in np.where(self.state[:,1])] # Convert index locations in state to correct championIds
        print("Enemy Draft: {0}".format(list(map(championNameFromId,enemyDraftIds[0]))))

        print("Our Draft:")
        for posIndex in range(2,len(self.state[0,:])): # Iterate through each position column in state
            champ = np.where(self.state[:,posIndex])[0] # Find non-zero index
            if not champ.size: # No pick is found for this position, create a filler string
                draftName = "--"
            else:
                draftName = championNameFromId(int(champ[0])+1)
            print("Position {p}: {c}".format(p=posIndex-1,c=draftName))
        print("\n")

    def canPick(self, championId):
        """
        Check to see if a champion is available to be selected.
        Returns: True if champion is a valid selection, False otherwise.
        Args:
            championId (int): Id of champion to check for valid selection.
        """
        return ((championId not in self.picks) and championinfo.validChampionId(championId))

    def canBan(self, championId):
        """
        Check to see if a champion is available to be banned.
        Returns: True if champion is a valid ban, False otherwise.
        Args:
            championId (int): Id of champion to check for valid ban.
        """
        return ((championId not in self.bans) and championinfo.validChampionId(championId))

    def addPick(self, championId, position):
        """
        Attempt to add a champion to the selected champion list and update the state.
        Returns: True is selection was successful, False otherwise
        Args:
            championId (int): Id of champion to add to pick list.
            position (int): Position of champion to be selected. If position = 0 this is interpreted as a selection submitted by the opposing team.
        """
        #TODO: Currently getRewards() does not work correctly if invalid picks are blocked from selection. This should be fixed later.
        #if(not self.canPick(championId) or (position < 0) or (position > self.numPositions)):
        #    return False
        if((position < 0) or (position > self.numPositions) or (not validChampionId(championId))):
            return False
        self.picks.append(championId)
        self.state[championId-1,position+1] = True
        return True

    def addBan(self, championId):
        """
        Attempt to add a champion to the banned champion list and update the state.
        Returns: True is ban was successful, False otherwise
        Args:
            championId (int): Id of champion to add to bans.
        """
        #TODO: Currently getRewards() does not work correctly if invalid bans are blocked from selection. This should be fixed later.
        #if(not self.canBan(championId)):
        #    return False
        if(not validChampionId(championId)):
            return False
        self.bans.append(championId)
        self.state[championId-1,0] = True
        return True

    def evaluateState(self):
        """
        evaluateState checks the current state and determines if the draft as it is currently recorded is valid.
        Returns: value (int) - code indicating validitiy of state
            Valid codes:
                value = 0 -> state is valid but incomplete.
                value = DRAFT_COMPLETE -> state is valid and complete.
            Invalid codes:
                value = BAN_SELECTED -> state has a banned champion selected for draft.
                value = DUPLICATE_SELECTION -> state has a champion drafted which is already part of the opposing team.
                value = DUPLICATE_ROLE -> state has a champion selected for multiple roles (champion selected more than once).
        """
        for champIndex in range(len(self.state[:,0])):
            loc = np.argwhere(self.state[champIndex,:])
            if(len(loc)>1): # State is invalid, check where problem is
                errIndex = int(loc[0]) # Location of first non-zero index fo
                if(errIndex==0):
                    return DraftState.BAN_SELECTED # Invalid state includes an already banned champion
                elif(errIndex==1):
                    return DraftState.DUPLICATE_SELECTION # Invalid state includes a champion which was selected for the other team
                else:
                    return DraftState.DUPLICATE_ROLE # Invalid state includes a champion which has been selected for multiple roles
        # State is valid, check if draft is complete
        numEnemyPicks = np.sum(self.state[:,1])
        numAllyPicks = np.sum(self.state[:,2:])
        if(numAllyPicks == self.numPositions and numEnemyPicks == self.numPositions):
            return DraftState.DRAFT_COMPLETE # Draft is valid and complete
        if(DraftState.DRAFT_MODE == 'BAN' and len(self.bans) == 6):
            return DraftState.DRAFT_COMPLETE # For ban-only drafts, stop once the bans are registered.

        # Draft is valid, but not complete
        return 0 <commit_msg>Fixed typo in draftState<commit_after>import numpy as np
from championinfo import championNameFromId, validChampionId

class DraftState:
    """
    Args:
        whichTeam (int) : indicator for which team we are drafting for (RED_TEAM or BLUE_TEAM)
        champIds (list(int)) : list of valid championids which are available for drafting.
        numPositions (int) : number of available positions to draft for. Default is 5 for a standard 5x5 draft.

    DraftState is the class responsible for holding and maintaining the current state of the draft. For a given champion with championid c,
    that champion's state with respect to the draft is at most one of:
        - c is banned from selection.
        - c is selected as part of the opponent's team.
        - c is selected as one of our team's position.

     The state of the draft will be stored as a (numChampions) x (numRoles+2) numPy array. If state(c,k) = 1 then:
        - k = 0 -> champion c is banned from selection.
        - k = 1 -> champion c is selected as part of the enemy team.
        - 2 <= k = numPositions+1 -> champion c is selected as position k-1 in our draft.

    Default draft positions are interpreted as:
        Position 1 -> ADC/Marksman (Primary farm)
        Position 2 -> Middle (Secondary farm)
        Position 3 -> Top (Tertiary farm)
        Position 4 -> Jungle (Farming support)
        Position 5 -> Support (Primary support)
    """
    # State codes
    BAN_SELECTED = 101
    DUPLICATE_SELECTION = 102
    DUPLICATE_ROLE = 103
    invalidStates = [BAN_SELECTED, DUPLICATE_ROLE, DUPLICATE_SELECTION]

    DRAFT_COMPLETE = 1
    #TODO (Devin): For 'BAN' draft mode,
    DRAFT_MODE = 'BAN'
    BLUE_TEAM = 0
    RED_TEAM = 1

    def __init__(self, whichTeam, champIds, numPositions = 5):
        #TODO (Devin): This should make sure that numChampions >= numPositions
        self.numChampions = len(champIds)
        self.numPositions = numPositions
        self.stateIndexToChampId = {i:k for i,k in zip(range(self.numChampions),champIds)}
        self.champIdToStateIndex = {k:i for i,k in zip(range(self.numChampions),champIds)}
        self.state = np.zeros((self.numChampions, self.numPositions+2), dtype=bool)
        self.picks = []
        self.bans = []

        #TODO (Devin): This should check for an invalid team passed
        self.team = whichTeam

    def stateIndexToChampId(self,index):
        """
        stateIndexToChampId returns the valid champion ID corresponding to the given state index. Since champion IDs are not contiguously defined or even necessarily ordered,
        this mapping will not be trivial. If index is invalid, returns -1.
        Args:
            index (int): location index in the state array of the desired champion.
        Returns:
            champId (int): champion ID corresponding to index (as defined by champIds)
        """
        if index not in self.stateIndexToChampId.keys():
            return -1
        return self.stateIndexToChampId[index]

    def champIdToStateIndex(self,champid):
        """
        champIdTostateIndex returns the state index corresponding to the given champion ID. Since champion IDs are not contiguously defined or even necessarily ordered,
        this mapping will not be trivial. If champid is invalid, returns -1.
        Args:
            champid (int): id of champion to look up
        Returns
            index (int): state index of corresponding champion id
        """
        if champid not in self.champIdToStateIndex.keys():
            return -1
        return self.champIdToStateIndex[champid]

    def formatState(self):
        """
        Format the state array into a vector so the Q-network can process it.
        Args:
            None
        Returns:
            A copy of self.state reshaped as a numpy vector of length numChampions*(numPositions+2)
        """
        return np.reshape(self.state,self.numChampions*(self.numPositions+2))

    def formatAction(self,action):
        """
        Format input action into the corresponding tuple (champid, position) which indexes the state array.
        Args:
            action (int): Action to be interpreted as an index into the state array.
        Returns:
            (championId, position) (tuple of ints): Tuple of integer values which may be passed as arguments to either
            self.addPick() or self.addBan() depending on the value of position. If position = -1 -> action is a ban otherwise action
            is a pick.
        """
        (champId,pos) = np.unravel_index(action,self.state.shape)
        champId += 1
        pos -= 1
        return (champId,pos)

    def updateState(self, championId, position):
        """
        Attempt to update the current state of the draft and pick/ban lists with a given championId.
        Returns: True is selection was successful, False otherwise
        Args:
            championId (int): Id of champion to add to pick list.
            position (int): Position of champion to be selected. The value of position determines if championId is interpreted as a pick or ban:
                position = -1 -> champion ban submitted.
                position = 0 -> champion selection submitted by the opposing team.
                0 < position <= numPositions -> champion selection submitted by our team for pos = position
        """
        #TODO: Currently getRewards() does not work correctly if invalid picks are blocked from selection. This should be fixed later.
        #if(not self.canPick(championId) or (position < -1) or (position > self.numPositions)):
        #    return False

        # Devin: As is, our input formatting of championId & position allows for submitted ally picks
        # of the form (champId, pos) to correspond with the selection champion = championId in position = pos. However, this is *not* how they are stored in the state
        # array. Furthermore this also forces bans to be given pos = -1 and enemy picks pos = 0. Finally this doesn't match indexing used for state array and action vector indexing
        # (which follow state indexing).

        if((position < -1) or (position > self.numPositions) or (not validChampionId(championId))):
            return False

        if(position == -1):
            self.bans.append(championId)
        else:
            self.picks.append(championId)
        index = self.champIdToStateIndex[championId]
        self.state[index,position+1] = True
        return True

    def displayState(self):
        #TODO (Devin): Clean up displayState to make it prettier.
        print("Currently there are {numPicks} picks and {numBans} bans completed in this draft. \n".format(numPicks=len(self.picks),numBans=len(self.bans)))

        print("Banned Champions: {0}".format(list(map(championNameFromId, self.bans))))
        enemyDraftIds = [x+1 for x in np.where(self.state[:,1])] # Convert index locations in state to correct championIds
        print("Enemy Draft: {0}".format(list(map(championNameFromId,enemyDraftIds[0]))))

        print("Our Draft:")
        for posIndex in range(2,len(self.state[0,:])): # Iterate through each position column in state
            champ = np.where(self.state[:,posIndex])[0] # Find non-zero index
            if not champ.size: # No pick is found for this position, create a filler string
                draftName = "--"
            else:
                draftName = championNameFromId(int(champ[0])+1)
            print("Position {p}: {c}".format(p=posIndex-1,c=draftName))
        print("\n")

    def canPick(self, championId):
        """
        Check to see if a champion is available to be selected.
        Returns: True if champion is a valid selection, False otherwise.
        Args:
            championId (int): Id of champion to check for valid selection.
        """
        return ((championId not in self.picks) and championinfo.validChampionId(championId))

    def canBan(self, championId):
        """
        Check to see if a champion is available to be banned.
        Returns: True if champion is a valid ban, False otherwise.
        Args:
            championId (int): Id of champion to check for valid ban.
        """
        return ((championId not in self.bans) and championinfo.validChampionId(championId))

    def addPick(self, championId, position):
        """
        Attempt to add a champion to the selected champion list and update the state.
        Returns: True is selection was successful, False otherwise
        Args:
            championId (int): Id of champion to add to pick list.
            position (int): Position of champion to be selected. If position = 0 this is interpreted as a selection submitted by the opposing team.
        """
        #TODO: Currently getRewards() does not work correctly if invalid picks are blocked from selection. This should be fixed later.
        #if(not self.canPick(championId) or (position < 0) or (position > self.numPositions)):
        #    return False
        if((position < 0) or (position > self.numPositions) or (not validChampionId(championId))):
            return False
        self.picks.append(championId)
        self.state[championId-1,position+1] = True
        return True

    def addBan(self, championId):
        """
        Attempt to add a champion to the banned champion list and update the state.
        Returns: True is ban was successful, False otherwise
        Args:
            championId (int): Id of champion to add to bans.
        """
        #TODO: Currently getRewards() does not work correctly if invalid bans are blocked from selection. This should be fixed later.
        #if(not self.canBan(championId)):
        #    return False
        if(not validChampionId(championId)):
            return False
        self.bans.append(championId)
        self.state[championId-1,0] = True
        return True

    def evaluateState(self):
        """
        evaluateState checks the current state and determines if the draft as it is currently recorded is valid.
        Returns: value (int) - code indicating validitiy of state
            Valid codes:
                value = 0 -> state is valid but incomplete.
                value = DRAFT_COMPLETE -> state is valid and complete.
            Invalid codes:
                value = BAN_SELECTED -> state has a banned champion selected for draft.
                value = DUPLICATE_SELECTION -> state has a champion drafted which is already part of the opposing team.
                value = DUPLICATE_ROLE -> state has a champion selected for multiple roles (champion selected more than once).
        """
        for champIndex in range(len(self.state[:,0])):
            loc = np.argwhere(self.state[champIndex,:])
            if(len(loc)>1): # State is invalid, check where problem is
                errIndex = int(loc[0]) # Location of first non-zero index fo
                if(errIndex==0):
                    return DraftState.BAN_SELECTED # Invalid state includes an already banned champion
                elif(errIndex==1):
                    return DraftState.DUPLICATE_SELECTION # Invalid state includes a champion which was selected for the other team
                else:
                    return DraftState.DUPLICATE_ROLE # Invalid state includes a champion which has been selected for multiple roles
        # State is valid, check if draft is complete
        numEnemyPicks = np.sum(self.state[:,1])
        numAllyPicks = np.sum(self.state[:,2:])
        if(numAllyPicks == self.numPositions and numEnemyPicks == self.numPositions):
            return DraftState.DRAFT_COMPLETE # Draft is valid and complete
        if(DraftState.DRAFT_MODE == 'BAN' and len(self.bans) == 6):
            return DraftState.DRAFT_COMPLETE # For ban-only drafts, stop once the bans are registered.

        # Draft is valid, but not complete
        return 0 <|endoftext|>


<commit_before>import collections.abc

import declare

from .conditions import ComparisonMixin
from .exceptions import InvalidIndex, InvalidModel, InvalidStream
from .signals import model_created, object_modified, table_validated
from .util import missing, printable_column_name, unpack_from_dynamodb

__all__ = ["BaseModel", "Column", "GlobalSecondaryIndex", "LocalSecondaryIndex"]

@table_validated.connect
def on_table_validated(_, model, actual_description, **kwargs):
    if model.Meta.stream:
        model.Meta.stream["arn"] = actual_description["LatestStreamArn"]

def loaded_columns(obj):
    """Yields each (model_name, value) tuple for all columns in an object that aren't missing"""
    for column in sorted(obj.Meta.columns, key=lambda c: c.model_name):
        value = getattr(obj, column.model_name, missing)
        if value is not missing:
            yield column.model_name, value

def validate_projection(projection):
    validated_projection = {
        "mode": None,
        "included": None,
        "available": None,
        "strict": True
    }

    # String check first since it is also an Iterable.
    # Without this, the following will make "unknown" a list
    if isinstance(projection, str):
        if projection not in ("keys", "all"):
            raise InvalidIndex("{!r} is not a valid Index projection.".format(projection))
        validated_projection["mode"] = projection
    elif isinstance(projection, collections.abc.Iterable):
        projection = list(projection)
        # These checks aren't done together; that would allow a mix
        # of column instances and column names.  There aren't any cases
        # where a mix is required, over picking a style.  Much more likely,
        # the user is trying to do something odd and doesn't understand what
        # the index projection means.
        if (
                all(isinstance(p, str) for p in projection) or
                all(isinstance(p, Column) for p in projection)):
            validated_projection["mode"] = "include"
            validated_projection["included"] = projection
        else:
            raise InvalidIndex(
                "Index projection must be a list of strings or Columns to select specific Columns.")
    else:
        raise InvalidIndex(
            "Index projection must be 'all', 'keys', or a list of Columns or Column names.")
    return validated_projection

def validate_stream(stream):
    if stream is None:
        return

    if not isinstance(stream, collections.abc.MutableMapping):
        raise InvalidStream("Stream must be None or a dict.")

    if "include" not in stream:
        raise InvalidStream("Specify what the stream will return with the 'include' key.")
    include = stream["include"] = set(stream["include"])

    # []
    if not include:
        raise InvalidStream("Must include at least one of 'keys', 'old', or 'new'.")

    # ["what is this", "keys"]
    for value in include:
        if value not in {"new", "keys", "old"}:
            raise InvalidStream("Streams can only contain 'keys', 'old', and/or 'new'.")

    # ["keys", "old"]
    if include == {"new", "keys"} or include == {"old", "keys"}:
        raise InvalidStream("The option 'keys' cannot be used with either 'old' or 'new'.")
    stream["arn"] = None

class ModelMetaclass(declare.ModelMetaclass):
    def __new__(mcs, name, bases, attrs):
        model = super().__new__(mcs, name, bases, attrs)
        meta = model.Meta
        meta.model = model
        # new_class will set abstract to true, all other models are assumed
        # to be concrete unless specified
        setdefault(meta, "abstract", False)
        setdefault(meta, "write_units", 1)
        setdefault(meta, "read_units", 1)

        setup_columns(meta)
        setup_indexes(meta)

        # Entry point for model population. By default this is the
        # class's __init__ function. Custom models can specify the
        # Meta attr `init`, which must be a function taking no
        # arguments that returns an instance of the class
        setdefault(meta, "init", model)
        setdefault(meta, "table_name", model.__name__)
        setdefault(meta, "stream", None)

        validate_stream(meta.stream)

        model_created.send(model=model)
        return model

    def __repr__(cls):
        return "<Model[{}]>".format(cls.__name__)

def setdefault(obj, field, default):
    """Set an object's field to default if it doesn't have a value"""
    setattr(obj, field, getattr(obj, field, default))

def setup_columns(meta):
    """Filter columns from fields, identify hash and range keys"""

    # This is a set instead of a list, because set uses __hash__
    # while some list operations uses __eq__ which will break
    # with the ComparisonMixin
    meta.columns = set(filter(
        lambda field: isinstance(field, Column), meta.fields))

    meta.hash_key = None
    meta.range_key = None
    meta.keys = set()

    if not meta.abstract:
        cls_name = meta.model.__name__

        hash_keys = [c for c in meta.columns if c.hash_key]
        range_keys = [c for c in meta.columns if c.range_key]

        if len(hash_keys) == 0:
            raise InvalidModel("{!r} has no hash key.".format(cls_name))
        elif len(hash_keys) > 1:
            raise InvalidModel("{!r} has more than one hash key.".format(cls_name))

        if len(range_keys) > 1:
            raise InvalidModel("{!r} has more than one range key.".format(cls_name))

        if range_keys:
            if hash_keys[0] is range_keys[0]:
                raise InvalidModel("{!r} has the same hash and range key.".format(cls_name))
            meta.range_key = range_keys[0]
            meta.keys.add(meta.range_key)
        meta.hash_key = hash_keys[0]
        meta.keys.add(meta.hash_key)

    for column in meta.columns:
        column.model = meta.model

    # API consistency with an Index, so (index or model.Meta) can be
    # used interchangeably to get the available columns from that
    # object.
    meta.projection = {
        "mode": "all",
        "included": meta.columns,
        "available": meta.columns,
        "strict": True
    }

def setup_indexes(meta):
    """Filter indexes from fields, compute projection for each index"""
    # Don't put these in the metadata until they bind successfully.
    gsis = set(filter(lambda field: isinstance(field, GlobalSecondaryIndex), meta.fields))
    lsis = set(filter(lambda field: isinstance(field, LocalSecondaryIndex), meta.fields))
    indexes = set.union(gsis, lsis)

    for index in indexes:
        index._bind(meta.model)

    meta.gsis = gsis
    meta.lsis = lsis
    meta.indexes = indexes

class BaseModel(metaclass=ModelMetaclass):
    """An unbound, abstract base model"""
    class Meta:
        abstract = True

    def __init__(self, **attrs):
        # Only set values from **attrs if there's a
        # corresponding `model_name` for a column in the model
        for column in self.Meta.columns:
            value = attrs.get(column.model_name, missing)
            if value is not missing:
                setattr(self, column.model_name, value)

    @classmethod
    def _load(cls, attrs, *, context, **kwargs):
        """ dict (dynamo name) -> obj """
        return unpack_from_dynamodb(
            model=cls,
            attrs=attrs or {},
            expected=cls.Meta.columns,
            context=context, **kwargs)

    @classmethod
    def _dump(cls, obj, *, context, **kwargs):
        """ obj -> dict """
        if obj is None:
            return None
        dump = context["engine"]._dump
        filtered = filter(
            lambda item: item[1] is not None,
            ((
                column.dynamo_name,
                dump(column.typedef, getattr(obj, column.model_name, None), context=context, **kwargs)
            ) for column in cls.Meta.columns))
        return dict(filtered) or None

    @classmethod
    def _register(cls, type_engine):
        for column in cls.Meta.columns:
            type_engine.register(column.typedef)

    def __repr__(self):
        attrs = ", ".join("{}={}".format(*item) for item in loaded_columns(self))
        return "{}({})".format(self.__class__.__name__, attrs)

class Index(declare.Field):
    def __init__(self, *, projection, hash_key=None, range_key=None, name=None, **kwargs):
        self.model = None
        self.hash_key = hash_key
        self.range_key = range_key
        self._dynamo_name = name
        super().__init__(**kwargs)

        self.projection = validate_projection(projection)

    def __repr__(self):
        if isinstance(self, LocalSecondaryIndex):
            cls_name = "LSI"
        elif isinstance(self, GlobalSecondaryIndex):
            cls_name = "GSI"
        else:
            cls_name = self.__class__.__name__

        # <GSI[User.by_email=all]>
        # <GSI[User.by_email=keys]>
        # <LSI[User.by_email=include]>
        return "<{}[{}.{}={}]>".format(
            cls_name,
            self.model.__name__, self.model_name,
            self.projection["mode"]
        )

    @property
    def dynamo_name(self):
        if self._dynamo_name is None:
            return self.model_name
        return self._dynamo_name

    def _bind(self, model):
        """Set up hash, range keys and compute projection"""
        self.model = model

        # Index by model_name so we can replace hash_key, range_key with the proper `bloop.Column` object
        columns = declare.index(model.Meta.columns, "model_name")
        if isinstance(self.hash_key, str):
            self.hash_key = columns[self.hash_key]
        if not isinstance(self.hash_key, Column):
            raise InvalidIndex("Index hash key must be a Column or Column model name.")
        if self.range_key:
            if isinstance(self.range_key, str):
                self.range_key = columns[self.range_key]
            if not isinstance(self.range_key, Column):
                raise InvalidIndex("Index range key (if provided) must be a Column or Column model name.")

        self.keys = {self.hash_key}
        if self.range_key:
            self.keys.add(self.range_key)

        # Compute and the projected columns
        # All projections include model + index keys
        projection_keys = set.union(model.Meta.keys, self.keys)

        if self.projection["mode"] == "keys":
            self.projection["included"] = projection_keys
        elif self.projection["mode"] == "all":
            self.projection["included"] = model.Meta.columns
        elif self.projection["mode"] == "include":  # pragma: no branch
            # model_name -> Column
            if all(isinstance(p, str) for p in self.projection["included"]):
                projection = set(columns[name] for name in self.projection["included"])
            else:
                projection = set(self.projection["included"])
            projection.update(projection_keys)
            self.projection["included"] = projection

        # Strict has the same availability as the included columns,
        # while non-strict has access to the full range of columns
        if self.projection["strict"]:
            self.projection["available"] = self.projection["included"]
        else:
            self.projection["available"] = model.Meta.columns

    # TODO: disallow set/get/del for an index; these don't store values.  Raise AttributeError.

class GlobalSecondaryIndex(Index):
    def __init__(self, *, projection, hash_key, range_key=None, read_units=1, write_units=1, name=None, **kwargs):
        super().__init__(hash_key=hash_key, range_key=range_key, name=name, projection=projection, **kwargs)
        self.write_units = write_units
        self.read_units = read_units

class LocalSecondaryIndex(Index):
    """ LSIs don't have individual read/write units """
    def __init__(self, *, projection, range_key, name=None, strict=True, **kwargs):
        # Hash key MUST be the table hash; do not specify
        if "hash_key" in kwargs:
            raise InvalidIndex("An LSI shares its hash key with the Model.")
        if ("write_units" in kwargs) or ("read_units" in kwargs):
            raise InvalidIndex("An LSI shares its provisioned throughput with the Model.")
        super().__init__(range_key=range_key, name=name, projection=projection, **kwargs)
        self.projection["strict"] = strict

    def _bind(self, model):
        """Raise if the model doesn't have a range key"""
        if not model.Meta.range_key:
            raise InvalidIndex("An LSI requires the Model to have a range key.")
        # this is model_name (string) because super()._bind will do the string -> Column lookup
        self.hash_key = model.Meta.hash_key.model_name
        super()._bind(model)

    @property
    def read_units(self):
        return self.model.Meta.read_units

    @read_units.setter
    def read_units(self, value):
        self.model.Meta.read_units = value

    @property
    def write_units(self):
        return self.model.Meta.write_units

    @write_units.setter
    def write_units(self, value):
        self.model.Meta.write_units = value

class Column(declare.Field, ComparisonMixin):
    def __init__(self, typedef, hash_key=None, range_key=None,
                 name=None, **kwargs):
        self.hash_key = hash_key
        self.range_key = range_key
        self._dynamo_name = name
        kwargs['typedef'] = typedef
        super().__init__(**kwargs)

    __hash__ = object.__hash__

    def _repr_with_path(self, path):
        if self.hash_key:
            extra = "=hash"
        elif self.range_key:
            extra = "=range"
        else:
            extra = ""

        # <Column[Pin.url]>
        # <Column[User.id=hash]>
        # <Column[File.fragment=range]>
        return "<Column[{}.{}{}]>".format(self.model.__name__, printable_column_name(self, path), extra)

    @property
    def dynamo_name(self):
        if self._dynamo_name is None:
            return self.model_name
        return self._dynamo_name

    def set(self, obj, value):
        super().set(obj, value)
        # Notify the tracking engine that this value was intentionally mutated
        object_modified.send(obj=obj, column=self, value=value)

    def delete(self, obj):
        try:
            super().delete(obj)
        finally:
            # Unlike set, we always want to mark on delete.  If we didn't, and the column wasn't loaded
            # (say from a query) then the intention "ensure this doesn't have a value" wouldn't be captured.
            object_modified.send(obj=obj, column=self, value=None)
<commit_msg>use repr in model repr<commit_after>import collections.abc

import declare

from .conditions import ComparisonMixin
from .exceptions import InvalidIndex, InvalidModel, InvalidStream
from .signals import model_created, object_modified, table_validated
from .util import missing, printable_column_name, unpack_from_dynamodb

__all__ = ["BaseModel", "Column", "GlobalSecondaryIndex", "LocalSecondaryIndex"]

@table_validated.connect
def on_table_validated(_, model, actual_description, **kwargs):
    if model.Meta.stream:
        model.Meta.stream["arn"] = actual_description["LatestStreamArn"]

def loaded_columns(obj):
    """Yields each (model_name, value) tuple for all columns in an object that aren't missing"""
    for column in sorted(obj.Meta.columns, key=lambda c: c.model_name):
        value = getattr(obj, column.model_name, missing)
        if value is not missing:
            yield column.model_name, value

def validate_projection(projection):
    validated_projection = {
        "mode": None,
        "included": None,
        "available": None,
        "strict": True
    }

    # String check first since it is also an Iterable.
    # Without this, the following will make "unknown" a list
    if isinstance(projection, str):
        if projection not in ("keys", "all"):
            raise InvalidIndex("{!r} is not a valid Index projection.".format(projection))
        validated_projection["mode"] = projection
    elif isinstance(projection, collections.abc.Iterable):
        projection = list(projection)
        # These checks aren't done together; that would allow a mix
        # of column instances and column names.  There aren't any cases
        # where a mix is required, over picking a style.  Much more likely,
        # the user is trying to do something odd and doesn't understand what
        # the index projection means.
        if (
                all(isinstance(p, str) for p in projection) or
                all(isinstance(p, Column) for p in projection)):
            validated_projection["mode"] = "include"
            validated_projection["included"] = projection
        else:
            raise InvalidIndex(
                "Index projection must be a list of strings or Columns to select specific Columns.")
    else:
        raise InvalidIndex(
            "Index projection must be 'all', 'keys', or a list of Columns or Column names.")
    return validated_projection

def validate_stream(stream):
    if stream is None:
        return

    if not isinstance(stream, collections.abc.MutableMapping):
        raise InvalidStream("Stream must be None or a dict.")

    if "include" not in stream:
        raise InvalidStream("Specify what the stream will return with the 'include' key.")
    include = stream["include"] = set(stream["include"])

    # []
    if not include:
        raise InvalidStream("Must include at least one of 'keys', 'old', or 'new'.")

    # ["what is this", "keys"]
    for value in include:
        if value not in {"new", "keys", "old"}:
            raise InvalidStream("Streams can only contain 'keys', 'old', and/or 'new'.")

    # ["keys", "old"]
    if include == {"new", "keys"} or include == {"old", "keys"}:
        raise InvalidStream("The option 'keys' cannot be used with either 'old' or 'new'.")
    stream["arn"] = None

class ModelMetaclass(declare.ModelMetaclass):
    def __new__(mcs, name, bases, attrs):
        model = super().__new__(mcs, name, bases, attrs)
        meta = model.Meta
        meta.model = model
        # new_class will set abstract to true, all other models are assumed
        # to be concrete unless specified
        setdefault(meta, "abstract", False)
        setdefault(meta, "write_units", 1)
        setdefault(meta, "read_units", 1)

        setup_columns(meta)
        setup_indexes(meta)

        # Entry point for model population. By default this is the
        # class's __init__ function. Custom models can specify the
        # Meta attr `init`, which must be a function taking no
        # arguments that returns an instance of the class
        setdefault(meta, "init", model)
        setdefault(meta, "table_name", model.__name__)
        setdefault(meta, "stream", None)

        validate_stream(meta.stream)

        model_created.send(model=model)
        return model

    def __repr__(cls):
        return "<Model[{}]>".format(cls.__name__)

def setdefault(obj, field, default):
    """Set an object's field to default if it doesn't have a value"""
    setattr(obj, field, getattr(obj, field, default))

def setup_columns(meta):
    """Filter columns from fields, identify hash and range keys"""

    # This is a set instead of a list, because set uses __hash__
    # while some list operations uses __eq__ which will break
    # with the ComparisonMixin
    meta.columns = set(filter(
        lambda field: isinstance(field, Column), meta.fields))

    meta.hash_key = None
    meta.range_key = None
    meta.keys = set()

    if not meta.abstract:
        cls_name = meta.model.__name__

        hash_keys = [c for c in meta.columns if c.hash_key]
        range_keys = [c for c in meta.columns if c.range_key]

        if len(hash_keys) == 0:
            raise InvalidModel("{!r} has no hash key.".format(cls_name))
        elif len(hash_keys) > 1:
            raise InvalidModel("{!r} has more than one hash key.".format(cls_name))

        if len(range_keys) > 1:
            raise InvalidModel("{!r} has more than one range key.".format(cls_name))

        if range_keys:
            if hash_keys[0] is range_keys[0]:
                raise InvalidModel("{!r} has the same hash and range key.".format(cls_name))
            meta.range_key = range_keys[0]
            meta.keys.add(meta.range_key)
        meta.hash_key = hash_keys[0]
        meta.keys.add(meta.hash_key)

    for column in meta.columns:
        column.model = meta.model

    # API consistency with an Index, so (index or model.Meta) can be
    # used interchangeably to get the available columns from that
    # object.
    meta.projection = {
        "mode": "all",
        "included": meta.columns,
        "available": meta.columns,
        "strict": True
    }

def setup_indexes(meta):
    """Filter indexes from fields, compute projection for each index"""
    # Don't put these in the metadata until they bind successfully.
    gsis = set(filter(lambda field: isinstance(field, GlobalSecondaryIndex), meta.fields))
    lsis = set(filter(lambda field: isinstance(field, LocalSecondaryIndex), meta.fields))
    indexes = set.union(gsis, lsis)

    for index in indexes:
        index._bind(meta.model)

    meta.gsis = gsis
    meta.lsis = lsis
    meta.indexes = indexes

class BaseModel(metaclass=ModelMetaclass):
    """An unbound, abstract base model"""
    class Meta:
        abstract = True

    def __init__(self, **attrs):
        # Only set values from **attrs if there's a
        # corresponding `model_name` for a column in the model
        for column in self.Meta.columns:
            value = attrs.get(column.model_name, missing)
            if value is not missing:
                setattr(self, column.model_name, value)

    @classmethod
    def _load(cls, attrs, *, context, **kwargs):
        """ dict (dynamo name) -> obj """
        return unpack_from_dynamodb(
            model=cls,
            attrs=attrs or {},
            expected=cls.Meta.columns,
            context=context, **kwargs)

    @classmethod
    def _dump(cls, obj, *, context, **kwargs):
        """ obj -> dict """
        if obj is None:
            return None
        dump = context["engine"]._dump
        filtered = filter(
            lambda item: item[1] is not None,
            ((
                column.dynamo_name,
                dump(column.typedef, getattr(obj, column.model_name, None), context=context, **kwargs)
            ) for column in cls.Meta.columns))
        return dict(filtered) or None

    @classmethod
    def _register(cls, type_engine):
        for column in cls.Meta.columns:
            type_engine.register(column.typedef)

    def __repr__(self):
        attrs = ", ".join("{}={!r}".format(*item) for item in loaded_columns(self))
        return "{}({})".format(self.__class__.__name__, attrs)

class Index(declare.Field):
    def __init__(self, *, projection, hash_key=None, range_key=None, name=None, **kwargs):
        self.model = None
        self.hash_key = hash_key
        self.range_key = range_key
        self._dynamo_name = name
        super().__init__(**kwargs)

        self.projection = validate_projection(projection)

    def __repr__(self):
        if isinstance(self, LocalSecondaryIndex):
            cls_name = "LSI"
        elif isinstance(self, GlobalSecondaryIndex):
            cls_name = "GSI"
        else:
            cls_name = self.__class__.__name__

        # <GSI[User.by_email=all]>
        # <GSI[User.by_email=keys]>
        # <LSI[User.by_email=include]>
        return "<{}[{}.{}={}]>".format(
            cls_name,
            self.model.__name__, self.model_name,
            self.projection["mode"]
        )

    @property
    def dynamo_name(self):
        if self._dynamo_name is None:
            return self.model_name
        return self._dynamo_name

    def _bind(self, model):
        """Set up hash, range keys and compute projection"""
        self.model = model

        # Index by model_name so we can replace hash_key, range_key with the proper `bloop.Column` object
        columns = declare.index(model.Meta.columns, "model_name")
        if isinstance(self.hash_key, str):
            self.hash_key = columns[self.hash_key]
        if not isinstance(self.hash_key, Column):
            raise InvalidIndex("Index hash key must be a Column or Column model name.")
        if self.range_key:
            if isinstance(self.range_key, str):
                self.range_key = columns[self.range_key]
            if not isinstance(self.range_key, Column):
                raise InvalidIndex("Index range key (if provided) must be a Column or Column model name.")

        self.keys = {self.hash_key}
        if self.range_key:
            self.keys.add(self.range_key)

        # Compute and the projected columns
        # All projections include model + index keys
        projection_keys = set.union(model.Meta.keys, self.keys)

        if self.projection["mode"] == "keys":
            self.projection["included"] = projection_keys
        elif self.projection["mode"] == "all":
            self.projection["included"] = model.Meta.columns
        elif self.projection["mode"] == "include":  # pragma: no branch
            # model_name -> Column
            if all(isinstance(p, str) for p in self.projection["included"]):
                projection = set(columns[name] for name in self.projection["included"])
            else:
                projection = set(self.projection["included"])
            projection.update(projection_keys)
            self.projection["included"] = projection

        # Strict has the same availability as the included columns,
        # while non-strict has access to the full range of columns
        if self.projection["strict"]:
            self.projection["available"] = self.projection["included"]
        else:
            self.projection["available"] = model.Meta.columns

    # TODO: disallow set/get/del for an index; these don't store values.  Raise AttributeError.

class GlobalSecondaryIndex(Index):
    def __init__(self, *, projection, hash_key, range_key=None, read_units=1, write_units=1, name=None, **kwargs):
        super().__init__(hash_key=hash_key, range_key=range_key, name=name, projection=projection, **kwargs)
        self.write_units = write_units
        self.read_units = read_units

class LocalSecondaryIndex(Index):
    """ LSIs don't have individual read/write units """
    def __init__(self, *, projection, range_key, name=None, strict=True, **kwargs):
        # Hash key MUST be the table hash; do not specify
        if "hash_key" in kwargs:
            raise InvalidIndex("An LSI shares its hash key with the Model.")
        if ("write_units" in kwargs) or ("read_units" in kwargs):
            raise InvalidIndex("An LSI shares its provisioned throughput with the Model.")
        super().__init__(range_key=range_key, name=name, projection=projection, **kwargs)
        self.projection["strict"] = strict

    def _bind(self, model):
        """Raise if the model doesn't have a range key"""
        if not model.Meta.range_key:
            raise InvalidIndex("An LSI requires the Model to have a range key.")
        # this is model_name (string) because super()._bind will do the string -> Column lookup
        self.hash_key = model.Meta.hash_key.model_name
        super()._bind(model)

    @property
    def read_units(self):
        return self.model.Meta.read_units

    @read_units.setter
    def read_units(self, value):
        self.model.Meta.read_units = value

    @property
    def write_units(self):
        return self.model.Meta.write_units

    @write_units.setter
    def write_units(self, value):
        self.model.Meta.write_units = value

class Column(declare.Field, ComparisonMixin):
    def __init__(self, typedef, hash_key=None, range_key=None,
                 name=None, **kwargs):
        self.hash_key = hash_key
        self.range_key = range_key
        self._dynamo_name = name
        kwargs['typedef'] = typedef
        super().__init__(**kwargs)

    __hash__ = object.__hash__

    def _repr_with_path(self, path):
        if self.hash_key:
            extra = "=hash"
        elif self.range_key:
            extra = "=range"
        else:
            extra = ""

        # <Column[Pin.url]>
        # <Column[User.id=hash]>
        # <Column[File.fragment=range]>
        return "<Column[{}.{}{}]>".format(self.model.__name__, printable_column_name(self, path), extra)

    @property
    def dynamo_name(self):
        if self._dynamo_name is None:
            return self.model_name
        return self._dynamo_name

    def set(self, obj, value):
        super().set(obj, value)
        # Notify the tracking engine that this value was intentionally mutated
        object_modified.send(obj=obj, column=self, value=value)

    def delete(self, obj):
        try:
            super().delete(obj)
        finally:
            # Unlike set, we always want to mark on delete.  If we didn't, and the column wasn't loaded
            # (say from a query) then the intention "ensure this doesn't have a value" wouldn't be captured.
            object_modified.send(obj=obj, column=self, value=None)
<|endoftext|>


<commit_before># --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

# pylint: disable=line-too-long
from collections import OrderedDict

from azure.cli.core.commands import cli_command
from azure.cli.core.util import empty_on_404
from azure.cli.core.commands.arm import \
    (cli_generic_wait_command, deployment_validate_table_format)
from azure.cli.core.profiles import supported_api_version, PROFILE_TYPE
from ._client_factory import _acs_client_factory
from ._client_factory import _aks_client_factory

def aks_list_table_format(results):
    """"Format a list of managed clusters as summary results for display with "-o table"."""
    return [aks_show_table_format(r) for r in results]

def aks_show_table_format(result):
    """Format a managed cluster as summary results for display with "-o table"."""
    # move some nested properties up to top-level values
    properties = result.get('properties', {})
    promoted = ['kubernetesVersion', 'provisioningState', 'fqdn']
    result.update({k: properties.get(k) for k in promoted})

    columns = ['name', 'location', 'resourceGroup'] + promoted
    # put results in an ordered dict so the headers are predictable
    return OrderedDict({k: result.get(k) for k in columns})

if not supported_api_version(PROFILE_TYPE, max_api='2017-03-09-profile'):
    cli_command(__name__, 'acs show', 'azure.mgmt.containerservice.operations.container_services_operations#ContainerServicesOperations.get', _acs_client_factory, exception_handler=empty_on_404)
    cli_command(__name__, 'acs delete', 'azure.mgmt.containerservice.operations.container_services_operations#ContainerServicesOperations.delete', _acs_client_factory)

    # Per conversation with ACS team, hide the update till we have something meaningful to tweak
    # from azure.cli.command_modules.acs.custom import update_acs
    # cli_generic_update_command(__name__, 'acs update', ContainerServicesOperations.get, ContainerServicesOperations.create_or_update, cf_acs)

    # custom commands
    cli_command(__name__, 'acs list-locations', 'azure.cli.command_modules.acs.custom#list_acs_locations')
    cli_command(__name__, 'acs scale', 'azure.cli.command_modules.acs.custom#update_acs', _acs_client_factory)
    cli_command(__name__, 'acs list', 'azure.cli.command_modules.acs.custom#list_container_services', _acs_client_factory)
    cli_command(__name__, 'acs browse', 'azure.cli.command_modules.acs.custom#acs_browse')
    cli_command(__name__, 'acs install-cli', 'azure.cli.command_modules.acs.custom#acs_install_cli')
    cli_command(__name__, 'acs dcos browse', 'azure.cli.command_modules.acs.custom#dcos_browse')
    cli_command(__name__, 'acs dcos install-cli', 'azure.cli.command_modules.acs.custom#dcos_install_cli')
    cli_command(__name__, 'acs create', 'azure.cli.command_modules.acs.custom#acs_create', no_wait_param='no_wait', table_transformer=deployment_validate_table_format)
    cli_generic_wait_command(__name__, 'acs wait', 'azure.mgmt.containerservice.operations.container_services_operations#ContainerServicesOperations.get', _acs_client_factory)
    cli_command(__name__, 'acs kubernetes browse', 'azure.cli.command_modules.acs.custom#k8s_browse')
    cli_command(__name__, 'acs kubernetes install-cli', 'azure.cli.command_modules.acs.custom#k8s_install_cli')
    cli_command(__name__, 'acs kubernetes get-credentials', 'azure.cli.command_modules.acs.custom#k8s_get_credentials')

if not supported_api_version(PROFILE_TYPE, max_api='2017-08-31-profile'):
    cli_command(__name__, 'aks browse',
                'azure.cli.command_modules.acs.custom#aks_browse', _aks_client_factory)
    cli_command(__name__, 'aks create',
                'azure.cli.command_modules.acs.custom#aks_create', _aks_client_factory, no_wait_param='no_wait')
    cli_command(__name__, 'aks delete',
                'azure.cli.command_modules.acs.custom#aks_delete', _aks_client_factory, no_wait_param='no_wait',
                confirmation='Are you sure you want to perform this operation?')
    cli_command(__name__, 'aks get-credentials',
                'azure.cli.command_modules.acs.custom#aks_get_credentials', _aks_client_factory)
    cli_command(__name__, 'aks get-versions',
                'azure.cli.command_modules.acs.custom#aks_get_versions', _aks_client_factory)
    cli_command(__name__, 'aks install-cli',
                'azure.cli.command_modules.acs.custom#k8s_install_cli')
    cli_command(__name__, 'aks list',
                'azure.cli.command_modules.acs.custom#aks_list', _aks_client_factory,
                table_transformer=aks_list_table_format)
    cli_command(__name__, 'aks scale',
                'azure.cli.command_modules.acs.custom#aks_scale', _aks_client_factory, no_wait_param='no_wait')
    cli_command(__name__, 'aks show',
                'azure.cli.command_modules.acs.custom#aks_show', _aks_client_factory,
                table_transformer=aks_show_table_format)
    cli_command(__name__, 'aks upgrade',
                'azure.cli.command_modules.acs.custom#aks_upgrade', _aks_client_factory, no_wait_param='no_wait',
                confirmation='Kubernetes may be unavailable during cluster upgrades.\n' +
                'Are you sure you want to perform this operation?')
    cli_generic_wait_command(__name__, 'aks wait',
                             'azure.mgmt.containerservice.operations.managed_clusters_operations' +
                             '#ManagedClustersOperations.get',
                             _aks_client_factory)
<commit_msg>[AKS] add a useful table formatter for get-versions (#68)<commit_after># --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

# pylint: disable=line-too-long
from collections import OrderedDict

from azure.cli.core.commands import cli_command
from azure.cli.core.util import empty_on_404
from azure.cli.core.commands.arm import \
    (cli_generic_wait_command, deployment_validate_table_format)
from azure.cli.core.profiles import supported_api_version, PROFILE_TYPE
from ._client_factory import _acs_client_factory
from ._client_factory import _aks_client_factory

def aks_list_table_format(results):
    """"Format a list of managed clusters as summary results for display with "-o table"."""
    return [aks_show_table_format(r) for r in results]

def aks_show_table_format(result):
    """Format a managed cluster as summary results for display with "-o table"."""
    # move some nested properties up to top-level values
    properties = result.get('properties', {})
    promoted = ['kubernetesVersion', 'provisioningState', 'fqdn']
    result.update({k: properties.get(k) for k in promoted})

    columns = ['name', 'location', 'resourceGroup'] + promoted
    # put results in an ordered dict so the headers are predictable
    return OrderedDict({k: result.get(k) for k in columns})

def aks_get_versions_table_format(result):
    """Format get-versions upgrade results as a summary for display with "-o table"."""
    properties = result.get('properties', {})
    master = properties.get('controlPlaneProfile', {})
    result['masterVersion'] = master.get('kubernetesVersion', 'unknown')
    result['masterUpgrades'] = ', '.join(master.get('upgrades', []))

    agents = properties.get('agentPoolProfiles', [])
    versions, upgrades = [], []
    for agent in agents:
        version = agent.get('kubernetesVersion', 'unknown')
        upgrade = ', '.join(agent.get('upgrades', []))
        name = agent.get('name')
        if name:  # multiple agent pools, presumably
            version = "{}: {}".format(name, version)
            upgrade = "{}: {}".format(name, upgrades)
        versions.append(version)
        upgrades.append(upgrade)

    result['agentPoolVersion'] = ', '.join(versions)
    result['agentPoolUpgrades'] = ', '.join(upgrades)

    columns = ['name', 'resourceGroup', 'masterVersion', 'masterUpgrades', 'agentPoolVersion', 'agentPoolUpgrades']
    # put results in an ordered dict so the headers are predictable
    return OrderedDict({k: result.get(k) for k in columns})

if not supported_api_version(PROFILE_TYPE, max_api='2017-03-09-profile'):
    cli_command(__name__, 'acs show', 'azure.mgmt.containerservice.operations.container_services_operations#ContainerServicesOperations.get', _acs_client_factory, exception_handler=empty_on_404)
    cli_command(__name__, 'acs delete', 'azure.mgmt.containerservice.operations.container_services_operations#ContainerServicesOperations.delete', _acs_client_factory)

    # Per conversation with ACS team, hide the update till we have something meaningful to tweak
    # from azure.cli.command_modules.acs.custom import update_acs
    # cli_generic_update_command(__name__, 'acs update', ContainerServicesOperations.get, ContainerServicesOperations.create_or_update, cf_acs)

    # custom commands
    cli_command(__name__, 'acs list-locations', 'azure.cli.command_modules.acs.custom#list_acs_locations')
    cli_command(__name__, 'acs scale', 'azure.cli.command_modules.acs.custom#update_acs', _acs_client_factory)
    cli_command(__name__, 'acs list', 'azure.cli.command_modules.acs.custom#list_container_services', _acs_client_factory)
    cli_command(__name__, 'acs browse', 'azure.cli.command_modules.acs.custom#acs_browse')
    cli_command(__name__, 'acs install-cli', 'azure.cli.command_modules.acs.custom#acs_install_cli')
    cli_command(__name__, 'acs dcos browse', 'azure.cli.command_modules.acs.custom#dcos_browse')
    cli_command(__name__, 'acs dcos install-cli', 'azure.cli.command_modules.acs.custom#dcos_install_cli')
    cli_command(__name__, 'acs create', 'azure.cli.command_modules.acs.custom#acs_create', no_wait_param='no_wait', table_transformer=deployment_validate_table_format)
    cli_generic_wait_command(__name__, 'acs wait', 'azure.mgmt.containerservice.operations.container_services_operations#ContainerServicesOperations.get', _acs_client_factory)
    cli_command(__name__, 'acs kubernetes browse', 'azure.cli.command_modules.acs.custom#k8s_browse')
    cli_command(__name__, 'acs kubernetes install-cli', 'azure.cli.command_modules.acs.custom#k8s_install_cli')
    cli_command(__name__, 'acs kubernetes get-credentials', 'azure.cli.command_modules.acs.custom#k8s_get_credentials')

if not supported_api_version(PROFILE_TYPE, max_api='2017-08-31-profile'):
    cli_command(__name__, 'aks browse',
                'azure.cli.command_modules.acs.custom#aks_browse', _aks_client_factory)
    cli_command(__name__, 'aks create',
                'azure.cli.command_modules.acs.custom#aks_create', _aks_client_factory, no_wait_param='no_wait')
    cli_command(__name__, 'aks delete',
                'azure.cli.command_modules.acs.custom#aks_delete', _aks_client_factory, no_wait_param='no_wait',
                confirmation='Are you sure you want to perform this operation?')
    cli_command(__name__, 'aks get-credentials',
                'azure.cli.command_modules.acs.custom#aks_get_credentials', _aks_client_factory)
    cli_command(__name__, 'aks get-versions',
                'azure.cli.command_modules.acs.custom#aks_get_versions', _aks_client_factory,
                table_transformer=aks_get_versions_table_format)
    cli_command(__name__, 'aks install-cli',
                'azure.cli.command_modules.acs.custom#k8s_install_cli')
    cli_command(__name__, 'aks list',
                'azure.cli.command_modules.acs.custom#aks_list', _aks_client_factory,
                table_transformer=aks_list_table_format)
    cli_command(__name__, 'aks scale',
                'azure.cli.command_modules.acs.custom#aks_scale', _aks_client_factory, no_wait_param='no_wait')
    cli_command(__name__, 'aks show',
                'azure.cli.command_modules.acs.custom#aks_show', _aks_client_factory,
                table_transformer=aks_show_table_format)
    cli_command(__name__, 'aks upgrade',
                'azure.cli.command_modules.acs.custom#aks_upgrade', _aks_client_factory, no_wait_param='no_wait',
                confirmation='Kubernetes may be unavailable during cluster upgrades.\n' +
                'Are you sure you want to perform this operation?')
    cli_generic_wait_command(__name__, 'aks wait',
                             'azure.mgmt.containerservice.operations.managed_clusters_operations' +
                             '#ManagedClustersOperations.get',
                             _aks_client_factory)
<|endoftext|>


<commit_before># A part of pdfrw (pdfrw.googlecode.com)
# Copyright (C) 2006-2009 Patrick Maupin, Austin, Texas
# MIT license -- See LICENSE.txt for details

'''
A tokenizer for PDF streams.

In general, documentation used was "PDF reference",
sixth edition, for PDF version 1.7, dated November 2006.

'''

from __future__ import generators

try:
    set
except NameError:
    from sets import Set as set

import re
from pdfobjects import PdfString, PdfObject

class _PrimitiveTokens(object):

    # Table 3.1, page 50 of reference, defines whitespace
    whitespaceset = set('\x00\t\n\f\r ')

    # Text on page 50 defines delimiter characters
    delimiterset = set('()<>{}[]/%')

    # Coalesce contiguous whitespace into a single token
    whitespace_pattern = '[%s]+' % ''.join(whitespaceset)

    # In addition to the delimiters, we also use '\', which
    # is special in some contexts in PDF.
    delimiter_pattern = '\\\\|\\' + '|\\'.join(delimiterset)

    # Dictionary delimiters are '<<' and '>>'.  Look for
    # these before the single variety.
    dictdelim_pattern = r'\<\<|\>\>'

    pattern = '(%s|%s|%s)' % (whitespace_pattern,
                    dictdelim_pattern, delimiter_pattern)
    re_func = re.compile(pattern).finditer
    del whitespace_pattern, dictdelim_pattern
    del delimiter_pattern, pattern

    def __init__(self, fdata):

        class MyIterator(object):
            def next():
                if not tokens:
                    startloc = self.startloc
                    match = next_match[0]()
                    if match is not None:
                        start = match.start()
                        end = match.end()
                        tappend(fdata[start:end])
                        if start > startloc:
                            tappend(fdata[startloc:start])
                        self.startloc = end
                    else:
                        s = fdata[startloc:]
                        if s:
                            tappend(s)
                    if not tokens:
                        raise StopIteration
                return tpop()
            next = staticmethod(next)

        self.fdata = fdata
        self.tokens = tokens = []
        self.iterator = iterator = MyIterator()
        self.next = iterator.next
        self.next_match = next_match = [None]
        tappend = tokens.append
        tpop = tokens.pop

    def setstart(self, startloc):
        self.startloc = startloc
        self.next_match[0] = self.re_func(self.fdata, startloc).next

    def __iter__(self):
        return self.iterator

    def coalesce(self, result):
        ''' This function coalesces tokens together up until
            the next delimiter or whitespace.
            All of the coalesced tokens will either be non-matches,
            or will be a matched backslash.  We distinguish the
            non-matches by the fact that next() will have left
            a following match inside self.tokens for the actual match.
        '''
        tokens = self.tokens
        whitespace = self.whitespaceset

        # Optimized path for usual case -- regular data (not a name string),
        # with no escape character, and followed by whitespace.

        if tokens:
            token = tokens.pop()
            if token != '\\':
                if token[0] not in whitespace:
                    tokens.append(token)
                return
            result.append(token)

        # Non-optimized path.  Either start of a name string received,
        # or we just had one escape.

        for token in self:
            if tokens:
                result.append(token)
                token = tokens.pop()
            if token != '\\':
                if token[0] not in whitespace:
                    tokens.append(token)
                return
            result.append(token)

    def floc(self):
        return self.startloc - sum([len(x) for x in self.tokens])

class PdfTokens(object):

    def __init__(self, fdata, startloc=0, strip_comments=True):

        def comment(token):
            tokens = [token]
            for token in primitive:
                tokens.append(token)
                if token[0] in whitespaceset and ('\n' in token or '\r' in token):
                    break
            return not strip_comments and ''.join(tokens)

        def single(token):
            return token

        def regular_string(token):
            def escaped():
                escaped = False
                i = -2
                while tokens[i] == '\\':
                    escaped = not escaped
                    i -= 1
                return escaped

            tokens = [token]
            nestlevel = 1
            for token in primitive:
                tokens.append(token)
                if token in '()' and not escaped():
                    nestlevel += token == '(' or -1
                    if not nestlevel:
                        break
            else:
                assert 0, "Unexpected end of token stream"
            return PdfString(''.join(tokens))

        def hex_string(token):
            tokens = [token]
            for token in primitive:
                tokens.append(token)
                if token == '>':
                    break
            while tokens[-2] == '>>':
                tokens.append(tokens.pop(-2))
            return PdfString(''.join(tokens))

        def normal_data(token):

            # Obscure optimization -- we can get here with
            # whitespace or regular character data.  If we get
            # here with whitespace, then there won't be an additional
            # token queued up in the primitive object, otherwise there
            # will...
            if primitive_tokens:     #if token[0] not in whitespaceset:
                tokens = [token]
                primitive.coalesce(tokens)
                return PdfObject(''.join(tokens))

        def name_string(token):
            tokens = [token]
            primitive.coalesce(tokens)
            token = ''.join(tokens)
            if '#' in token:
                substrs = token.split('#')
                substrs.reverse()
                tokens = [substrs.pop()]
                while substrs:
                    s = substrs.pop()
                    tokens.append(chr(int(s[:2], 16)))
                    tokens.append(s[2:])
                token = ''.join(tokens)
            return PdfObject(token)

        def broken(token):
            assert 0, token

        dispatch = {
            '(': regular_string,
            ')': broken,
            '<': hex_string,
            '>': broken,
            '[': single,
            ']': single,
            '{': single,
            '}': single,
            '/': name_string,
            '%' : comment,
            '<<': single,
            '>>': single,
        }.get

        class MyIterator(object):
            def next():
                while not tokens:
                    token = primitive_next()
                    token = dispatch(token, normal_data)(token)
                    if token:
                        return token
                return tokens.pop()
            next = staticmethod(next)

        self.primitive = primitive = _PrimitiveTokens(fdata)
        self.setstart = primitive.setstart
        primitive.setstart(startloc)
        self.fdata = fdata
        self.strip_comments = strip_comments
        self.tokens = tokens = []
        self.iterator = iterator = MyIterator()
        self.next = iterator.next
        primitive_next = primitive.next
        primitive_tokens = primitive.tokens
        whitespaceset = _PrimitiveTokens.whitespaceset

    def floc(self):
        return self.primitive.floc() - sum([len(x) for x in self.tokens])
    floc = property(floc)

    def __iter__(self):
        return self.iterator

    def multiple(self, count):
        next = self.next
        return [next() for i in range(count)]
<commit_msg>Fixed bug in tokenizer next() function that kept it from handling EOF properly.  Thanks to Attila Tajti for providing testcase in comment to r120.<commit_after># A part of pdfrw (pdfrw.googlecode.com)
# Copyright (C) 2006-2009 Patrick Maupin, Austin, Texas
# MIT license -- See LICENSE.txt for details

'''
A tokenizer for PDF streams.

In general, documentation used was "PDF reference",
sixth edition, for PDF version 1.7, dated November 2006.

'''

from __future__ import generators

try:
    set
except NameError:
    from sets import Set as set

import re
from pdfobjects import PdfString, PdfObject

class _PrimitiveTokens(object):

    # Table 3.1, page 50 of reference, defines whitespace
    whitespaceset = set('\x00\t\n\f\r ')

    # Text on page 50 defines delimiter characters
    delimiterset = set('()<>{}[]/%')

    # Coalesce contiguous whitespace into a single token
    whitespace_pattern = '[%s]+' % ''.join(whitespaceset)

    # In addition to the delimiters, we also use '\', which
    # is special in some contexts in PDF.
    delimiter_pattern = '\\\\|\\' + '|\\'.join(delimiterset)

    # Dictionary delimiters are '<<' and '>>'.  Look for
    # these before the single variety.
    dictdelim_pattern = r'\<\<|\>\>'

    pattern = '(%s|%s|%s)' % (whitespace_pattern,
                    dictdelim_pattern, delimiter_pattern)
    re_func = re.compile(pattern).finditer
    del whitespace_pattern, dictdelim_pattern
    del delimiter_pattern, pattern

    def __init__(self, fdata):

        class MyIterator(object):
            def next():
                if not tokens:
                    startloc = self.startloc
                    for match in next_match[0]:
                        start = match.start()
                        end = match.end()
                        tappend(fdata[start:end])
                        if start > startloc:
                            tappend(fdata[startloc:start])
                        self.startloc = end
                        break
                    else:
                        s = fdata[startloc:]
                        self.startloc = len(fdata)
                        if s:
                            tappend(s)
                    if not tokens:
                        raise StopIteration
                return tpop()
            next = staticmethod(next)

        self.fdata = fdata
        self.tokens = tokens = []
        self.iterator = iterator = MyIterator()
        self.next = iterator.next
        self.next_match = next_match = [None]
        tappend = tokens.append
        tpop = tokens.pop

    def setstart(self, startloc):
        self.startloc = startloc
        self.next_match[0] = self.re_func(self.fdata, startloc)

    def __iter__(self):
        return self.iterator

    def coalesce(self, result):
        ''' This function coalesces tokens together up until
            the next delimiter or whitespace.
            All of the coalesced tokens will either be non-matches,
            or will be a matched backslash.  We distinguish the
            non-matches by the fact that next() will have left
            a following match inside self.tokens for the actual match.
        '''
        tokens = self.tokens
        whitespace = self.whitespaceset

        # Optimized path for usual case -- regular data (not a name string),
        # with no escape character, and followed by whitespace.

        if tokens:
            token = tokens.pop()
            if token != '\\':
                if token[0] not in whitespace:
                    tokens.append(token)
                return
            result.append(token)

        # Non-optimized path.  Either start of a name string received,
        # or we just had one escape.

        for token in self:
            if tokens:
                result.append(token)
                token = tokens.pop()
            if token != '\\':
                if token[0] not in whitespace:
                    tokens.append(token)
                return
            result.append(token)

    def floc(self):
        return self.startloc - sum([len(x) for x in self.tokens])

class PdfTokens(object):

    def __init__(self, fdata, startloc=0, strip_comments=True):

        def comment(token):
            tokens = [token]
            for token in primitive:
                tokens.append(token)
                if token[0] in whitespaceset and ('\n' in token or '\r' in token):
                    break
            return not strip_comments and ''.join(tokens)

        def single(token):
            return token

        def regular_string(token):
            def escaped():
                escaped = False
                i = -2
                while tokens[i] == '\\':
                    escaped = not escaped
                    i -= 1
                return escaped

            tokens = [token]
            nestlevel = 1
            for token in primitive:
                tokens.append(token)
                if token in '()' and not escaped():
                    nestlevel += token == '(' or -1
                    if not nestlevel:
                        break
            else:
                assert 0, "Unexpected end of token stream"
            return PdfString(''.join(tokens))

        def hex_string(token):
            tokens = [token]
            for token in primitive:
                tokens.append(token)
                if token == '>':
                    break
            while tokens[-2] == '>>':
                tokens.append(tokens.pop(-2))
            return PdfString(''.join(tokens))

        def normal_data(token):

            # Obscure optimization -- we can get here with
            # whitespace or regular character data.  If we get
            # here with whitespace, then there won't be an additional
            # token queued up in the primitive object, otherwise there
            # will...
            if primitive_tokens:     #if token[0] not in whitespaceset:
                tokens = [token]
                primitive.coalesce(tokens)
                return PdfObject(''.join(tokens))

        def name_string(token):
            tokens = [token]
            primitive.coalesce(tokens)
            token = ''.join(tokens)
            if '#' in token:
                substrs = token.split('#')
                substrs.reverse()
                tokens = [substrs.pop()]
                while substrs:
                    s = substrs.pop()
                    tokens.append(chr(int(s[:2], 16)))
                    tokens.append(s[2:])
                token = ''.join(tokens)
            return PdfObject(token)

        def broken(token):
            assert 0, token

        dispatch = {
            '(': regular_string,
            ')': broken,
            '<': hex_string,
            '>': broken,
            '[': single,
            ']': single,
            '{': single,
            '}': single,
            '/': name_string,
            '%' : comment,
            '<<': single,
            '>>': single,
        }.get

        class MyIterator(object):
            def next():
                while not tokens:
                    token = primitive_next()
                    token = dispatch(token, normal_data)(token)
                    if token:
                        return token
                return tokens.pop()
            next = staticmethod(next)

        self.primitive = primitive = _PrimitiveTokens(fdata)
        self.setstart = primitive.setstart
        primitive.setstart(startloc)
        self.fdata = fdata
        self.strip_comments = strip_comments
        self.tokens = tokens = []
        self.iterator = iterator = MyIterator()
        self.next = iterator.next
        primitive_next = primitive.next
        primitive_tokens = primitive.tokens
        whitespaceset = _PrimitiveTokens.whitespaceset

    def floc(self):
        return self.primitive.floc() - sum([len(x) for x in self.tokens])
    floc = property(floc)

    def __iter__(self):
        return self.iterator

    def multiple(self, count):
        next = self.next
        return [next() for i in range(count)]
<|endoftext|>


<commit_before>import threading

from rx.internal import noop

class Disposable(object):
    """Main disposable class"""
    def __init__(self, action=None):
        """Creates a disposable object that invokes the specified action when
        disposed.

        Keyword arguments:
        dispose -- Action to run during the first call to Disposable.dispose.
            The action is guaranteed to be run at most once.

        Returns the disposable object that runs the given action upon disposal.
        """
        self.is_disposed = False
        self.action = action

        self.lock = threading.Lock()

    def dispose(self):
        """Performs the task of cleaning up resources."""

        dispose = False
        with self.lock:
            if not self.is_disposed:
                dispose = True
                self.is_disposed = True

        if dispose and self.action:
            self.action()

    def __enter__(self):
        """Context management protocol"""

        pass

    def __exit__(self, type, value, traceback):
        """Context management protocol"""

        self.dispose()

    @classmethod
    def empty(cls):
        return cls(noop)

    @classmethod
    def create(cls, action):
        return cls(action)
<commit_msg>Disposable.create() and empty() not needed anymore<commit_after>import threading

from rx.internal import noop

class Disposable(object):
    """Main disposable class"""
    def __init__(self, action=None):
        """Creates a disposable object that invokes the specified action when
        disposed.

        Keyword arguments:
        dispose -- Action to run during the first call to Disposable.dispose.
            The action is guaranteed to be run at most once.

        Returns the disposable object that runs the given action upon disposal.
        """
        self.is_disposed = False
        self.action = action or noop

        self.lock = threading.Lock()

    def dispose(self):
        """Performs the task of cleaning up resources."""

        dispose = False
        with self.lock:
            if not self.is_disposed:
                dispose = True
                self.is_disposed = True

        if dispose:
            self.action()

    def __enter__(self):
        """Context management protocol"""

        pass

    def __exit__(self, type, value, traceback):
        """Context management protocol"""

        self.dispose()

    @classmethod
    def empty(cls):
        return cls(noop)

    @classmethod
    def create(cls, action):
        return cls(action)
<|endoftext|>