<commit_before>import asyncio
from concurrent import futures
import random
import re
from urllib.parse import parse_qs

import discord
from discord.ext import commands
from lxml import etree

class RPG:
    def __init__(self, bot):
        self.bot = bot

    @commands.command()
    async def choose(self, ctx, *options):
        """Choose between some options. Use quotes if they have spaces."""
        await ctx.send(random.choice(options))

    @commands.command(aliases=['r'])
    async def roll(self, ctx, *, inp='1d20'):
        """Roll some dice!

        Can roll multiple dice of any size, with modifiers.
        Format: XdY([+-^v]Z)(xN)(s)(t)
        X is the number of dice
        Y is the number of sides
        + adds Z to the result
        - subtracts Z from the result
        ^ drops the Z highest dice
        v drops the Z lowest dice
        x repeats the roll N times
        s sorts the results
        t totals each roll
        """
        if inp == 'stats':
            inp = '4d6v1x6t'
        inp = ''.join(inp.split()).lower()
        expr = r'^[\d]*d?\d+(?:[+\-^v]\d+)?(?:x\d+)?(?:[ts]{1,2})?$'
        if re.match(expr, inp) is None:
            raise commands.BadArgument

        if 'd' not in inp:
            inp = f'1d{inp}'
        elif inp[0] == 'd':
            inp = f'1{inp}'
        args = tuple(int(arg) for arg in re.findall(r'\d+', inp))

        num = args[0]
        sides = args[1]

        hi_drop = 0
        lo_drop = 0
        mod = 0

        if '^' in inp:
            hi_drop = args[2]
        elif 'v' in inp:
            lo_drop = args[2]
        elif '+' in inp:
            mod = args[2]
        elif '-' in inp:
            mod = -args[2]

        if 'x' in inp:
            times = args[-1]
        else:
            times = 1

        loop = self.bot.loop
        args = (num, sides, lo_drop, hi_drop, mod, times)
        future = loop.run_in_executor(None, roller, *args)
        async with ctx.typing():
            result = await asyncio.wait_for(future, 10, loop=loop)

        total = 't' in inp

        if total:
            result = [[sum(roll_)] for roll_ in result]
        if 's' in inp:
            for roll_ in result:
                roll_.sort()
            result.sort()
        if total or num == 1:
            result = [roll_[0] for roll_ in result]
        if times == 1:
            result = result[0]

        await ctx.reply(f'{inp}: {result}')

    @roll.error
    async def roll_error(self, e, ctx):
        e = getattr(e, 'original', e)
        if isinstance(e, (commands.MissingRequiredArgument,
                      commands.BadArgument)):
            await ctx.send('Invalid input. Valid input examples:'
                           '\n1d20+3'
                           '\n1d6'
                           '\n2d8-4'
                           '\n2d20^1'
                           '\n4d6v1x6t')
        elif isinstance(e, futures.TimeoutError):
            await ctx.reply('Your execution took too long. Roll fewer dice.')
        elif isinstance(e, discord.HTTPException):
            await ctx.reply('Your results were too long. Maybe sum them?')
        else:
            await self.bot.handle_error(e, ctx)

    @commands.command(aliases=['shadroll', 'sr'])
    async def shadowroll(self, ctx, *, inp):
        """Roll some dice - for Shadowrun!

        Format: N[e]
        Roll N six-sided dice and return the number of dice that rolled 5 or 6.
        If you put "e" after the number, 6s are counted and then rerolled."""
        inp = inp.strip()
        expr = r'^\d+e?$'
        if not re.match(expr, inp):
            raise commands.BadArgument
        edge = 'e' in inp
        if edge:
            inp = inp[:-1]
        num = int(inp)

        loop = self.bot.loop
        args = (num, edge)
        future = loop.run_in_executor(None, shadowroller, *args)
        async with ctx.typing():
            result = await asyncio.wait_for(future, 10, loop=loop)

        await ctx.reply(result)

    @shadowroll.error
    async def shadowroll_error(self, e, ctx):
        e = getattr(e, 'original', e)
        if isinstance(e, (commands.MissingRequiredArgument,
                      commands.BadArgument)):
            await ctx.send('Invalid input. Valid input examples:'
                           '\n6'
                           '\n13e')
        elif isinstance(e, futures.TimeoutError):
            await ctx.reply('Your execution took too long. Roll fewer dice.')
        else:
            await self.bot.handle_error(e, ctx)

    @commands.command(aliases=['sw'])
    async def starroll(self, ctx, *, inp):
        """Roll some dice - for Fantasy Flight Star Wars!

        Available dice:
        b[oost]
        a[bility]
        p[roficiency]
        s[etback]
        d[ifficulty]
        c[hallenge]
        f[orce]

        Input examples:
        4a3d
        3a2p1b4d1c
        2f"""
        inp = inp.lower()
        match = re.match(r'^(\d+[a-z])+$', inp)
        if not match:
            raise commands.BadArgument
        dice = {}
        for roll in match.groups():
            num = int(roll[:-1])
            try:
                die = die_names[roll[-1]]
            except KeyError:
                await ctx.send(f'Die "{roll[-1]}" does not exist.')
                return
            dice[die] = num

        loop = self.bot.loop
        future = loop.run_in_executor(None, lambda: starroller(**dice))
        async with ctx.typing():
            try:
                result = await asyncio.wait_for(future, 10, loop=loop)
            except ValueError:
                await ctx.send('Force dice cannot be used with other dice.')
            else:
                await ctx.reply(result)

    @starroll.error
    async def starroll_error(self, e, ctx):
        e = getattr(e, 'original', e)
        if isinstance(e, futures.TimeoutError):
            await ctx.reply('Your execution took too long. Roll fewer dice.')
        else:
            await self.bot.handle_error(e, ctx)

    @commands.command()
    async def srd(self, ctx, *, inp):
        """Search the Pathfinder SRD.

        Returns the first three results from d20pfsrd.com on the search query.
        Copied shamelessly with some minimal editing from RoboDanny."""
        params = {'q': inp + ' site:d20pfsrd.com'}
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64)'
        }
        entries = []
        async with ctx.typing():
            async with self.bot.get('https://google.com/search',
                                    params=params, headers=headers) as resp:
                root = etree.fromstring(await resp.text(), etree.HTMLParser())
            search_nodes = root.findall(".//div[@class='g']")
            for node in search_nodes:
                url_node = node.find('.//h3/a')
                if url_node is None:
                    continue
                url = url_node.attrib['href']
                if not url.startswith('/url?'):
                    continue

                url = parse_qs(url[5:])['q'][0]

                entries.append(url)
            try:
                msg = entries[0]
            except IndexError:
                msg = 'No results found.'
            else:
                if entries[1:3]:
                    msg += '\n\nSee also:\n{}'.format('\n'.join(f'<{ent}>'
                                                      for ent in entries[1:3]))

        await ctx.send(msg)

    @srd.error
    async def srd_error(self, e, ctx):
        e = getattr(e, 'original', e)
        if isinstance(e, commands.MissingRequiredArgument):
            await ctx.send('Please include a search term.')
        else:
            await self.bot.handle_error(e, ctx)

def roller(num=1, sides=20, lo_drop=0, hi_drop=0, mod=0, times=1):
    rolls = []
    for _ in range(times):
        pool = [random.randint(1, sides) for _ in range(num)]
        if lo_drop or hi_drop:
            sorted_pool = sorted(pool)
            dropped_vals = sorted_pool[:lo_drop] + sorted_pool[num-hi_drop:]
            for val in dropped_vals:
                pool.remove(val)
        if mod != 0:
            pool = [sum(pool) + mod]
        rolls.append(pool)
    return rolls

def shadowroller(num, edge=False):
    rolls = hits = count1 = 0
    while True:
        count6 = 0
        rolls += num
        for _ in range(num):
            roll = random.randint(1, 6)
            if roll > 4:
                hits += 1
                if roll == 6:
                    count6 += 1
            elif roll == 1:
                count1 += 1
        if not (count6 > 0 and edge):
            break
        num = count6
    s = 's' if hits != 1 else ''
    if count1 > rolls / 2:
        if hits == 0:
            result = 'Critical glitch.'
        else:
            result = f'Glitch with {hits} hit{s}.'
    else:
        result = f'{hits} hit{s}.'
    return result

class Result:
    def __init__(self, advantages=0, hits=0, triumphs=0):
        self.advantages = advantages
        self.hits = hits
        self.triumphs = triumphs

    def __repr__(self):
        return (f'{type(self).__name__}'
                f'({self.advantages}, {self.hits}, {self.triumphs})')

    def __str__(self):
        ret = []

        if self.hits > 0:
            s = 's' if self.hits > 1 else ''
            ret.append(f'{self.hits} hit{s}')
        elif self.hits < 0:
            misses = -self.hits
            es = 'es' if misses > 1 else ''
            ret.append(f'{misses} miss{es}')

        if self.advantages > 0:
            s = 's' if self.advantages > 1 else ''
            ret.append(f'{self.advantages} advantage{s}')
        elif self.advantages < 0:
            disadvantages = -self.advantages
            s = 's' if disadvantages > 1 else ''
            ret.append(f'{disadvantages} disadvantage{s}')

        if self.triumphs > 0:
            s = 's' if self.triumphs > 1 else ''
            ret.append(f'{self.triumphs} triumph{s}')
        elif self.triumphs < 0:
            despairs = -self.triumphs
            s = 's' if despairs > 1 else ''
            ret.append(f'{despairs} despair{s}')

        if ret:
            ret = ', '.join(ret) + '.'
        else:
            ret = 'Wash.'
        return ret

    def __add__(self, other):
        if isinstance(other, Result):
            return type(self)(self.advantages + other.advantages,
                              self.hits + other.hits,
                              self.triumphs + other.triumphs)
        elif isinstance(other, int):
            return type(self)(self.advantages + other,
                              self.hits + other,
                              self.triumphs + other)
        else:
            return NotImplemented

    __radd__ = __add__

    def __mul__(self, other):
        ret = type(self)(self.advantages * other,
                         self.hits * other,
                         self.triumphs * other)
        return ret

    __rmul__ = __mul__

    def __neg__(self):
        return type(self)(-self.advantages, -self.hits, -self.triumphs)

class Force:
    def __init__(self, light=0, dark=0):
        self.light = light
        self.dark = dark

    def __repr__(self):
        return f'{type(self).__name__}({self.light}, {self.dark})'

    def __str__(self):
        ret = []
        if self.light:
            ret.append(f'{self.light} light side')
        if self.dark:
            ret.append(f'{self.dark} dark side')
        if not ret:
            ret = 'Wash.'
        else:
            ret = ', '.join(ret) + '.'
        return ret

    def __add__(self, other):
        if isinstance(other, Force):
            return type(self)(self.light + other.light,
                              self.dark + other.dark)
        elif isinstance(other, int):
            return type(self)(self.light + other,
                              self.dark + other)
        return NotImplemented

    __radd__ = __add__

    def __mul__(self, other):
        return type(self)(self.light * other, self.dark * other)

    __rmul__ = __mul__

wash = Result()
adv = Result(advantages=1)
hit = Result(hits=1)
triumph = Result(triumphs=1)
dis = -adv
miss = -hit
despair = -triumph
light = Force(light=1)
dark = Force(dark=1)

die_names = {'b': 'boost',
             's': 'setback',
             'a': 'ability',
             'd': 'difficulty',
             'p': 'proficiency',
             'c': 'challenge',
             'f': 'force'}

stardice = {'boost': (wash, wash, hit, hit + adv, 2 * adv, adv),
            'setback': (wash, wash, miss, miss, dis, dis),
            'ability': (wash, hit, hit, 2 * hit, 2 * adv, adv,
                        hit + adv, 2 * adv),
            'difficulty': (wash, miss, 2 * miss, dis, dis, dis,
                           2 * dis, miss + dis),
            'proficiency': (wash, hit, hit, 2 * hit, 2 * hit, adv, hit + adv,
                            hit + adv, hit + adv, adv * 2, adv * 2, triumph),
            'challenge': (wash, miss, miss, 2 * miss, 2 * miss, dis, dis,
                          miss + dis, miss + dis, 2 * dis, 2 * dis, despair),
            'force': (dark, dark, dark, dark, dark, dark, 2 * dark,
                      light, light, 2 * light, 2 * light, 2 * light),
            }

def starroller(**kwargs):
    if 'force' in kwargs:
        if len(kwargs) > 1:
            raise ValueError
        return sum(random.choice(stardice['force'])
                   for _ in range(kwargs['force']))
    result = Result()
    for die in kwargs:
        result += sum(random.choice(stardice[die])
                      for _ in range(kwargs[die]))
    return result

def setup(bot):
    bot.add_cog(RPG(bot))
<commit_msg>Minor implementation fixes, no real changes.<commit_after>import asyncio
from concurrent import futures
import random
import re
from urllib.parse import parse_qs

import discord
from discord.ext import commands
from lxml import etree

class RPG:
    def __init__(self, bot):
        self.bot = bot

    @commands.command()
    async def choose(self, ctx, *options):
        """Choose between some options. Use quotes if they have spaces."""
        await ctx.send(random.choice(options))

    @commands.command(aliases=['r'])
    async def roll(self, ctx, *, inp='1d20'):
        """Roll some dice!

        Can roll multiple dice of any size, with modifiers.
        Format: XdY([+-^v]Z)(xN)(s)(t)
        X is the number of dice
        Y is the number of sides
        + adds Z to the result
        - subtracts Z from the result
        ^ drops the Z highest dice
        v drops the Z lowest dice
        x repeats the roll N times
        s sorts the results
        t totals each roll
        """
        if inp == 'stats':
            inp = '4d6v1x6t'
        inp = ''.join(inp.split()).lower()
        expr = r'^[\d]*d?\d+(?:[+\-^v]\d+)?(?:x\d+)?(?:[ts]{1,2})?$'
        if re.match(expr, inp) is None:
            raise commands.BadArgument

        if 'd' not in inp:
            inp = f'1d{inp}'
        elif inp[0] == 'd':
            inp = f'1{inp}'
        args = tuple(int(arg) for arg in re.findall(r'\d+', inp))

        num = args[0]
        sides = args[1]

        hi_drop = 0
        lo_drop = 0
        mod = 0

        if '^' in inp:
            hi_drop = args[2]
        elif 'v' in inp:
            lo_drop = args[2]
        elif '+' in inp:
            mod = args[2]
        elif '-' in inp:
            mod = -args[2]

        if 'x' in inp:
            times = args[-1]
        else:
            times = 1

        loop = self.bot.loop
        args = (num, sides, lo_drop, hi_drop, mod, times)
        future = loop.run_in_executor(None, roller, *args)
        async with ctx.typing():
            result = await asyncio.wait_for(future, 10, loop=loop)

        total = 't' in inp

        if total:
            result = [[sum(roll_)] for roll_ in result]
        if 's' in inp:
            for roll_ in result:
                roll_.sort()
            result.sort()
        if total or num == 1:
            result = [roll_[0] for roll_ in result]
        if times == 1:
            result = result[0]

        await ctx.reply(f'{inp}: {result}')

    @roll.error
    async def roll_error(self, e, ctx):
        e = getattr(e, 'original', e)
        if isinstance(e, (commands.MissingRequiredArgument,
                      commands.BadArgument)):
            await ctx.send('Invalid input. Valid input examples:'
                           '\n1d20+3'
                           '\n1d6'
                           '\n2d8-4'
                           '\n2d20^1'
                           '\n4d6v1x6t')
        elif isinstance(e, futures.TimeoutError):
            await ctx.reply('Your execution took too long. Roll fewer dice.')
        elif isinstance(e, discord.HTTPException):
            await ctx.reply('Your results were too long. Maybe sum them?')
        else:
            await self.bot.handle_error(e, ctx)

    @commands.command(aliases=['shadroll', 'sr'])
    async def shadowroll(self, ctx, *, inp):
        """Roll some dice - for Shadowrun!

        Format: N[e]
        Roll N six-sided dice and return the number of dice that rolled 5 or 6.
        If you put "e" after the number, 6s are counted and then rerolled."""
        inp = inp.strip()
        expr = r'^\d+e?$'
        if not re.match(expr, inp):
            raise commands.BadArgument
        edge = 'e' in inp
        if edge:
            inp = inp[:-1]
        num = int(inp)

        loop = self.bot.loop
        args = (num, edge)
        future = loop.run_in_executor(None, shadowroller, *args)
        async with ctx.typing():
            result = await asyncio.wait_for(future, 10, loop=loop)

        await ctx.reply(result)

    @shadowroll.error
    async def shadowroll_error(self, e, ctx):
        e = getattr(e, 'original', e)
        if isinstance(e, (commands.MissingRequiredArgument,
                      commands.BadArgument)):
            await ctx.send('Invalid input. Valid input examples:'
                           '\n6'
                           '\n13e')
        elif isinstance(e, futures.TimeoutError):
            await ctx.reply('Your execution took too long. Roll fewer dice.')
        else:
            await self.bot.handle_error(e, ctx)

    @commands.command(aliases=['sw'])
    async def starroll(self, ctx, *, inp):
        """Roll some dice - for Fantasy Flight Star Wars!

        Available dice:
        b[oost]
        a[bility]
        p[roficiency]
        s[etback]
        d[ifficulty]
        c[hallenge]
        f[orce]

        Input examples:
        4a3d
        3a2p1b4d1c
        2f"""
        inp = inp.lower()
        expr = r'^(\d+[a-z])+$'
        match = re.match(expr, inp)
        if not match:
            raise commands.BadArgument
        dice = {}
        for roll in match.groups():
            num = int(roll[:-1])
            try:
                die = die_names[roll[-1]]
            except KeyError:
                await ctx.send(f'Die "{roll[-1]}" does not exist.')
                return
            dice[die] = num

        loop = self.bot.loop
        future = loop.run_in_executor(None, lambda: starroller(**dice))
        async with ctx.typing():
            try:
                result = await asyncio.wait_for(future, 10, loop=loop)
            except ValueError:
                await ctx.send('Force dice cannot be used with other dice.')
            else:
                await ctx.reply(result)

    @starroll.error
    async def starroll_error(self, e, ctx):
        e = getattr(e, 'original', e)
        if isinstance(e, futures.TimeoutError):
            await ctx.reply('Your execution took too long. Roll fewer dice.')
        else:
            await self.bot.handle_error(e, ctx)

    @commands.command()
    async def srd(self, ctx, *, inp):
        """Search the Pathfinder SRD.

        Returns the first three results from d20pfsrd.com on the search query.
        Copied shamelessly with some minimal editing from RoboDanny."""
        params = {'q': inp + ' site:d20pfsrd.com'}
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64)'
        }
        entries = []
        async with ctx.typing():
            async with self.bot.get('https://google.com/search',
                                    params=params, headers=headers) as resp:
                root = etree.fromstring(await resp.text(), etree.HTMLParser())
            search_nodes = root.findall(".//div[@class='g']")
            for node in search_nodes:
                url_node = node.find('.//h3/a')
                if url_node is None:
                    continue
                url = url_node.attrib['href']
                if not url.startswith('/url?'):
                    continue

                url = parse_qs(url[5:])['q'][0]

                entries.append(url)
            try:
                msg = entries[0]
            except IndexError:
                msg = 'No results found.'
            else:
                if entries[1:3]:
                    msg += '\n\nSee also:\n{}'.format('\n'.join(f'<{ent}>'
                                                      for ent in entries[1:3]))

        await ctx.send(msg)

    @srd.error
    async def srd_error(self, e, ctx):
        e = getattr(e, 'original', e)
        if isinstance(e, commands.MissingRequiredArgument):
            await ctx.send('Please include a search term.')
        else:
            await self.bot.handle_error(e, ctx)

def roller(num=1, sides=20, lo_drop=0, hi_drop=0, mod=0, times=1):
    rolls = []
    for _ in range(times):
        pool = [random.randint(1, sides) for _ in range(num)]
        if lo_drop or hi_drop:
            sorted_pool = sorted(pool)
            dropped_vals = sorted_pool[:lo_drop] + sorted_pool[num-hi_drop:]
            for val in dropped_vals:
                pool.remove(val)
        if mod:
            pool = [sum(pool) + mod]
        rolls.append(pool)
    return rolls

def shadowroller(num, edge=False):
    rolls = hits = count1 = 0
    while True:
        count6 = 0
        rolls += num
        for _ in range(num):
            roll = random.randint(1, 6)
            if roll > 4:
                hits += 1
                if roll == 6:
                    count6 += 1
            elif roll == 1:
                count1 += 1
        if not (count6 > 0 and edge):
            break
        num = count6
    s = 's' if hits != 1 else ''
    if count1 > rolls / 2:
        if hits == 0:
            result = 'Critical glitch.'
        else:
            result = f'Glitch with {hits} hit{s}.'
    else:
        result = f'{hits} hit{s}.'
    return result

class Result:
    def __init__(self, advantages=0, hits=0, triumphs=0):
        self.advantages = advantages
        self.hits = hits
        self.triumphs = triumphs

    def __repr__(self):
        return (f'{type(self).__name__}'
                f'({self.advantages}, {self.hits}, {self.triumphs})')

    def __str__(self):
        ret = []

        if self.hits > 0:
            s = 's' if self.hits > 1 else ''
            ret.append(f'{self.hits} hit{s}')
        elif self.hits < 0:
            misses = -self.hits
            es = 'es' if misses > 1 else ''
            ret.append(f'{misses} miss{es}')

        if self.advantages > 0:
            s = 's' if self.advantages > 1 else ''
            ret.append(f'{self.advantages} advantage{s}')
        elif self.advantages < 0:
            disadvantages = -self.advantages
            s = 's' if disadvantages > 1 else ''
            ret.append(f'{disadvantages} disadvantage{s}')

        if self.triumphs > 0:
            s = 's' if self.triumphs > 1 else ''
            ret.append(f'{self.triumphs} triumph{s}')
        elif self.triumphs < 0:
            despairs = -self.triumphs
            s = 's' if despairs > 1 else ''
            ret.append(f'{despairs} despair{s}')

        if ret:
            ret = ', '.join(ret) + '.'
        else:
            ret = 'Wash.'
        return ret

    def __add__(self, other):
        if isinstance(other, Result):
            return type(self)(self.advantages + other.advantages,
                              self.hits + other.hits,
                              self.triumphs + other.triumphs)
        elif isinstance(other, int):
            return type(self)(self.advantages + other,
                              self.hits + other,
                              self.triumphs + other)
        else:
            return NotImplemented

    __radd__ = __add__

    def __mul__(self, other):
        ret = type(self)(self.advantages * other,
                         self.hits * other,
                         self.triumphs * other)
        return ret

    __rmul__ = __mul__

    def __neg__(self):
        return type(self)(-self.advantages, -self.hits, -self.triumphs)

class Force:
    def __init__(self, light=0, dark=0):
        self.light = light
        self.dark = dark

    def __repr__(self):
        return f'{type(self).__name__}({self.light}, {self.dark})'

    def __str__(self):
        ret = []
        if self.light:
            ret.append(f'{self.light} light side')
        if self.dark:
            ret.append(f'{self.dark} dark side')
        if not ret:
            ret = 'Wash.'
        else:
            ret = ', '.join(ret) + '.'
        return ret

    def __add__(self, other):
        if isinstance(other, Force):
            return type(self)(self.light + other.light,
                              self.dark + other.dark)
        elif isinstance(other, int):
            return type(self)(self.light + other,
                              self.dark + other)
        return NotImplemented

    __radd__ = __add__

    def __mul__(self, other):
        return type(self)(self.light * other, self.dark * other)

    __rmul__ = __mul__

wash = Result()
adv = Result(advantages=1)
hit = Result(hits=1)
triumph = Result(triumphs=1)
dis = -adv
miss = -hit
despair = -triumph
light = Force(light=1)
dark = Force(dark=1)

die_names = {'b': 'boost',
             's': 'setback',
             'a': 'ability',
             'd': 'difficulty',
             'p': 'proficiency',
             'c': 'challenge',
             'f': 'force'}

stardice = {'boost': (wash, wash, hit, hit + adv, 2 * adv, adv),
            'setback': (wash, wash, miss, miss, dis, dis),
            'ability': (wash, hit, hit, 2 * hit, 2 * adv, adv,
                        hit + adv, 2 * adv),
            'difficulty': (wash, miss, 2 * miss, dis, dis, dis,
                           2 * dis, miss + dis),
            'proficiency': (wash, hit, hit, 2 * hit, 2 * hit, adv, hit + adv,
                            hit + adv, hit + adv, adv * 2, adv * 2, triumph),
            'challenge': (wash, miss, miss, 2 * miss, 2 * miss, dis, dis,
                          miss + dis, miss + dis, 2 * dis, 2 * dis, despair),
            'force': (dark, dark, dark, dark, dark, dark, 2 * dark,
                      light, light, 2 * light, 2 * light, 2 * light),
            }

def starroller(**kwargs):
    if 'force' in kwargs:
        if len(kwargs) > 1:
            raise ValueError
        return sum(random.choice(stardice['force'])
                   for _ in range(kwargs['force']))
    result = Result()
    for die, times in kwargs.items():
        result += sum(random.choice(stardice[die])
                      for _ in range(times))
    return result

def setup(bot):
    bot.add_cog(RPG(bot))
<|endoftext|>


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

from sys import stdin, stderr
from decimal import Decimal
from datetime import datetime, time
from fractions import Fraction, gcd
from inspect import signature as getsignature, Parameter
from functools import reduce, wraps, partial
from collections import deque
from argparse import ArgumentParser, REMAINDER

import inspect

import operator
import math

import regex

class Machine:
    IFMTS = {
        'i': int,
        'f': float,
        'D': Decimal,

        'd': datetime.fromtimestamp,
        't': time,

        # TODO: Are these two any use?
        'c': complex,
        'F': Fraction,
    }
    DEFAULT_IFMT = 'f'

    def __init__(self):
        self.registers = dict()
        self.stack = deque()
        self.frames = deque([self.stack])
        self.current = self.stack
        self.ifmt = type(self).IFMTS[type(self).DEFAULT_IFMT]
        # TODO: Endianness

    def feed(self, match):
        parsed = next(self.parse(match))
        if isstackable(match):
            self._pshstack(parsed)
        else:
            self._apply(parsed)

    def parse(self, match):
        groups = matchedgroups(match)
        if 'str' in groups:
            yield groups['__str__']
        elif 'number' in groups:
            yield self.ifmt(groups['number'])
        elif 'operator' in groups:
            yield OPERATORS[groups['operator']]
        elif 'apply' in groups:
            yield self.apply

    def _arity(self, f):
        if not callable(f):
            return None
        signature = inspect.signature(f)
        parameters = signature.parameters.values()
        positionals = [parameter
                    for parameter
                    in parameters
                    if parameter.kind == Parameter.POSITIONAL_OR_KEYWORD and
                        parameter.default == Parameter.empty]
        return len(positionals)

    def apply(self):
        f = self._popstack()[0]
        self._apply(NAMESPACE[f])

    def _apply(self, parsed):
        if parsed in type(self).FUNCTIONS.values():
            parsed = partial(parsed, self)
        # If you don't do this, you'll do 2**9 when you say 9 2 ^ instead of
        # 9**2.
        args = reversed(self._popstack(self._arity(parsed)))
        res = parsed(*args)
        if res is not None:
            self._pshstack(res)

    def _convert(number):
        return self.ofmt(number)

    def newstack(self):
        newstack = deque()
        self.stack.append(newstack)
        self.frames.append(newstack)
        self.current = newstack

    def retstack(self):
        self.current = self.frames[-2]

    def clrstack(self):
        self.current.clear()

    def clrstacks(self):
        self.stack = deque()
        self.current = self.stack

    def printtop(self):
        print(self.current[-1])

    def printcur(self):
        # TODO: Output endianness?
        print(*reversed(self.current), sep='\n')

    def printall(self):
        # TODO: Output endianness?
        print(*reversed(self.stack), sep='\n')

    def _pshstack(self, *new):
        self.current.extend(new)

    pshstack = _pshstack

    def _popstack(self, n=1):
        return [self.current.pop() for _ in range(n)]

    def dupstack(self):
        top = self._popstack()[0]
        self._pshstack(top)
        self._pshstack(top)

    def popstack(self):
        print(self._popstack()[0])

    def revstack(self):
        self._pshstack(reversed(self._popstack(n=2)))

    def rotstack(self, n):
        self.current.rotate(n)

    def printhelp(self):
        print('functions:', *sorted(NAMESPACE), file=stderr)
        print('operators:', *sorted(OPERATORS), file=stderr)

    def load(self, name):
        self._pshstack(self.registers[name])

    def store(self, name, value):
        self.registers[name] = value

    # TODO: Create proper lexer class bound to a Machine
    FUNCTIONS = {
        # TODO: How to bind to machine?
        '[': newstack,
        ']': retstack,
        'p': printtop,
        'P': popstack,
        'f': printcur,
        'F': printall,
        'd': dupstack,
        'r': revstack,
        'R': rotstack,
        # TODO: Clear all the stacks
        'c': clrstack,
        'C': clrstacks,
        'h': printhelp,
        's': store,
        'l': load,
    }

NUMBER = r'''
          (?:
            \d+
            (?:
              \.
              \d*
            )?
          )|(?:
            \d*
            \.
            \d+
          )
          '''
STR = r'''
       (?:
         '
         (?<__str__>
           (?:
             [^'\\]
             |
             \\'
           )*
         )
         '
       )|(?:
         \\
         (?<__str__>
           .
         )
       )
       '''

# FIXME: *really* dirty hack around inspect.signature not working on some
# builtins.
def unary(f):
    # FIXME: @wraps(f) just exposes the original bug
    #@wraps(f)
    def wrapped(only):
        return f(only)
    return wrapped

def binary(f):
    #@wraps(f)
    def wrapped(left, right):
        return f(left, right)
    return wrapped

BUILTINS = {
    # Arithmetic
    '+': binary(operator.__add__),
    '-': binary(operator.__sub__),
    # FIXME: Something better? Woulda been nice to keep this for floor
    '_': unary(operator.__neg__),
    '*': binary(operator.__mul__),
    '/': binary(operator.__truediv__),
    '%': binary(operator.__mod__),
    '^': binary(operator.__pow__),

    # Logical
    '=': binary(operator.__eq__),
    '!': unary(operator.__not__),

    # Bitwise
    '«': binary(operator.__lshift__),
    '»': binary(operator.__rshift__),
    '~': unary(operator.__invert__),
}

SYMBOLS = {
    # TODO: U+2200-U+222A, and more
    '\N{INFINITY}': math.inf,
    '\N{EMPTY SET}': set(),
    '\N{INTERSECTION}': set.intersection,
    '\N{UNION}': set.union,
    '\N{DEGREE SIGN}': math.degrees,
}

# FIXME: Workaround for inspect.signature not working on these:
MATH = {
    'acos': unary(math.acos),
    'acosh': unary(math.acosh),
    'asin': unary(math.asin),
    'asinh': unary(math.asinh),
    'atan': unary(math.atan),
    'atan2': binary(math.atan2),
    'atanh': unary(math.atanh),
    'ceil': unary(math.ceil),
    'copysign': binary(math.copysign),
    'cos': unary(math.cos),
    'cosh': unary(math.cosh),
    'degrees': unary(math.degrees),
    'e': lambda: math.e,
    'erf': unary(math.erf),
    'erfc': unary(math.erfc),
    'exp': unary(math.exp),
    'expm1': unary(math.expm1),
    'fabs': unary(math.fabs),
    'factorial': unary(math.factorial),
    'floor': unary(math.floor),
    'fmod': unary(math.fmod),
    'frexp': unary(math.frexp),
    'fsum': unary(math.fsum),
    'gamma': unary(math.gamma),
    'gcd': binary(math.gcd),
    'hypot': binary(math.hypot),
    'inf': lambda: math.inf,
    'isclose': binary(math.isclose),
    'isfinite': unary(math.isfinite),
    'isinf': unary(math.isinf),
    'isnan': unary(math.isnan),
    'ldexp': binary(math.ldexp),
    'lgamma': unary(math.lgamma),
    'log': unary(math.log),
    'log10': unary(math.log10),
    'log1p': unary(math.log1p),
    'log2': unary(math.log2),
    'modf': unary(math.modf),
    'nan': lambda: math.nan,
    'pi': lambda: math.pi,
    'pow': binary(math.pow),
    'radians': unary(math.radians),
    'sin': unary(math.sin),
    'sinh': unary(math.sinh),
    'sqrt': unary(math.sqrt),
    'tan': unary(math.tan),
    'tanh': unary(math.tanh),
    'trunc': unary(math.trunc),
}
#MATH = {
#    key: value
#    for key, value
#    in math.__dict__.items()
#    if not key.startswith('_')
#}
SHORTHAND = {
    'v': unary(math.sqrt),
}

OPERATORS = dict()
for namespace in SYMBOLS, Machine.FUNCTIONS, BUILTINS:
    OPERATORS.update(namespace)
NAMESPACE = dict()
for namespace in MATH, SHORTHAND:
    NAMESPACE.update(namespace)

OPERATOR = r'(?:' + '|'.join(map(regex.escape, OPERATORS)) + r')'
# TODO: a and A instead? Would make this no longer a special case, which is
# kinda nice.
APPLY = r'\$'
SPACE = r'\s+'

IMMEDIATE = r'(?<str>' + STR + r')|' \
            r'(?<operator>' + OPERATOR + r')|' \
            r'(?<apply>' + APPLY + r')|' \
            r'(?<space>' + SPACE + r')'
LEXEME = r'(?<number>' + NUMBER + r')|' \
         r'(?<immediate>' + IMMEDIATE + r')'
FLAGS = reduce(operator.__or__,
               {regex.POSIX,
                regex.DOTALL,
                regex.VERSION1,
                regex.VERBOSE},
               0)

class Lexer:
    def __init__(self, machine):
        self.machine = machine

    def feed(line):
        raise NotImplementedError()

    @staticmethod
    def lex(line):
        while line:
            match = regex.match(LEXEME, line, flags=FLAGS)
            if match is None:
                break
            yield match
            line = line[len(match.group(0)):]

def isfeedable(match):
    return 'space' not in matchedgroups(match).keys()

def isstackable(match):
    return bool(matchedgroups(match).keys() & {'str', 'number'})

def isimmediate(match):
    return 'immediate' in match.groupdict()

def matchedgroups(match):
    return {key:value
            for key, value
            in match.groupdict().items()
            if value and key != 'immediate'}

def dumper():
    machine = Machine()
    print('[groups] <repr(repr)> <arity>')
    for line in stdin:
        for match in Lexer.lex(line):
            matched = match.group(0)
            groups = matchedgroups(match).keys()
            parsed = next(machine.parse(match), None)
            print(*groups,
                  repr(matched),
                  machine._arity(parsed))

def executor():
    machine = Machine()
    for line in stdin:
        for match in Lexer.lex(line):
            if isimmediate(match) and isfeedable(match):
                machine.feed(match)

def grammar():
    with open('grammar.pcre') as fp:
        print(fp.read().rstrip())

def lex():
    raise NotImplementedError()

argument_parser = ArgumentParser(description='RPN calculator')
argument_parser.add_argument('-e', '--expression', nargs=REMAINDER)
for short_, long_, action in [('-g', '--grammar', grammar),
                              ('-l', '--lex', lex),
                              ('-D', '--dump', dumper)]:
    argument_parser.add_argument(short_, long_,
                                 action='store_const', const=action,
                                 dest='action')
argument_parser.set_defaults(action=executor)

if __name__ == '__main__':
    args = argument_parser.parse_args()
    args.action()
<commit_msg>Tab-separate dumped output.<commit_after>#! /usr/bin/env python3

from sys import stdin, stderr
from decimal import Decimal
from datetime import datetime, time
from fractions import Fraction, gcd
from inspect import signature as getsignature, Parameter
from functools import reduce, wraps, partial
from collections import deque
from argparse import ArgumentParser, REMAINDER

import inspect

import operator
import math

import regex

class Machine:
    IFMTS = {
        'i': int,
        'f': float,
        'D': Decimal,

        'd': datetime.fromtimestamp,
        't': time,

        # TODO: Are these two any use?
        'c': complex,
        'F': Fraction,
    }
    DEFAULT_IFMT = 'f'

    def __init__(self):
        self.registers = dict()
        self.stack = deque()
        self.frames = deque([self.stack])
        self.current = self.stack
        self.ifmt = type(self).IFMTS[type(self).DEFAULT_IFMT]
        # TODO: Endianness

    def feed(self, match):
        parsed = next(self.parse(match))
        if isstackable(match):
            self._pshstack(parsed)
        else:
            self._apply(parsed)

    def parse(self, match):
        groups = matchedgroups(match)
        if 'str' in groups:
            yield groups['__str__']
        elif 'number' in groups:
            yield self.ifmt(groups['number'])
        elif 'operator' in groups:
            yield OPERATORS[groups['operator']]
        elif 'apply' in groups:
            yield self.apply

    def _arity(self, f):
        if not callable(f):
            return None
        signature = inspect.signature(f)
        parameters = signature.parameters.values()
        positionals = [parameter
                    for parameter
                    in parameters
                    if parameter.kind == Parameter.POSITIONAL_OR_KEYWORD and
                        parameter.default == Parameter.empty]
        return len(positionals)

    def apply(self):
        f = self._popstack()[0]
        self._apply(NAMESPACE[f])

    def _apply(self, parsed):
        if parsed in type(self).FUNCTIONS.values():
            parsed = partial(parsed, self)
        # If you don't do this, you'll do 2**9 when you say 9 2 ^ instead of
        # 9**2.
        args = reversed(self._popstack(self._arity(parsed)))
        res = parsed(*args)
        if res is not None:
            self._pshstack(res)

    def _convert(number):
        return self.ofmt(number)

    def newstack(self):
        newstack = deque()
        self.stack.append(newstack)
        self.frames.append(newstack)
        self.current = newstack

    def retstack(self):
        self.current = self.frames[-2]

    def clrstack(self):
        self.current.clear()

    def clrstacks(self):
        self.stack = deque()
        self.current = self.stack

    def printtop(self):
        print(self.current[-1])

    def printcur(self):
        # TODO: Output endianness?
        print(*reversed(self.current), sep='\n')

    def printall(self):
        # TODO: Output endianness?
        print(*reversed(self.stack), sep='\n')

    def _pshstack(self, *new):
        self.current.extend(new)

    pshstack = _pshstack

    def _popstack(self, n=1):
        return [self.current.pop() for _ in range(n)]

    def dupstack(self):
        top = self._popstack()[0]
        self._pshstack(top)
        self._pshstack(top)

    def popstack(self):
        print(self._popstack()[0])

    def revstack(self):
        self._pshstack(reversed(self._popstack(n=2)))

    def rotstack(self, n):
        self.current.rotate(n)

    def printhelp(self):
        print('functions:', *sorted(NAMESPACE), file=stderr)
        print('operators:', *sorted(OPERATORS), file=stderr)

    def load(self, name):
        self._pshstack(self.registers[name])

    def store(self, name, value):
        self.registers[name] = value

    # TODO: Create proper lexer class bound to a Machine
    FUNCTIONS = {
        # TODO: How to bind to machine?
        '[': newstack,
        ']': retstack,
        'p': printtop,
        'P': popstack,
        'f': printcur,
        'F': printall,
        'd': dupstack,
        'r': revstack,
        'R': rotstack,
        # TODO: Clear all the stacks
        'c': clrstack,
        'C': clrstacks,
        'h': printhelp,
        's': store,
        'l': load,
    }

NUMBER = r'''
          (?:
            \d+
            (?:
              \.
              \d*
            )?
          )|(?:
            \d*
            \.
            \d+
          )
          '''
STR = r'''
       (?:
         '
         (?<__str__>
           (?:
             [^'\\]
             |
             \\'
           )*
         )
         '
       )|(?:
         \\
         (?<__str__>
           .
         )
       )
       '''

# FIXME: *really* dirty hack around inspect.signature not working on some
# builtins.
def unary(f):
    # FIXME: @wraps(f) just exposes the original bug
    #@wraps(f)
    def wrapped(only):
        return f(only)
    return wrapped

def binary(f):
    #@wraps(f)
    def wrapped(left, right):
        return f(left, right)
    return wrapped

BUILTINS = {
    # Arithmetic
    '+': binary(operator.__add__),
    '-': binary(operator.__sub__),
    # FIXME: Something better? Woulda been nice to keep this for floor
    '_': unary(operator.__neg__),
    '*': binary(operator.__mul__),
    '/': binary(operator.__truediv__),
    '%': binary(operator.__mod__),
    '^': binary(operator.__pow__),

    # Logical
    '=': binary(operator.__eq__),
    '!': unary(operator.__not__),

    # Bitwise
    '«': binary(operator.__lshift__),
    '»': binary(operator.__rshift__),
    '~': unary(operator.__invert__),
}

SYMBOLS = {
    # TODO: U+2200-U+222A, and more
    '\N{INFINITY}': math.inf,
    '\N{EMPTY SET}': set(),
    '\N{INTERSECTION}': set.intersection,
    '\N{UNION}': set.union,
    '\N{DEGREE SIGN}': math.degrees,
}

# FIXME: Workaround for inspect.signature not working on these:
MATH = {
    'acos': unary(math.acos),
    'acosh': unary(math.acosh),
    'asin': unary(math.asin),
    'asinh': unary(math.asinh),
    'atan': unary(math.atan),
    'atan2': binary(math.atan2),
    'atanh': unary(math.atanh),
    'ceil': unary(math.ceil),
    'copysign': binary(math.copysign),
    'cos': unary(math.cos),
    'cosh': unary(math.cosh),
    'degrees': unary(math.degrees),
    'e': lambda: math.e,
    'erf': unary(math.erf),
    'erfc': unary(math.erfc),
    'exp': unary(math.exp),
    'expm1': unary(math.expm1),
    'fabs': unary(math.fabs),
    'factorial': unary(math.factorial),
    'floor': unary(math.floor),
    'fmod': unary(math.fmod),
    'frexp': unary(math.frexp),
    'fsum': unary(math.fsum),
    'gamma': unary(math.gamma),
    'gcd': binary(math.gcd),
    'hypot': binary(math.hypot),
    'inf': lambda: math.inf,
    'isclose': binary(math.isclose),
    'isfinite': unary(math.isfinite),
    'isinf': unary(math.isinf),
    'isnan': unary(math.isnan),
    'ldexp': binary(math.ldexp),
    'lgamma': unary(math.lgamma),
    'log': unary(math.log),
    'log10': unary(math.log10),
    'log1p': unary(math.log1p),
    'log2': unary(math.log2),
    'modf': unary(math.modf),
    'nan': lambda: math.nan,
    'pi': lambda: math.pi,
    'pow': binary(math.pow),
    'radians': unary(math.radians),
    'sin': unary(math.sin),
    'sinh': unary(math.sinh),
    'sqrt': unary(math.sqrt),
    'tan': unary(math.tan),
    'tanh': unary(math.tanh),
    'trunc': unary(math.trunc),
}
#MATH = {
#    key: value
#    for key, value
#    in math.__dict__.items()
#    if not key.startswith('_')
#}
SHORTHAND = {
    'v': unary(math.sqrt),
}

OPERATORS = dict()
for namespace in SYMBOLS, Machine.FUNCTIONS, BUILTINS:
    OPERATORS.update(namespace)
NAMESPACE = dict()
for namespace in MATH, SHORTHAND:
    NAMESPACE.update(namespace)

OPERATOR = r'(?:' + '|'.join(map(regex.escape, OPERATORS)) + r')'
# TODO: a and A instead? Would make this no longer a special case, which is
# kinda nice.
APPLY = r'\$'
SPACE = r'\s+'

IMMEDIATE = r'(?<str>' + STR + r')|' \
            r'(?<operator>' + OPERATOR + r')|' \
            r'(?<apply>' + APPLY + r')|' \
            r'(?<space>' + SPACE + r')'
LEXEME = r'(?<number>' + NUMBER + r')|' \
         r'(?<immediate>' + IMMEDIATE + r')'
FLAGS = reduce(operator.__or__,
               {regex.POSIX,
                regex.DOTALL,
                regex.VERSION1,
                regex.VERBOSE},
               0)

class Lexer:
    def __init__(self, machine):
        self.machine = machine

    def feed(line):
        raise NotImplementedError()

    @staticmethod
    def lex(line):
        while line:
            match = regex.match(LEXEME, line, flags=FLAGS)
            if match is None:
                break
            yield match
            line = line[len(match.group(0)):]

def isfeedable(match):
    return 'space' not in matchedgroups(match).keys()

def isstackable(match):
    return bool(matchedgroups(match).keys() & {'str', 'number'})

def isimmediate(match):
    return 'immediate' in match.groupdict()

def matchedgroups(match):
    return {key:value
            for key, value
            in match.groupdict().items()
            if value and key != 'immediate'}

def dumper():
    machine = Machine()
    print('[groups]\t<repr(repr)>\t<arity>')
    for line in stdin:
        for match in Lexer.lex(line):
            matched = match.group(0)
            groups = matchedgroups(match).keys()
            parsed = next(machine.parse(match), None)
            print(*groups,
                  repr(matched),
                  machine._arity(parsed),
                  sep='\t')

def executor():
    machine = Machine()
    for line in stdin:
        for match in Lexer.lex(line):
            if isimmediate(match) and isfeedable(match):
                machine.feed(match)

def grammar():
    with open('grammar.pcre') as fp:
        print(fp.read().rstrip())

def lex():
    raise NotImplementedError()

argument_parser = ArgumentParser(description='RPN calculator')
argument_parser.add_argument('-e', '--expression', nargs=REMAINDER)
for short_, long_, action in [('-g', '--grammar', grammar),
                              ('-l', '--lex', lex),
                              ('-D', '--dump', dumper)]:
    argument_parser.add_argument(short_, long_,
                                 action='store_const', const=action,
                                 dest='action')
argument_parser.set_defaults(action=executor)

if __name__ == '__main__':
    args = argument_parser.parse_args()
    args.action()
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
'''flask-reveal

Usage:
    run.py start [-d | --debug] [-p | --path PATH]
    run.py (-h | --help)

Options:
    -d --debug          Start flask with debug mode on.
    -p --path PATH      Presentation directory [default: ./].
    -h --help           Show this help.

'''

import os

from docopt import docopt

from flask_reveal.app import create_app

def start(path, debug):
    if os.path.isdir(path):
        app = create_app(path)

        app.run(debug=debug)
    else:
        print('This is not a valid directory')

if __name__ == '__main__':
    arguments = docopt(__doc__)

    if arguments['start']:
        start(arguments['--path'][0], arguments['--debug'])
<commit_msg>including reveal instalation/update from file<commit_after># -*- coding: utf-8 -*-
'''flask-reveal

Usage:
    run.py start [-d | --debug] [-p | --path PATH]
    run.py installreveal [-f | --file FILE]
    run.py (-h | --help)

Options:
    -d --debug          Start flask with debug mode on.
    -p --path PATH      Presentation directory [default: ./].
    -f --file FILE      Reveal.js tar.gz file.
    -h --help           Show this help.

'''

import os
import shutil
import tarfile
import zipfile

from docopt import docopt

from flask_reveal.app import create_app

def start(path, debug):
    if os.path.isdir(path):
        app = create_app(path)

        app.run(debug=debug)
    else:
        print('This is not a valid directory')

def move_and_replace(src, dst):
    src = os.path.abspath(src)
    dst = os.path.abspath(dst)

    for src_dir, dirs, files in os.walk(src):
        dst_dir = src_dir.replace(src, dst)

        if not os.path.exists(dst_dir):
            os.mkdir(dst_dir)

        for file in files:
            src_file = os.path.join(src_dir, file)
            dst_file = os.path.join(dst_dir, file)

            if os.path.exists(dst_file):
                os.remove(dst_file)

            shutil.move(src_file, dst_dir)

    shutil.rmtree(src)

def install_reveal_from_file(file):
    static_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), 'flask_reveal/static/'))

    if os.path.isfile(file):
        if tarfile.is_tarfile(file):
            with tarfile.open(file, 'r:gz') as tfile:
                basename = tfile.members[0].name
                tfile.extractall()

            move_and_replace(basename, static_folder)
        elif zipfile.is_zipfile(file):
            with zipfile.ZipFile(file, 'r') as zfile:
                basename = zfile.namelist()[0]
                zfile.extractall()

            move_and_replace(basename, static_folder)
        else:
            print('File type not supported')
    else:
        print('This is not a valid file')

if __name__ == '__main__':
    arguments = docopt(__doc__)

    if arguments['start']:
        start(arguments['--path'][0], arguments['--debug'])
    elif arguments['installreveal']:
        install_reveal_from_file(arguments['--file'][0])<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
#!/usr/bin/env python
"""
Run sasview in place.  This allows sasview to use the python
files in the source tree without having to call setup.py install
first.  A rebuild is still necessary when working on sas models
or c modules.

Usage:

./run.py [(module|script) args...]

Without arguments run.py runs sasview.  With arguments, run.py will run
the given module or script.
"""
from __future__ import print_function

import imp
import os
import sys
from contextlib import contextmanager
from os.path import join as joinpath
from os.path import abspath, dirname

def addpath(path):
    """
    Add a directory to the python path environment, and to the PYTHONPATH
    environment variable for subprocesses.
    """
    path = abspath(path)
    if 'PYTHONPATH' in os.environ:
        PYTHONPATH = path + os.pathsep + os.environ['PYTHONPATH']
    else:
        PYTHONPATH = path
    os.environ['PYTHONPATH'] = PYTHONPATH
    sys.path.insert(0, path)

@contextmanager
def cd(path):
    """
    Change directory for duration of "with" context.
    """
    old_dir = os.getcwd()
    os.chdir(path)
    yield
    os.chdir(old_dir)

def import_package(modname, path):
    """Import a package into a particular point in the python namespace"""
    #logger.debug("Dynamicly importing: %s", path)
    mod = imp.load_source(modname, abspath(joinpath(path, '__init__.py')))
    sys.modules[modname] = mod
    mod.__path__ = [abspath(path)]
    return mod

def import_dll(modname, build_path):
    """Import a DLL from the build directory"""
    import sysconfig
    ext = sysconfig.get_config_var('SO')
    # build_path comes from context
    path = joinpath(build_path, *modname.split('.')) + ext
    # print "importing", modname, "from", path
    return imp.load_dynamic(modname, path)

def prepare():
    # Don't create *.pyc files
    sys.dont_write_bytecode = True

    # Debug numpy warnings
    #import numpy; numpy.seterr(all='raise')

    # find the directories for the source and build
    from distutils.util import get_platform
    root = abspath(dirname(__file__))
    platform = '%s-%s' % (get_platform(), sys.version[:3])
    build_path = joinpath(root, 'build', 'lib.' + platform)

    # Notify the help menu that the Sphinx documentation is in a different
    # place than it otherwise would be.
    os.environ['SASVIEW_DOC_PATH'] = joinpath(build_path, "doc")

    # Make sure that we have a private version of mplconfig
    #mplconfig = joinpath(abspath(dirname(__file__)), '.mplconfig')
    #os.environ['MPLCONFIGDIR'] = mplconfig
    #if not os.path.exists(mplconfig): os.mkdir(mplconfig)
    #import matplotlib
    # matplotlib.use('Agg')
    # print matplotlib.__file__
    #import pylab; pylab.hold(False)
    # add periodictable to the path
    try:
        import periodictable
    except:
        addpath(joinpath(root, '..', 'periodictable'))

    try:
        import bumps
    except:
        addpath(joinpath(root, '..', 'bumps'))

    # select wx version
    #addpath(os.path.join(root, '..','wxPython-src-3.0.0.0','wxPython'))

    # Build project if the build directory does not already exist.
    if not os.path.exists(build_path):
        import subprocess
        with cd(root):
            subprocess.call((sys.executable, "setup.py", "build"), shell=False)

    # Put the source trees on the path
    addpath(joinpath(root, 'src'))

    # sasmodels on the path
    addpath(joinpath(root, '../sasmodels/'))

    # The sas.models package Compiled Model files should be pulled in from the build directory even though
    # the source is stored in src/sas/models.

    # Compiled modules need to be pulled from the build directory.
    # Some packages are not where they are needed, so load them explicitly.
    import sas.sascalc.pr
    sas.sascalc.pr.core = import_package('sas.sascalc.pr.core',
                                         joinpath(build_path, 'sas', 'sascalc', 'pr', 'core'))

    # Compiled modules need to be pulled from the build directory.
    # Some packages are not where they are needed, so load them explicitly.
    import sas.sascalc.file_converter
    sas.sascalc.file_converter.core = import_package('sas.sascalc.file_converter.core',
                                                     joinpath(build_path, 'sas', 'sascalc', 'file_converter', 'core'))

    import sas.sascalc.calculator
    sas.sascalc.calculator.core = import_package('sas.sascalc.calculator.core',
                                                 joinpath(build_path, 'sas', 'sascalc', 'calculator', 'core'))

    sys.path.append(build_path)

    set_git_tag()
    # print "\n".join(sys.path)

def set_git_tag():
    try:
        import subprocess
        import os
        import platform
        FNULL = open(os.devnull, 'w')
        if platform.system() == "Windows":
            args = ['git', 'describe', '--tags']
        else:
            args = ['git describe --tags']
        git_revision = subprocess.check_output(args, stderr=FNULL, shell=True)
        import sas.sasview
        sas.sasview.__build__ = str(git_revision).strip()
    except subprocess.CalledProcessError as cpe:
        get_logger().warning("Error while determining build number\n  Using command:\n %s \n Output:\n %s"% (cpe.cmd,cpe.output))

_logger = None
def get_logger():
    global _logger
    if _logger is None:
        from sas.logger_config import SetupLogger
        _logger = SetupLogger(__name__).config_development()
    return _logger

if __name__ == "__main__":
    # Need to add absolute path before actual prepare call,
    # so logging can be done during initialization process too
    root = abspath(dirname(__file__))
    addpath(joinpath(root, 'src'))

    get_logger().debug("Starting SASVIEW in debug mode.")
    prepare()
    from sas.sasview.sasview import run_gui
    run_gui()
    get_logger().debug("Ending SASVIEW in debug mode.")
<commit_msg>run without gui if run.py includes a script file; include tinycc path on windows<commit_after># -*- coding: utf-8 -*-
#!/usr/bin/env python
"""
Run sasview in place.  This allows sasview to use the python
files in the source tree without having to call setup.py install
first.  A rebuild is still necessary when working on sas models
or c modules.

Usage:

./run.py [(module|script) args...]

Without arguments run.py runs sasview.  With arguments, run.py will run
the given module or script.
"""
from __future__ import print_function

import imp
import os
import sys
from contextlib import contextmanager
from os.path import join as joinpath
from os.path import abspath, dirname

def addpath(path):
    """
    Add a directory to the python path environment, and to the PYTHONPATH
    environment variable for subprocesses.
    """
    path = abspath(path)
    if 'PYTHONPATH' in os.environ:
        PYTHONPATH = path + os.pathsep + os.environ['PYTHONPATH']
    else:
        PYTHONPATH = path
    os.environ['PYTHONPATH'] = PYTHONPATH
    sys.path.insert(0, path)

@contextmanager
def cd(path):
    """
    Change directory for duration of "with" context.
    """
    old_dir = os.getcwd()
    os.chdir(path)
    yield
    os.chdir(old_dir)

def import_package(modname, path):
    """Import a package into a particular point in the python namespace"""
    #logger.debug("Dynamicly importing: %s", path)
    mod = imp.load_source(modname, abspath(joinpath(path, '__init__.py')))
    sys.modules[modname] = mod
    mod.__path__ = [abspath(path)]
    return mod

def import_dll(modname, build_path):
    """Import a DLL from the build directory"""
    import sysconfig
    ext = sysconfig.get_config_var('SO')
    # build_path comes from context
    path = joinpath(build_path, *modname.split('.')) + ext
    # print "importing", modname, "from", path
    return imp.load_dynamic(modname, path)

def prepare():
    # Don't create *.pyc files
    sys.dont_write_bytecode = True

    # Debug numpy warnings
    #import numpy; numpy.seterr(all='raise')

    # find the directories for the source and build
    from distutils.util import get_platform
    root = abspath(dirname(__file__))
    platform = '%s-%s' % (get_platform(), sys.version[:3])
    build_path = joinpath(root, 'build', 'lib.' + platform)

    # Notify the help menu that the Sphinx documentation is in a different
    # place than it otherwise would be.
    os.environ['SASVIEW_DOC_PATH'] = joinpath(build_path, "doc")

    # Make sure that we have a private version of mplconfig
    #mplconfig = joinpath(abspath(dirname(__file__)), '.mplconfig')
    #os.environ['MPLCONFIGDIR'] = mplconfig
    #if not os.path.exists(mplconfig): os.mkdir(mplconfig)
    #import matplotlib
    # matplotlib.use('Agg')
    # print matplotlib.__file__
    #import pylab; pylab.hold(False)
    # add periodictable to the path
    try:
        import periodictable
    except:
        addpath(joinpath(root, '..', 'periodictable'))

    try:
        import bumps
    except:
        addpath(joinpath(root, '..', 'bumps'))

    try:
        import tinycc
    except:
        addpath(joinpath(root, '../tinycc/build/lib'))

    # select wx version
    #addpath(os.path.join(root, '..','wxPython-src-3.0.0.0','wxPython'))

    # Build project if the build directory does not already exist.
    if not os.path.exists(build_path):
        import subprocess
        with cd(root):
            subprocess.call((sys.executable, "setup.py", "build"), shell=False)

    # Put the source trees on the path
    addpath(joinpath(root, 'src'))

    # sasmodels on the path
    addpath(joinpath(root, '../sasmodels/'))

    # The sas.models package Compiled Model files should be pulled in from the build directory even though
    # the source is stored in src/sas/models.

    # Compiled modules need to be pulled from the build directory.
    # Some packages are not where they are needed, so load them explicitly.
    import sas.sascalc.pr
    sas.sascalc.pr.core = import_package('sas.sascalc.pr.core',
                                         joinpath(build_path, 'sas', 'sascalc', 'pr', 'core'))

    # Compiled modules need to be pulled from the build directory.
    # Some packages are not where they are needed, so load them explicitly.
    import sas.sascalc.file_converter
    sas.sascalc.file_converter.core = import_package('sas.sascalc.file_converter.core',
                                                     joinpath(build_path, 'sas', 'sascalc', 'file_converter', 'core'))

    import sas.sascalc.calculator
    sas.sascalc.calculator.core = import_package('sas.sascalc.calculator.core',
                                                 joinpath(build_path, 'sas', 'sascalc', 'calculator', 'core'))

    sys.path.append(build_path)

    set_git_tag()
    # print "\n".join(sys.path)

def set_git_tag():
    try:
        import subprocess
        import os
        import platform
        FNULL = open(os.devnull, 'w')
        if platform.system() == "Windows":
            args = ['git', 'describe', '--tags']
        else:
            args = ['git describe --tags']
        git_revision = subprocess.check_output(args, stderr=FNULL, shell=True)
        import sas.sasview
        sas.sasview.__build__ = str(git_revision).strip()
    except subprocess.CalledProcessError as cpe:
        get_logger().warning("Error while determining build number\n  Using command:\n %s \n Output:\n %s"% (cpe.cmd,cpe.output))

_logger = None
def get_logger():
    global _logger
    if _logger is None:
        from sas.logger_config import SetupLogger
        _logger = SetupLogger(__name__).config_development()
    return _logger

if __name__ == "__main__":
    # Need to add absolute path before actual prepare call,
    # so logging can be done during initialization process too
    root = abspath(dirname(__file__))
    addpath(joinpath(root, 'src'))

    get_logger().debug("Starting SASVIEW in debug mode.")
    prepare()
    from sas.sasview.sasview import run_cli, run_gui
    if len(sys.argv) == 1:
        run_gui()
    else:
        run_cli()
    get_logger().debug("Ending SASVIEW in debug mode.")
<|endoftext|>


<commit_before>import sys
import logging
import random
import json
import ssl

from eve import Eve
from flask import abort, jsonify, g
from hashlib import sha1
from atlas import tasks
from atlas import utilities
from atlas.config import *

path = '/data/code'
if path not in sys.path:
    sys.path.append(path)

# Callbacks
def pre_post_callback(resource, request):
    """
    :param resource: resource accessed
    :param request: flask.request object
    """
    app.logger.debug('POST to {0} resource\nRequest:\n{1}'.format(resource, request.json))

def pre_delete_code_callback(request, lookup):
    """
    Make sure no sites are using the code.

    :param request: flask.request object
    :param lookup:
    """
    code = utilities.get_single_eve('code', lookup['_id'])
    app.logger.debug(code)
    if code['meta']['code_type'] in ['module', 'theme', 'library']:
        code_type = 'package'
    else:
        code_type = code['meta']['code_type']
    app.logger.debug(code_type)
    site_query = 'where={{"code.{0}":"{1}"}}'.format(code_type, code['_id'])
    sites = utilities.get_eve('sites', site_query)
    app.logger.debug(sites)
    if not sites['_meta']['total'] == 0:
        site_list = []
        for site in sites['_items']:
            # Create a list of sites that use this code item.
            # If 'sid' is a key in the site dict use it, otherwise use '_id'.
            if site.get('sid'):
                site_list.append(site['sid'])
            else:
                site_list.append(site['_id'])
        site_list_full = ', '.join(site_list)
        app.logger.error('Code item is in use by one or more sites:\n{0}'.format(site_list_full))
        abort(409, 'A conflict happened while processing the request. Code item is in use by one or more sites.')

def on_insert_sites_callback(items):
    """
    Assign a sid, an update group, db_key, any missing code, and date fields.

    :param items: List of dicts for items to be created.
    """
    app.logger.debug(items)
    for item in items:
        app.logger.debug(item)
        if item['type'] == 'express':
            if not item['sid']:
                item['sid'] = 'p1' + sha1(utilities.randomstring()).hexdigest()[0:10]
            if not item['path']:
                item['path'] = item['sid']
            if not item['update_group']:
                item['update_group'] = random.randint(0, 2)
            # Add default core and profile if not set.
            # The 'get' method checks if the key exists.
            if item.get('code'):
                if not item['code'].get('core'):
                    item['code']['core'] = utilities.get_current_code(name=default_core, type='core')
                if not item['code'].get('profile'):
                    item['code']['profile'] = utilities.get_current_code(name=default_profile, type='profile')
            else:
                item['code'] = {}
                item['code']['core'] = utilities.get_current_code(name=default_core, type='core')
                item['code']['profile'] = utilities.get_current_code(name=default_profile, type='profile')
            if not item['import_from_inventory']:
                date_json = '{{"created":"{0} GMT"}}'.format(item['_created'])
                item['dates'] = json.loads(date_json)
            app.logger.debug('Ready to create item\n{0}'.format(item))

def on_inserted_sites_callback(items):
    """
    Provision Express instances.

    :param items: List of dicts for instances to be provisioned.
    """
    app.logger.debug(items)
    for item in items:
        app.logger.debug(item)
        if item['type'] == 'express':
            app.logger.debug(item)
            # Create statistics item
            statistics_payload = {}
            # Need to get the string out of the ObjectID.
            statistics_payload['site'] = str(item['_id'])
            app.logger.debug('Create Statistics item\n{0}'.format(statistics_payload))
            statistics = utilities.post_eve(resource='statistics', payload= statistics_payload)
            app.logger.debug(statistics)
            item['statistics'] = str(statistics['_id'])
            app.logger.debug('Ready to send to Celery\n{0}'.format(item))
            if not item['import_from_inventory']:
                tasks.site_provision.delay(item)
            else:
                tasks.site_import_from_inventory.delay(item)

def on_insert_code_callback(items):
    """
    Deploy code onto servers as the items are created.

    If a new code item 'is_current', PATCH 'is_current' code with the same name
    and type to no longer be current.

    :param items: List of dicts for items to be created.
    """
    app.logger.debug(items)
    for item in items:
        if item.get('meta') and item['meta'].get('is_current') and item['meta']['is_current'] == True:
            # Need a lowercase string when querying boolean values. Python
            # stores it as 'True'.
            query = 'where={{"meta.name":"{0}","meta.code_type":"{1}","meta.is_current": {2}}}'.format(item['meta']['name'], item['meta']['code_type'], str(item['meta']['is_current']).lower())
            code_get = utilities.get_eve('code', query)
            app.logger.debug(code_get)
            for code in code_get['_items']:
                request_payload = {'meta.is_current': False}
                utilities.patch_eve('code', code['_id'], request_payload)
        app.logger.debug('Ready to send to Celery\n{0}'.format(item))
        tasks.code_deploy.delay(item)

def post_delete_site_callback(item):
    """
    Remove site from servers right before the item is removed.

    :param item:
    """
    app.logger.debug(item)
    tasks.site_remove.delay(item)

def on_delete_item_code_callback(item):
    """
    Remove code from servers right before the item is removed.

    :param item:
    """
    app.logger.debug(item)
    tasks.code_remove.delay(item)

def on_update_code_callback(updates, original):
    """
    Update code on the servers as the item is updated.

    :param updates:
    :param original:
    """
    app.logger.debug(updates)
    app.logger.debug(original)
    # If this 'is_current' PATCH code with the same name and code_type.
    if updates.get('meta') and updates['meta'].get('is_current') and updates['meta']['is_current'] == True:
        # If the name and code_type are not changing, we need to load them from
        # the original.
        name = updates['meta']['name'] if updates['meta'].get('name') else original['meta']['name']
        code_type = updates['meta']['code_type'] if updates['meta'].get('code_type') else original['meta']['code_type']

        query = 'where={{"meta.name":"{0}","meta.code_type":"{1}","meta.is_current": {2}}}'.format(name, code_type, str(updates['meta']['is_current']).lower())
        code_get = utilities.get_eve('code', query)
        # TODO: Filter out the site we are updating.
        app.logger.debug(code_get)

        for code in code_get['_items']:
            request_payload = {'meta.is_current': False}
            utilities.patch_eve('code', code['_id'], request_payload)

    # We need the whole record so that we can manipulate code in the right
    # place.
    # Copy 'original' to a new dict, then update it with values from 'updates'
    # to create an item to deploy. Need to do the same process for meta first,
    # otherwise the update will fully overwrite.
    meta = original['meta'].copy()
    meta.update(updates['meta'])
    updated_item = original.copy()
    updated_item.update(updates)
    updated_item['meta'] = meta

    app.logger.debug('Ready to hand to Celery\n{0}\n{1}'.format(updated_item, original))
    tasks.code_update.delay(updated_item, original)

def on_update_sites_callback(updates, original):
    """
    Update an instance.

    :param updates:
    :param original:
    """
    app.logger.debug('Update Site\n{0}\n\n{1}'.format(updates, original))
    site_type = updates['type'] if updates.get('type') else original['type']
    if site_type == 'express':
        item = original.copy()
        item.update(updates)
        # Only need to rewrite the nested dicts if they got updated.
        if updates.get('code'):
            code = original['code'].copy()
            code.update(updates['code'])
            item['code'] = code
        if updates.get('dates'):
            dates = original['dates'].copy()
            dates.update(updates['dates'])
            item['dates'] = dates

        if updates.get('status'):
            if updates['status'] in ['installing', 'launching', 'take_down','restore']:
                if updates['status'] == 'installing':
                    date_json = '{{"assigned":"{0} GMT"}}'.format(updates['_updated'])
                elif updates['status'] == 'launching':
                    date_json = '{{"launched":"{0} GMT"}}'.format(updates['_updated'])
                elif updates['status'] == 'take_down':
                    date_json = '{{"taken_down":"{0} GMT"}}'.format(updates['_updated'])
                elif updates['status'] == 'restore':
                    date_json = '{{"taken_down":""}}'.format(updates['_updated'])

                updates['dates'] = json.loads(date_json)

            elif updates['status'] == 'delete':
                app.logger.debug('Ready to hand to Celery\n{0}'.format(item))
                tasks.site_remove.delay(item)
                return
        app.logger.debug('Ready to hand to Celery\n{0}'.format(item))
        tasks.site_update.delay(item, updates, original)

def on_update_commands_callback(updates, original):
    """
    Run commands when API endpoints are called.

    :param updates:
    :param original:
    """
    item = original.copy()
    item.update(updates)
    app.logger.debug('Update command\n\nItem\n{0}\n\nUpdate\n{1}\n\nOriginal\n{2}'.format(item, updates, original))
    tasks.command_prepare.delay(item)

# Update user fields on all events. If the update is coming from Drupal, it
# will use the client_username for authentication and include the field for
# us. If someone is querying the API directly, they will user their own
# username and we need to add that.
def pre_insert(resource, items):
    username = g.get('username', None)
    if username is not None:
        for item in items:
            item['created_by'] = username
            item['modified_by'] = username

def pre_update(resource, updates, original):
    username = g.get('username', None)
    if username is not None:
        if username is not service_account_username:
            updates['modified_by'] = username

def pre_replace(resource, item, original):
    username = g.get('username', None)
    if username is not None:
        if username is not service_account_username:
            item['modified_by'] = username

"""
Setup the application and logging.
"""
# Tell Eve to use Basic Auth and where our data structure is defined.
app = Eve(auth=utilities.AtlasBasicAuth, settings="/data/code/atlas/config_data_structure.py")
# TODO: Remove debug mode.
app.debug = True

# Specific callbacks. Pattern is: `atlas.on_{Hook}_{Method}_{Resource}`
# Use pre event hooks if there is a chance you want to abort.
# Use DB hooks if you want to modify data on the way in.
app.on_pre_POST += pre_post_callback
app.on_pre_DELETE_code += pre_delete_code_callback
app.on_post_DELETE_site += post_delete_site_callback
app.on_insert_code += on_insert_code_callback
app.on_insert_sites += on_insert_sites_callback
app.on_inserted_sites += on_inserted_sites_callback
app.on_update_code += on_update_code_callback
app.on_update_sites += on_update_sites_callback
app.on_update_commands += on_update_commands_callback
app.on_delete_item_code += on_delete_item_code_callback
app.on_insert += pre_insert
app.on_update += pre_update
app.on_replace += pre_replace

@app.errorhandler(409)
def custom409(error):
    response = jsonify({'message': error.description})
    response.status_code = 409
    return response

if __name__ == '__main__':
    # Enable logging to 'atlas.log' file
    handler = logging.FileHandler('atlas.log')
    # The default log level is set to WARNING, so we have to explicitly set the
    # logging level to Debug.
    app.logger.setLevel(logging.DEBUG)
    # Append the handler to the default application logger
    app.logger.addHandler(handler)

    # This goes last.
    app.run(host='0.0.0.0', ssl_context='adhoc')
<commit_msg>Correct to check for keys.<commit_after>import sys
import logging
import random
import json
import ssl

from eve import Eve
from flask import abort, jsonify, g
from hashlib import sha1
from atlas import tasks
from atlas import utilities
from atlas.config import *

path = '/data/code'
if path not in sys.path:
    sys.path.append(path)

# Callbacks
def pre_post_callback(resource, request):
    """
    :param resource: resource accessed
    :param request: flask.request object
    """
    app.logger.debug('POST to {0} resource\nRequest:\n{1}'.format(resource, request.json))

def pre_delete_code_callback(request, lookup):
    """
    Make sure no sites are using the code.

    :param request: flask.request object
    :param lookup:
    """
    code = utilities.get_single_eve('code', lookup['_id'])
    app.logger.debug(code)
    if code['meta']['code_type'] in ['module', 'theme', 'library']:
        code_type = 'package'
    else:
        code_type = code['meta']['code_type']
    app.logger.debug(code_type)
    site_query = 'where={{"code.{0}":"{1}"}}'.format(code_type, code['_id'])
    sites = utilities.get_eve('sites', site_query)
    app.logger.debug(sites)
    if not sites['_meta']['total'] == 0:
        site_list = []
        for site in sites['_items']:
            # Create a list of sites that use this code item.
            # If 'sid' is a key in the site dict use it, otherwise use '_id'.
            if site.get('sid'):
                site_list.append(site['sid'])
            else:
                site_list.append(site['_id'])
        site_list_full = ', '.join(site_list)
        app.logger.error('Code item is in use by one or more sites:\n{0}'.format(site_list_full))
        abort(409, 'A conflict happened while processing the request. Code item is in use by one or more sites.')

def on_insert_sites_callback(items):
    """
    Assign a sid, an update group, db_key, any missing code, and date fields.

    :param items: List of dicts for items to be created.
    """
    app.logger.debug(items)
    for item in items:
        app.logger.debug(item)
        if item['type'] == 'express':
            if not item.get('sid'):
                item['sid'] = 'p1' + sha1(utilities.randomstring()).hexdigest()[0:10]
            if not item.get('path'):
                item['path'] = item['sid']
            if not item.get('update_group'):
                item['update_group'] = random.randint(0, 2)
            # Add default core and profile if not set.
            # The 'get' method checks if the key exists.
            if item.get('code'):
                if not item['code'].get('core'):
                    item['code']['core'] = utilities.get_current_code(name=default_core, type='core')
                if not item['code'].get('profile'):
                    item['code']['profile'] = utilities.get_current_code(name=default_profile, type='profile')
            else:
                item['code'] = {}
                item['code']['core'] = utilities.get_current_code(name=default_core, type='core')
                item['code']['profile'] = utilities.get_current_code(name=default_profile, type='profile')
            if not item['import_from_inventory']:
                date_json = '{{"created":"{0} GMT"}}'.format(item['_created'])
                item['dates'] = json.loads(date_json)
            app.logger.debug('Ready to create item\n{0}'.format(item))

def on_inserted_sites_callback(items):
    """
    Provision Express instances.

    :param items: List of dicts for instances to be provisioned.
    """
    app.logger.debug(items)
    for item in items:
        app.logger.debug(item)
        if item['type'] == 'express':
            app.logger.debug(item)
            # Create statistics item
            statistics_payload = {}
            # Need to get the string out of the ObjectID.
            statistics_payload['site'] = str(item['_id'])
            app.logger.debug('Create Statistics item\n{0}'.format(statistics_payload))
            statistics = utilities.post_eve(resource='statistics', payload= statistics_payload)
            app.logger.debug(statistics)
            item['statistics'] = str(statistics['_id'])
            app.logger.debug('Ready to send to Celery\n{0}'.format(item))
            if not item['import_from_inventory']:
                tasks.site_provision.delay(item)
            else:
                tasks.site_import_from_inventory.delay(item)

def on_insert_code_callback(items):
    """
    Deploy code onto servers as the items are created.

    If a new code item 'is_current', PATCH 'is_current' code with the same name
    and type to no longer be current.

    :param items: List of dicts for items to be created.
    """
    app.logger.debug(items)
    for item in items:
        if item.get('meta') and item['meta'].get('is_current') and item['meta']['is_current'] == True:
            # Need a lowercase string when querying boolean values. Python
            # stores it as 'True'.
            query = 'where={{"meta.name":"{0}","meta.code_type":"{1}","meta.is_current": {2}}}'.format(item['meta']['name'], item['meta']['code_type'], str(item['meta']['is_current']).lower())
            code_get = utilities.get_eve('code', query)
            app.logger.debug(code_get)
            for code in code_get['_items']:
                request_payload = {'meta.is_current': False}
                utilities.patch_eve('code', code['_id'], request_payload)
        app.logger.debug('Ready to send to Celery\n{0}'.format(item))
        tasks.code_deploy.delay(item)

def post_delete_site_callback(item):
    """
    Remove site from servers right before the item is removed.

    :param item:
    """
    app.logger.debug(item)
    tasks.site_remove.delay(item)

def on_delete_item_code_callback(item):
    """
    Remove code from servers right before the item is removed.

    :param item:
    """
    app.logger.debug(item)
    tasks.code_remove.delay(item)

def on_update_code_callback(updates, original):
    """
    Update code on the servers as the item is updated.

    :param updates:
    :param original:
    """
    app.logger.debug(updates)
    app.logger.debug(original)
    # If this 'is_current' PATCH code with the same name and code_type.
    if updates.get('meta') and updates['meta'].get('is_current') and updates['meta']['is_current'] == True:
        # If the name and code_type are not changing, we need to load them from
        # the original.
        name = updates['meta']['name'] if updates['meta'].get('name') else original['meta']['name']
        code_type = updates['meta']['code_type'] if updates['meta'].get('code_type') else original['meta']['code_type']

        query = 'where={{"meta.name":"{0}","meta.code_type":"{1}","meta.is_current": {2}}}'.format(name, code_type, str(updates['meta']['is_current']).lower())
        code_get = utilities.get_eve('code', query)
        # TODO: Filter out the site we are updating.
        app.logger.debug(code_get)

        for code in code_get['_items']:
            request_payload = {'meta.is_current': False}
            utilities.patch_eve('code', code['_id'], request_payload)

    # We need the whole record so that we can manipulate code in the right
    # place.
    # Copy 'original' to a new dict, then update it with values from 'updates'
    # to create an item to deploy. Need to do the same process for meta first,
    # otherwise the update will fully overwrite.
    meta = original['meta'].copy()
    meta.update(updates['meta'])
    updated_item = original.copy()
    updated_item.update(updates)
    updated_item['meta'] = meta

    app.logger.debug('Ready to hand to Celery\n{0}\n{1}'.format(updated_item, original))
    tasks.code_update.delay(updated_item, original)

def on_update_sites_callback(updates, original):
    """
    Update an instance.

    :param updates:
    :param original:
    """
    app.logger.debug('Update Site\n{0}\n\n{1}'.format(updates, original))
    site_type = updates['type'] if updates.get('type') else original['type']
    if site_type == 'express':
        item = original.copy()
        item.update(updates)
        # Only need to rewrite the nested dicts if they got updated.
        if updates.get('code'):
            code = original['code'].copy()
            code.update(updates['code'])
            item['code'] = code
        if updates.get('dates'):
            dates = original['dates'].copy()
            dates.update(updates['dates'])
            item['dates'] = dates

        if updates.get('status'):
            if updates['status'] in ['installing', 'launching', 'take_down','restore']:
                if updates['status'] == 'installing':
                    date_json = '{{"assigned":"{0} GMT"}}'.format(updates['_updated'])
                elif updates['status'] == 'launching':
                    date_json = '{{"launched":"{0} GMT"}}'.format(updates['_updated'])
                elif updates['status'] == 'take_down':
                    date_json = '{{"taken_down":"{0} GMT"}}'.format(updates['_updated'])
                elif updates['status'] == 'restore':
                    date_json = '{{"taken_down":""}}'.format(updates['_updated'])

                updates['dates'] = json.loads(date_json)

            elif updates['status'] == 'delete':
                app.logger.debug('Ready to hand to Celery\n{0}'.format(item))
                tasks.site_remove.delay(item)
                return
        app.logger.debug('Ready to hand to Celery\n{0}'.format(item))
        tasks.site_update.delay(item, updates, original)

def on_update_commands_callback(updates, original):
    """
    Run commands when API endpoints are called.

    :param updates:
    :param original:
    """
    item = original.copy()
    item.update(updates)
    app.logger.debug('Update command\n\nItem\n{0}\n\nUpdate\n{1}\n\nOriginal\n{2}'.format(item, updates, original))
    tasks.command_prepare.delay(item)

# Update user fields on all events. If the update is coming from Drupal, it
# will use the client_username for authentication and include the field for
# us. If someone is querying the API directly, they will user their own
# username and we need to add that.
def pre_insert(resource, items):
    username = g.get('username', None)
    if username is not None:
        for item in items:
            item['created_by'] = username
            item['modified_by'] = username

def pre_update(resource, updates, original):
    username = g.get('username', None)
    if username is not None:
        if username is not service_account_username:
            updates['modified_by'] = username

def pre_replace(resource, item, original):
    username = g.get('username', None)
    if username is not None:
        if username is not service_account_username:
            item['modified_by'] = username

"""
Setup the application and logging.
"""
# Tell Eve to use Basic Auth and where our data structure is defined.
app = Eve(auth=utilities.AtlasBasicAuth, settings="/data/code/atlas/config_data_structure.py")
# TODO: Remove debug mode.
app.debug = True

# Specific callbacks. Pattern is: `atlas.on_{Hook}_{Method}_{Resource}`
# Use pre event hooks if there is a chance you want to abort.
# Use DB hooks if you want to modify data on the way in.
app.on_pre_POST += pre_post_callback
app.on_pre_DELETE_code += pre_delete_code_callback
app.on_post_DELETE_site += post_delete_site_callback
app.on_insert_code += on_insert_code_callback
app.on_insert_sites += on_insert_sites_callback
app.on_inserted_sites += on_inserted_sites_callback
app.on_update_code += on_update_code_callback
app.on_update_sites += on_update_sites_callback
app.on_update_commands += on_update_commands_callback
app.on_delete_item_code += on_delete_item_code_callback
app.on_insert += pre_insert
app.on_update += pre_update
app.on_replace += pre_replace

@app.errorhandler(409)
def custom409(error):
    response = jsonify({'message': error.description})
    response.status_code = 409
    return response

if __name__ == '__main__':
    # Enable logging to 'atlas.log' file
    handler = logging.FileHandler('atlas.log')
    # The default log level is set to WARNING, so we have to explicitly set the
    # logging level to Debug.
    app.logger.setLevel(logging.DEBUG)
    # Append the handler to the default application logger
    app.logger.addHandler(handler)

    # This goes last.
    app.run(host='0.0.0.0', ssl_context='adhoc')
<|endoftext|>