<commit_before>import random
import string
import urllib.parse
import asyncio

import discord
import pymongo

from discord.ext import commands
from .common import Cog, WIDE_MAP

MAX_MEME_NAME = 30

RI_TABLE = {
    '0': ':zero:',
    '1': ':one:',
    '2': ':two:',
    '3': ':three:',
    '4': ':four:',
    '5': ':five:',
    '6': ':six:',
    '7': ':seven:',
    '8': ':eight:',
    '9': ':nine:',

    '.': ':record_button:',
    '!': ':exclamation:',
    '?': ':question:',
    '+': ':heavy_plus_sign:',
    '-': ':heavy_minus_sign:',
}

# implement letters
RI_STR = '🇦🇧🇨🇩🇪🇫🇬🇭🇮🇯🇰🇱🇲🇳🇴🇵🇶🇷🇸🇹🇺🇻🇼🇽🇾🇿'

RI_TABLE.update({letter:RI_STR[string.ascii_lowercase.find(letter)] for \
    letter in string.ascii_lowercase})

class Memes(Cog):
    def __init__(self, bot):
        super().__init__(bot)
        self.memes_coll = self.config.jose_db['memes']
        self.urban_cache = {}

    async def add_meme(self, name, value, author_id, uses=0, nsfw=None):
        """Add a meme to the database."""

        meme = {
            'name': name,
            'value': value,
            'uses': uses,
            'author_id': author_id,
            'nsfw': nsfw,
        }

        await self.memes_coll.insert_one(meme)

    async def get_meme(self, name):
        """Get a meme from the database."""
        return await self.memes_coll.find_one({'name': name})

    async def delete_meme(self, name):
        """Remove a meme from the database."""
        return await self.memes_coll.delete_many({'name': name})

    async def update_meme(self, new_meme):
        """Update a new meme into the database."""

        await self.memes_coll.update_one({'name': new_meme['name']}, {'$set': new_meme})

    async def search_memes(self, name):
        """Get a list of memes that have names that are close to the name given."""

        cur = self.memes_coll.find({
            'name': {
                '$regex': name,
            }
        })

        return [m['name'] for m in await cur.to_list(length=None)]

    @commands.group(aliases=['m'], invoke_without_command=True)
    async def meme(self, ctx):
        """The meme database."""
        await ctx.send("`j!help m`")

    @meme.command(name='add')
    async def add(self, ctx, name: str, *, value: str):
        """Add a meme."""
        name, value = name.strip(), value.strip()

        if len(name) > MAX_MEME_NAME:
            await ctx.send('2 long 5 me')
            return

        meme = await self.get_meme(name)
        if meme is not None:
            await ctx.send('meme already exists')
            return

        await self.add_meme(name, value, ctx.author.id, 0, ctx.channel.is_nsfw())
        await ctx.ok()

    @meme.command(name='get')
    async def get(self, ctx, *, name: str):
        """Retrieve a meme.

        Doesn't allow NSFW memes(memes created in NSFW channels) to be shown
        in non-NSFW channels
        """
        name = name.strip()

        meme = await self.get_meme(name)
        if meme is None:
            probables = await self.search_memes(name)
            if len(probables) > 0:
                await ctx.send(f'Didn\'t you mean `{",".join(probables)}`')
                return
            await ctx.send('Meme not found.')
            return

        nsfw_meme = meme.get('nsfw', False)
        if nsfw_meme and not ctx.channel.is_nsfw():
            await ctx.send('Can\'t show NSFW memes in a non-NSFW channel')
            return

        meme['uses'] += 1
        await self.update_meme(meme)
        await ctx.send(meme['value'])

    @meme.command()
    async def rm(self, ctx, name: str):
        """Remove a meme."""
        name = name.strip()

        meme = await self.get_meme(name)
        if meme is None:
            await ctx.send('Meme not found')
            return

        owner = (await self.bot.application_info()).owner
        authorized = (meme['author_id'] == ctx.author.id) or (ctx.author == owner)
        if not authorized:
            await ctx.send('Unauthorized')
            return

        await self.delete_meme(name)
        await ctx.ok()

    @meme.command()
    async def rename(self, ctx, name: str, new_name: str):
        """Rename a meme's name."""
        name, new_name = name.strip(), new_name.strip()

        meme = await self.get_meme(name)
        if meme is None:
            await ctx.send('Meme not found')
            return

        new_name_meme = await self.get_meme(new_name)
        if new_name_meme is not None:
            await ctx.send('New name already used.')
            return

        await self.delete_meme(meme['name'])
        await self.add_meme(new_name, meme['value'], meme['author_id'], meme['uses'], meme['nsfw'])
        await ctx.send(f'Renamed {meme["name"]!r} to {new_name!r}')

    @meme.command()
    async def owner(self, ctx, name: str):
        """Get a meme's owner (who created it)."""
        name = name.strip()

        meme = await self.get_meme(name)
        if meme is None:
            await ctx.send('Meme not found')
            return

        await ctx.send(f'`{name}` was made by {self.get_name(meme["author_id"])}')

    @meme.command()
    async def count(self, ctx):
        """Amount of memes in the database."""
        amnt = await self.memes_coll.count()
        await ctx.send(f'amount: {amnt}')

    @meme.command()
    async def top(self, ctx):
        """Shows the top 15 most used memes."""
        res = []
        cur = self.memes_coll.find({}).sort('uses', pymongo.DESCENDING)

        for (idx, meme) in enumerate(await cur.to_list(length=15)):
            res.append(f'[{idx}] {meme["name"]} used {meme["uses"]} times')

        _joined = "\n".join(res)
        await ctx.send(f'```\n{_joined}\n```')

    @meme.command()
    async def used(self, ctx, name: str):
        """Shows the amount of times this meme was used."""
        name = name.strip()

        meme = await self.get_meme(name)
        if meme is None:
            await ctx.send('Meme not found')
            return

        await ctx.send(f'`{meme["name"]}` => {meme["uses"]} times')

    @meme.command()
    async def see(self, ctx, owner: discord.User, page: int = 0):
        """See memes made by someone."""
        cur = self.memes_coll.find({'author_id': owner.id})

        from_owner = [m['name'] for m in await cur.to_list(length=None)]

        if len(from_owner) < 1:
            await ctx.send("No memes found.")
            return

        page_slice = from_owner[page * 50:(page + 1) * 50]

        res = len(from_owner), len(page_slice), ', '.join(page_slice)
        report = f'Showing {res[0]}[{res[1]} in page],\nMemes: {res[2]}'
        await ctx.send(report)

    @meme.command()
    async def rand(self, ctx):
        """Get a random meme."""
        total = await self.memes_coll.count()
        rand_idx = random.randint(0, total - 1)

        cur = self.memes_coll.find()
        async for meme in cur.limit(-1).skip(rand_idx):
            await ctx.send(f'{meme["name"]}: {meme["value"]}')

    @meme.command()
    async def search(self, ctx, term: str):
        """Search for memes"""
        term = term.strip()
        if len(term) < 1:
            await ctx.send('lul')
            return

        memes = await self.search_memes(term)
        if len(memes) < 1:
            await ctx.send('No memes found')
            return

        await ctx.send(f'```\n{",".join(memes)}\n```')

    @commands.command(aliases=['fw'])
    async def fullwidth(self, ctx, *, text: str):
        """Convert text to full width."""
        if len(text.strip()) <= 0:
            return

        await ctx.send(text.translate(WIDE_MAP))

    @commands.command()
    async def ri(self, ctx, *, inputstr: str):
        """Convert text to Regional Indicators."""
        inputstr = inputstr.strip().lower()
        l_inputstr = len(inputstr)
        if l_inputstr < 1 or l_inputstr > 1995:
            await ctx.send('lul')
            return

        inputstr = list(inputstr)

        for (index, char) in enumerate(inputstr):
            if char in RI_TABLE:
                inputstr[index] = f'{RI_TABLE[char]}\u200b'

        res = ''.join(inputstr)
        await ctx.send(res)

    @commands.command(hidden=True)
    async def pupper(self, ctx):
        await ctx.send('http://i.imgur.com/9Le8rW7.jpg :sob:')

    @commands.command(name='8ball')
    async def _8ball(self, ctx):
        """8ball lul"""
        answer = random.choice([
            'Yes',
            'No',
            'Maybe',
            'Potentially',
            'Answer hazy',
            'Only in your dreams',
            'Ask later',
        ])

        await ctx.send(f'**{ctx.author.name}**, :8ball: said {answer}.')

    @commands.command(hidden=True)
    async def br(self, ctx):
        """br?"""
        await ctx.send('br?')

    @commands.command()
    async def urban(self, ctx, *, term: str):
        """Search for a word in the Urban Dictionary."""

        # cache :DDDDDDDDD
        if term in self.urban_cache:
            definition = self.urban_cache[term]
            return await ctx.send(f'```\n{term!r}:\n{definition}\n```')

        await self.jcoin.pricing(ctx, self.prices['API'])

        urban_url = f'https://api.urbandictionary.com/v0/define?term={urllib.parse.quote(term)}'

        content = await self.get_json(urban_url)
        c_list = content['list']

        if len(c_list) < 1:
            raise self.SayException('No results found')

        definition = c_list[0]['definition']
        self.urban_cache[term] = definition

        await ctx.send(f'```\n{term!r}:\n{definition}\n```')

    @commands.command(hidden=True)
    async def ejaculate(self, ctx):
        """PUMPE THE MOOSCLES YIIIS"""
        await ctx.send('https://www.youtube.com/watch?v=S6UqgjaBt4w')

    @commands.command()
    @commands.is_owner()
    async def blink(self, ctx, *, text: str):
        m = await ctx.send(text)
        for i in range(10):
            if i % 2 == 0:
                await m.edit(content=f'**{text}**')
            else:
                await m.edit(content=f'{text}')
            await asyncio.sleep(2)

def setup(bot):
    bot.add_cog(Memes(bot))
<commit_msg>memes: hotpatch<commit_after>import random
import string
import urllib.parse
import asyncio

import discord
import pymongo

from discord.ext import commands
from .common import Cog, WIDE_MAP

MAX_MEME_NAME = 30

RI_TABLE = {
    '0': ':zero:',
    '1': ':one:',
    '2': ':two:',
    '3': ':three:',
    '4': ':four:',
    '5': ':five:',
    '6': ':six:',
    '7': ':seven:',
    '8': ':eight:',
    '9': ':nine:',

    '.': ':record_button:',
    '!': ':exclamation:',
    '?': ':question:',
    '+': ':heavy_plus_sign:',
    '-': ':heavy_minus_sign:',
}

# implement letters
RI_STR = '🇦🇧🇨🇩🇪🇫🇬🇭🇮🇯🇰🇱🇲🇳🇴🇵🇶🇷🇸🇹🇺🇻🇼🇽🇾🇿'

RI_TABLE.update({letter:RI_STR[string.ascii_lowercase.find(letter)] for \
    letter in string.ascii_lowercase})

class Memes(Cog):
    def __init__(self, bot):
        super().__init__(bot)
        self.memes_coll = self.config.jose_db['memes']
        self.urban_cache = {}

    async def add_meme(self, name, value, author_id, uses=0, nsfw=None):
        """Add a meme to the database."""

        meme = {
            'name': name,
            'value': value,
            'uses': uses,
            'author_id': author_id,
            'nsfw': nsfw,
        }

        await self.memes_coll.insert_one(meme)

    async def get_meme(self, name):
        """Get a meme from the database."""
        return await self.memes_coll.find_one({'name': name})

    async def delete_meme(self, name):
        """Remove a meme from the database."""
        return await self.memes_coll.delete_many({'name': name})

    async def update_meme(self, new_meme):
        """Update a new meme into the database."""

        await self.memes_coll.update_one({'name': new_meme['name']}, {'$set': new_meme})

    async def search_memes(self, name):
        """Get a list of memes that have names that are close to the name given."""

        cur = self.memes_coll.find({
            'name': {
                '$regex': name,
            }
        })

        return [m['name'] for m in await cur.to_list(length=None)]

    @commands.group(aliases=['m'], invoke_without_command=True)
    async def meme(self, ctx):
        """The meme database."""
        await ctx.send("`j!help m`")

    @meme.command(name='add')
    async def add(self, ctx, name: str, *, value: str):
        """Add a meme."""
        name, value = name.strip(), value.strip()

        if len(name) > MAX_MEME_NAME:
            await ctx.send('2 long 5 me')
            return

        meme = await self.get_meme(name)
        if meme is not None:
            await ctx.send('meme already exists')
            return

        await self.add_meme(name, value, ctx.author.id, 0, ctx.channel.is_nsfw())
        await ctx.ok()

    @meme.command(name='get')
    async def get(self, ctx, *, name: str):
        """Retrieve a meme.

        Doesn't allow NSFW memes(memes created in NSFW channels) to be shown
        in non-NSFW channels
        """
        name = name.strip()

        meme = await self.get_meme(name)
        if meme is None:
            probables = await self.search_memes(name)
            if len(probables) > 0:
                await ctx.send(f'Didn\'t you mean `{",".join(probables)}`')
                return
            await ctx.send('Meme not found.')
            return

        nsfw_meme = meme.get('nsfw', False)
        if nsfw_meme and not ctx.channel.is_nsfw():
            await ctx.send('Can\'t show NSFW memes in a non-NSFW channel')
            return

        meme['uses'] += 1
        await self.update_meme(meme)
        await ctx.send(meme['value'])

    @meme.command()
    async def rm(self, ctx, name: str):
        """Remove a meme."""
        name = name.strip()

        meme = await self.get_meme(name)
        if meme is None:
            await ctx.send('Meme not found')
            return

        owner = (await self.bot.application_info()).owner
        authorized = (meme.get('author_id') == ctx.author.id) or (ctx.author == owner)
        if not authorized:
            await ctx.send('Unauthorized')
            return

        await self.delete_meme(name)
        await ctx.ok()

    @meme.command()
    async def rename(self, ctx, name: str, new_name: str):
        """Rename a meme's name."""
        name, new_name = name.strip(), new_name.strip()

        meme = await self.get_meme(name)
        if meme is None:
            await ctx.send('Meme not found')
            return

        new_name_meme = await self.get_meme(new_name)
        if new_name_meme is not None:
            await ctx.send('New name already used.')
            return

        await self.delete_meme(meme['name'])
        await self.add_meme(new_name, meme['value'], meme.get('author_id'), meme['uses'], meme['nsfw'])
        await ctx.send(f'Renamed {meme["name"]!r} to {new_name!r}')

    @meme.command()
    async def owner(self, ctx, name: str):
        """Get a meme's owner (who created it)."""
        name = name.strip()

        meme = await self.get_meme(name)
        if meme is None:
            await ctx.send('Meme not found')
            return

        await ctx.send(f'`{name}` was made by {self.get_name(meme.get("author_id"))}')

    @meme.command()
    async def count(self, ctx):
        """Amount of memes in the database."""
        amnt = await self.memes_coll.count()
        await ctx.send(f'amount: {amnt}')

    @meme.command()
    async def top(self, ctx):
        """Shows the top 15 most used memes."""
        res = []
        cur = self.memes_coll.find({}).sort('uses', pymongo.DESCENDING)

        for (idx, meme) in enumerate(await cur.to_list(length=15)):
            res.append(f'[{idx}] {meme["name"]} used {meme["uses"]} times')

        _joined = "\n".join(res)
        await ctx.send(f'```\n{_joined}\n```')

    @meme.command()
    async def used(self, ctx, name: str):
        """Shows the amount of times this meme was used."""
        name = name.strip()

        meme = await self.get_meme(name)
        if meme is None:
            await ctx.send('Meme not found')
            return

        await ctx.send(f'`{meme["name"]}` => {meme["uses"]} times')

    @meme.command()
    async def see(self, ctx, owner: discord.User, page: int = 0):
        """See memes made by someone."""
        cur = self.memes_coll.find({'author_id': owner.id})

        from_owner = [m['name'] for m in await cur.to_list(length=None)]

        if len(from_owner) < 1:
            await ctx.send("No memes found.")
            return

        page_slice = from_owner[page * 50:(page + 1) * 50]

        res = len(from_owner), len(page_slice), ', '.join(page_slice)
        report = f'Showing {res[0]}[{res[1]} in page],\nMemes: {res[2]}'
        await ctx.send(report)

    @meme.command()
    async def rand(self, ctx):
        """Get a random meme."""
        total = await self.memes_coll.count()
        rand_idx = random.randint(0, total - 1)

        cur = self.memes_coll.find()
        async for meme in cur.limit(-1).skip(rand_idx):
            await ctx.send(f'{meme["name"]}: {meme["value"]}')

    @meme.command()
    async def search(self, ctx, term: str):
        """Search for memes"""
        term = term.strip()
        if len(term) < 1:
            await ctx.send('lul')
            return

        memes = await self.search_memes(term)
        if len(memes) < 1:
            await ctx.send('No memes found')
            return

        await ctx.send(f'```\n{",".join(memes)}\n```')

    @commands.command(aliases=['fw'])
    async def fullwidth(self, ctx, *, text: str):
        """Convert text to full width."""
        if len(text.strip()) <= 0:
            return

        await ctx.send(text.translate(WIDE_MAP))

    @commands.command()
    async def ri(self, ctx, *, inputstr: str):
        """Convert text to Regional Indicators."""
        inputstr = inputstr.strip().lower()
        l_inputstr = len(inputstr)
        if l_inputstr < 1 or l_inputstr > 1995:
            await ctx.send('lul')
            return

        inputstr = list(inputstr)

        for (index, char) in enumerate(inputstr):
            if char in RI_TABLE:
                inputstr[index] = f'{RI_TABLE[char]}\u200b'

        res = ''.join(inputstr)
        await ctx.send(res)

    @commands.command(hidden=True)
    async def pupper(self, ctx):
        await ctx.send('http://i.imgur.com/9Le8rW7.jpg :sob:')

    @commands.command(name='8ball')
    async def _8ball(self, ctx):
        """8ball lul"""
        answer = random.choice([
            'Yes',
            'No',
            'Maybe',
            'Potentially',
            'Answer hazy',
            'Only in your dreams',
            'Ask later',
        ])

        await ctx.send(f'**{ctx.author.name}**, :8ball: said {answer}.')

    @commands.command(hidden=True)
    async def br(self, ctx):
        """br?"""
        await ctx.send('br?')

    @commands.command()
    async def urban(self, ctx, *, term: str):
        """Search for a word in the Urban Dictionary."""

        # cache :DDDDDDDDD
        if term in self.urban_cache:
            definition = self.urban_cache[term]
            return await ctx.send(f'```\n{term!r}:\n{definition}\n```')

        await self.jcoin.pricing(ctx, self.prices['API'])

        urban_url = f'https://api.urbandictionary.com/v0/define?term={urllib.parse.quote(term)}'

        content = await self.get_json(urban_url)
        c_list = content['list']

        if len(c_list) < 1:
            raise self.SayException('No results found')

        definition = c_list[0]['definition']
        self.urban_cache[term] = definition

        await ctx.send(f'```\n{term!r}:\n{definition}\n```')

    @commands.command(hidden=True)
    async def ejaculate(self, ctx):
        """PUMPE THE MOOSCLES YIIIS"""
        await ctx.send('https://www.youtube.com/watch?v=S6UqgjaBt4w')

    @commands.command()
    @commands.is_owner()
    async def blink(self, ctx, *, text: str):
        m = await ctx.send(text)
        for i in range(10):
            if i % 2 == 0:
                await m.edit(content=f'**{text}**')
            else:
                await m.edit(content=f'{text}')
            await asyncio.sleep(2)

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


<commit_before># Copyright 2008 the Melange authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Release tag string for display in templates (and possibly other uses).

Steps (currently done by hand, but too be scripted in the future) to make
a release:

1) set RELEASE_TAG in this file to a "release candidate" release string that
   ends in "rc"

2) commit this file *by itself* in its own commit

3) use 'svn cp' to create a release branch in /branches/ with exactly the
   same name as the contents of the RELEASE_TAG string

4) set RELEASE_TAG back to None in /trunk/

To finalize a release candidate in a release branch for a push to the live
web site:

1) in the release branch, change RELEASE_TAG to remove the trailing "rc"

2) commit this file in the release branch *by itself* in its own commit

3) use 'svn cp' to create a tag in /tags/ with exactly the same name as the
   contents of the RELEASE_TAG string

4) put the release branch in a state where it is ready for additional patches
   after the tag by setting the end of the RELEASE_TAG string to "p0"

To re-release a previously-tagged release branch after a patch for a push to
the live web site:

1) increment the "patch suffix" of the RELEASE_TAG string to the next integer
   (for example, "p0" becomes "p1", so the first tagged patch release will
   always be "p1", not "p0", which is just a placeholder)

2) (same as #2 for a release candidate)

3) (same as #3 for a release candidate)

4) (there is no step 4)
"""

__authors__ = [
  '"Todd Larsen" <tlarsen@google.com>',
  ]

RELEASE_TAG = None

<commit_msg>Set RELEASE_TAG to 0.1a20081206rc for release candidate 06/12/2008.<commit_after># Copyright 2008 the Melange authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Release tag string for display in templates (and possibly other uses).

Steps (currently done by hand, but too be scripted in the future) to make
a release:

1) set RELEASE_TAG in this file to a "release candidate" release string that
   ends in "rc"

2) commit this file *by itself* in its own commit

3) use 'svn cp' to create a release branch in /branches/ with exactly the
   same name as the contents of the RELEASE_TAG string

4) set RELEASE_TAG back to None in /trunk/

To finalize a release candidate in a release branch for a push to the live
web site:

1) in the release branch, change RELEASE_TAG to remove the trailing "rc"

2) commit this file in the release branch *by itself* in its own commit

3) use 'svn cp' to create a tag in /tags/ with exactly the same name as the
   contents of the RELEASE_TAG string

4) put the release branch in a state where it is ready for additional patches
   after the tag by setting the end of the RELEASE_TAG string to "p0"

To re-release a previously-tagged release branch after a patch for a push to
the live web site:

1) increment the "patch suffix" of the RELEASE_TAG string to the next integer
   (for example, "p0" becomes "p1", so the first tagged patch release will
   always be "p1", not "p0", which is just a placeholder)

2) (same as #2 for a release candidate)

3) (same as #3 for a release candidate)

4) (there is no step 4)
"""

__authors__ = [
  '"Todd Larsen" <tlarsen@google.com>',
  ]

RELEASE_TAG = '0.1a20081206rc'

<|endoftext|>


<commit_before>import re
from .taf import TAF

class DecodeError(Exception):
    def __init__(self, msg):
        self.strerror = msg

class Decoder(object):
    def __init__(self, taf):
        if isinstance(taf, TAF):
            self._taf = taf
        else:
            raise DecodeError("Argument is not a TAF parser object")

    def decode_taf(self):
        form = self._taf.get_header()["form"]
        result = ""

        result += self._decode_header(self._taf.get_header()) + "\n"

        for group in self._taf.get_groups():
            # TAF specific stuff
            if form == "taf":
                if group["header"]:
                    result += self._decode_group_header(group["header"]) + "\n"

            # METAR specific stuff
            if form == "metar":
                if group["temperature"]:
                    result += "    Temperature: %s\n" % self._decode_temperature(group["temperature"])

                if group["pressure"]:
                    result += "    Pressure: %s\n" % self._decode_pressure(group["pressure"])

            # Both TAF and METAR
            if group["wind"]:
                result += "    Wind: %s \n" % self._decode_wind(group["wind"])

            if group["visibility"]:
                result += "    Visibility: %s \n" % self._decode_visibility(group["visibility"])

            if group["clouds"]:
                result += "    Sky conditions: %s \n" % self._decode_clouds(group["clouds"])

            if group["weather"]:
                result += "    Weather: %s \n" % self._decode_weather(group["weather"])

            if group["windshear"]:
                result += "    Windshear: %s\n" % self._decode_windshear(group["windshear"])

            result += " \n"

        if self._taf.get_maintenance():
            result += self._decode_maintenance(self._taf.get_maintenance())

        return(result)

    def _decode_header(self, header):
        result = ""

        # Ensure it's side effect free
        _header = header

        if _header["form"] == 'taf':
            # Decode TAF header
            # Type
            if _header["type"] == "AMD":
                result += "TAF amended for "
            elif _header["type"] == "COR":
                result += "TAF corrected for "
            elif _header["type"] == "RTD":
                result += "TAF delayed for "
            elif _header["type"] == "AMD COR":
                result += "TAF amended and corrected for "
            elif _header["type"] == "COR AMD":
                result += "TAF corrected and amended for "
            else:
                result += "TAF for "

            # Add ordinal suffix
            _header["origin_date"] = _header["origin_date"] + self._get_ordinal_suffix(_header["origin_date"])
            _header["valid_from_date"] = _header["valid_from_date"] + self._get_ordinal_suffix(_header["valid_from_date"])
            _header["valid_till_date" ] = _header["valid_till_date"] + self._get_ordinal_suffix(_header["valid_till_date"])

            result += ("%(icao_code)s issued %(origin_hours)s:%(origin_minutes)s UTC on the %(origin_date)s, "
                       "valid from %(valid_from_hours)s:00 UTC on the %(valid_from_date)s to %(valid_till_hours)s:00 UTC on the %(valid_till_date)s")
        else:
            # Decode METAR header
            # Type
            if _header["type"] == "COR":
                result += "METAR corrected for "
            else:
                result += "METAR for "

            _header["origin_date"] = _header["origin_date"] + self._get_ordinal_suffix(_header["origin_date"])

            result += ("%(icao_code)s issued %(origin_hours)s:%(origin_minutes)s UTC on the %(origin_date)s")

        result = result % _header

        return(result)

    def _decode_group_header(self, header):
        result = ""
        _header = header

        from_str = "From %(from_hours)s:%(from_minutes)s on the %(from_date)s: "
        prob_str = "Probability %(probability)s%% of the following between %(from_hours)s:00 on the %(from_date)s and %(till_hours)s:00 on the %(till_date)s: "
        tempo_str = "Temporarily between %(from_hours)s:00 on the %(from_date)s and %(till_hours)s:00 on the %(till_date)s: "
        prob_tempo_str = "Probability %(probability)s%% of the following temporarily between %(from_hours)s:00 on the %(from_date)s and %(till_hours)s:00 on the %(till_date)s: "
        becmg_str = "Gradual change to the following between %(from_hours)s:00 on the %(from_date)s and %(till_hours)s:00 on the %(till_date)s: "

        if "type" in _header:
            # Add ordinal suffix
            if "from_date" in _header:
                from_suffix = self._get_ordinal_suffix(_header["from_date"])
                _header["from_date"] = _header["from_date"] + from_suffix
            if "till_date" in _header:
                till_suffix = self._get_ordinal_suffix(_header["till_date"])
                _header["till_date"] = _header["till_date"] + till_suffix

            if _header["type"] == "FM":
                result += from_str % { "from_date":    _header["from_date"],
                                       "from_hours":   _header["from_hours"],
                                       "from_minutes": _header["from_minutes"] }
            elif _header["type"] == "PROB%s" % (_header["probability"]):
                result += prob_str % { "probability": _header["probability"],
                                       "from_date":   _header["from_date"],
                                       "from_hours":  _header["from_hours"],
                                       "till_date":   _header["till_date"],
                                       "till_hours":  _header["till_hours"] }
            elif "PROB" in _header["type"] and "TEMPO" in _header["type"]:
                result += prob_tempo_str % { "probability": _header["probability"],
                                           "from_date":   _header["from_date"],
                                           "from_hours":  _header["from_hours"],
                                           "till_date":   _header["till_date"],
                                           "till_hours":  _header["till_hours"] }
            elif _header["type"] == "TEMPO":
                result += tempo_str % { "from_date":  _header["from_date"],
                                        "from_hours": _header["from_hours"],
                                        "till_date":  _header["till_date"],
                                        "till_hours": _header["till_hours"] }
            elif _header["type"] == "BECMG":
                result += becmg_str % { "from_date":  _header["from_date"],
                                        "from_hours": _header["from_hours"],
                                        "till_date":  _header["till_date"],
                                        "till_hours": _header["till_hours"] }

        return(result)

    def _decode_wind(self, wind):
        unit = ""
        result = ""

        if wind["direction"] == "000":
            return("calm")
        elif wind["direction"] == "VRB":
            result += "variable"
        else:
            result += "from %s degrees" % wind["direction"]

        if wind["unit"] == "KT":
            unit = "knots"
        elif wind["unit"] == "MPS":
            unit = "meters per second"
        else:
            # Unlikely, but who knows
            unit = "(unknown unit)"

        result += " at %s %s" % (wind["speed"], unit)

        if wind["gust"]:
            result += " gusting to %s %s" % (wind["gust"], unit)

        return(result)

    def _decode_visibility(self, visibility):
        result = ""

        if "more" in visibility:
            if visibility["more"]:
                result += "more than "

        result += visibility["range"]

        if visibility["unit"] == "SM":
            result += " statute miles"
        elif visibility["unit"] == "M":
            result += " meters"

        return(result)

    def _decode_clouds(self, clouds):
        result = ""
        i_result = ""
        list = []

        for layer in clouds:
            if layer["layer"] == "SKC" or layer["layer"] == "CLR":
                return "sky clear"

            if layer["layer"] == "NSC":
                return "no significant cloud"

            if layer["layer"] == "CAVOK":
                return "ceiling and visibility are OK"

            if layer["layer"] == "CAVU":
                return "ceiling and visibility unrestricted"

            if layer["layer"] == "VV///":
                return "Sky Obscured"

            if layer["layer"] == "SCT":
                layer_type = "scattered"
            elif layer["layer"] == "BKN":
                layer_type = "broken"
            elif layer["layer"] == "FEW":
                layer_type = "few"
            elif layer["layer"] == "OVC":
                layer_type = "overcast"

            if layer["type"] == "CB":
                type = "cumulonimbus"
            elif layer["type"] == "CU":
                type = "cumulus"
            elif layer["type"] == "TCU":
                type = "towering cumulus"
            elif layer["type"] == "CI":
                type = "cirrus"
            else:
                type = ""

            result = "%s %s clouds at %d feet" % (layer_type, type, int(layer["ceiling"])*100)

            # Remove extra whitespace, if any
            result = re.sub(r'\s+', ' ', result)

            list.append(result)

            layer = ""
            type = ""
            result = ""

        result = ", ".join(list)
        return(result)

    def _decode_weather(self, weather):
        # Dicts for translating the abbreviations
        dict_intensities = {
            "-" : "light",
            "+" : "heavy",
            "VC" : "in the vicinity",
            "RE" : "recent"
        }

        dict_modifiers = {
            "MI" : "shallow",
            "BC" : "patchy",
            "DR" : "low drifting",
            "BL" : "blowing",
            "SH" : "showers",
            "TS" : "thunderstorms",
            "FZ" : "freezing",
            "PR" : "partial"
        }

        dict_phenomenons = {
            "DZ" : "drizzle",
            "RA" : "rain",
            "SN" : "snow",
            "SG" : "snow grains",
            "IC" : "ice",
            "PL" : "ice pellets",
            "GR" : "hail",
            "GS" : "small snow/hail pellets",
            "UP" : "unknown precipitation",
            "BR" : "mist",
            "FG" : "fog",
            "FU" : "smoke",
            "DU" : "dust",
            "SA" : "sand",
            "HZ" : "haze",
            "PY" : "spray",
            "VA" : "volcanic ash",
            "PO" : "dust/sand whirl",
            "SQ" : "squall",
            "FC" : "funnel cloud",
            "SS" : "sand storm",
            "DS" : "dust storm",
        }

        weather_txt_blocks = []

        # Check for special cases first. If a certain combination is found
        # then skip parsing the whole weather string and return a defined string
        # immediately
        for group in weather:
            # +FC = Tornado or Waterspout
            if "+" in group["intensity"] and "FC" in group["phenomenon"]:
                weather_txt_blocks.append("tornado or waterspout")
                continue

            # Sort the elements of the weather string, if no special combi-
            # nation is found.
            intensities_pre = []
            intensities_post = []
            if "RE" in group["intensity"]:
                intensities_pre.append("RE")
                group["intensity"].remove("RE")
            for intensity in group["intensity"]:
                if intensity != "VC":
                    intensities_pre.append(intensity)
                else:
                    intensities_post.append(intensity)

            modifiers_pre = []
            modifiers_post = []
            for modifier in group["modifier"]:
                if modifier != "TS" and modifier != "SH":
                    modifiers_pre.append(modifier)
                else:
                    modifiers_post.append(modifier)

            phenomenons_pre = []
            phenomenons_post = []
            for phenomenon in group["phenomenon"]:
                if phenomenon != "UP":
                    phenomenons_pre.append(phenomenon)
                else:
                    phenomenons_post.append(phenomenon)

            # Build the human readable text from the single weather string
            # and append it to a list containing all the interpreted text
            # blocks from a TAF group
            weather_txt = ""
            for intensity in intensities_pre:
                weather_txt += dict_intensities[intensity] + " "

            for modifier in modifiers_pre:
                weather_txt += dict_modifiers[modifier] + " "

            phenomenons = phenomenons_pre + phenomenons_post
            cnt = len(phenomenons)
            for phenomenon in phenomenons:
                weather_txt += dict_phenomenons[phenomenon]
                if cnt > 2:
                    weather_txt += ", "
                if cnt == 2:
                    weather_txt += " and "
                cnt = cnt-1
            weather_txt += " "

            for modifier in modifiers_post:
                weather_txt += dict_modifiers[modifier] + " "

            for intensity in intensities_post:
                weather_txt += dict_intensities[intensity] + " "

            weather_txt_blocks.append(weather_txt.strip())

        # Put all the human readable stuff together and return the final
        # output as a string.
        weather_txt_full = ""
        for block in weather_txt_blocks[:-1]:
            weather_txt_full += block + " / "
        weather_txt_full += weather_txt_blocks[-1]

        return(weather_txt_full)

    def _decode_temperature(self, temperature, unit='C'):
        if temperature["air_prefix"] == 'M':
            air_c = int(temperature["air"])*-1
        else:
            air_c = int(temperature["air"])

        if temperature["dewpoint_prefix"] == 'M':
            dew_c = int(temperature["dewpoint"])*-1
        else:
            dew_c = int(temperature["dewpoint"])

        if unit == 'C':
            air_txt = air_c
            dew_txt = dew_c

        if unit == 'F':
            air_f = int(round(air_c*1.8+32))
            dew_f = int(round(dew_c*1.8+32))
            air_txt = air_f
            dew_txt = dew_f

        result = "air at %s%s, dewpoint at %s%s" % (air_txt, unit, dew_txt, unit)
        return(result)

    def _decode_pressure(self, pressure):
        result = "%s hPa" % (pressure["athm_pressure"])
        return(result)

    def _decode_windshear(self, windshear):
        result = "at %s, wind %s at %s %s" % ((int(windshear["altitude"])*100), windshear["direction"], windshear["speed"], windshear["unit"])
        return(result)

    def _decode_maintenance(self, maintenance):
        if maintenance:
            return "Station is under maintenance check\n"

    def _get_ordinal_suffix(self, date):
        _date = str(date)

        suffix = ""

        if re.match(".*(1[12]|[04-9])$", _date):
            suffix = "th"
        elif re.match(".*1$", _date):
            suffix = "st"
        elif re.match(".*2$", _date):
            suffix = "nd"
        elif re.match(".*3$", _date):
            suffix = "rd"

        return(suffix)
<commit_msg>Update tafdecoder.py<commit_after>import re
from .taf import TAF

class DecodeError(Exception):
    def __init__(self, msg):
        self.strerror = msg

class Decoder(object):
    def __init__(self, taf):
        if isinstance(taf, TAF):
            self._taf = taf
        else:
            raise DecodeError("Argument is not a TAF parser object")

    def decode_taf(self):
        form = self._taf.get_header()["form"]
        result = ""

        result += self._decode_header(self._taf.get_header()) + "\n"

        for group in self._taf.get_groups():
            # TAF specific stuff
            if form == "taf":
                if group["header"]:
                    result += self._decode_group_header(group["header"]) + "\n"

            # METAR specific stuff
            if form == "metar":
                if group["temperature"]:
                    result += "    Temperature: %s\n" % self._decode_temperature(group["temperature"])

                if group["pressure"]:
                    result += "    Pressure: %s\n" % self._decode_pressure(group["pressure"])

            # Both TAF and METAR
            if group["wind"]:
                result += "    Wind: %s \n" % self._decode_wind(group["wind"])

            if group["visibility"]:
                result += "    Visibility: %s \n" % self._decode_visibility(group["visibility"])

            if group["clouds"]:
                result += "    Sky conditions: %s \n" % self._decode_clouds(group["clouds"])

            if group["weather"]:
                result += "    Weather: %s \n" % self._decode_weather(group["weather"])

            if group["windshear"]:
                result += "    Windshear: %s\n" % self._decode_windshear(group["windshear"])

            result += " \n"

        if self._taf.get_maintenance():
            result += self._decode_maintenance(self._taf.get_maintenance())

        return(result)

    def _decode_header(self, header):
        result = ""

        # Ensure it's side effect free
        _header = header

        if _header["form"] == 'taf':
            # Decode TAF header
            # Type
            if _header["type"] == "AMD":
                result += "TAF amended for "
            elif _header["type"] == "COR":
                result += "TAF corrected for "
            elif _header["type"] == "RTD":
                result += "TAF delayed for "
            elif _header["type"] == "AMD COR":
                result += "TAF amended and corrected for "
            elif _header["type"] == "COR AMD":
                result += "TAF corrected and amended for "
            else:
                result += "TAF for "

            # Add ordinal suffix
            _header["origin_date"] = _header["origin_date"] + self._get_ordinal_suffix(_header["origin_date"])
            _header["valid_from_date"] = _header["valid_from_date"] + self._get_ordinal_suffix(_header["valid_from_date"])
            _header["valid_till_date" ] = _header["valid_till_date"] + self._get_ordinal_suffix(_header["valid_till_date"])

            result += ("%(icao_code)s issued %(origin_hours)s:%(origin_minutes)s UTC on the %(origin_date)s, "
                       "valid from %(valid_from_hours)s:00 UTC on the %(valid_from_date)s to %(valid_till_hours)s:00 UTC on the %(valid_till_date)s")
        else:
            # Decode METAR header
            # Type
            if _header["type"] == "COR":
                result += "METAR corrected for "
            else:
                result += "METAR for "

            _header["origin_date"] = _header["origin_date"] + self._get_ordinal_suffix(_header["origin_date"])

            result += ("%(icao_code)s issued %(origin_hours)s:%(origin_minutes)s UTC on the %(origin_date)s")

        result = result % _header

        return(result)

    def _decode_group_header(self, header):
        result = ""
        _header = header

        from_str = "From %(from_hours)s:%(from_minutes)s on the %(from_date)s: "
        prob_str = "Probability %(probability)s%% of the following between %(from_hours)s:00 on the %(from_date)s and %(till_hours)s:00 on the %(till_date)s: "
        tempo_str = "Temporarily between %(from_hours)s:00 on the %(from_date)s and %(till_hours)s:00 on the %(till_date)s: "
        prob_tempo_str = "Probability %(probability)s%% of the following temporarily between %(from_hours)s:00 on the %(from_date)s and %(till_hours)s:00 on the %(till_date)s: "
        becmg_str = "Gradual change to the following between %(from_hours)s:00 on the %(from_date)s and %(till_hours)s:00 on the %(till_date)s: "

        if "type" in _header:
            # Add ordinal suffix
            if "from_date" in _header:
                from_suffix = self._get_ordinal_suffix(_header["from_date"])
                _header["from_date"] = _header["from_date"] + from_suffix
            if "till_date" in _header:
                till_suffix = self._get_ordinal_suffix(_header["till_date"])
                _header["till_date"] = _header["till_date"] + till_suffix

            if _header["type"] == "FM":
                result += from_str % { "from_date":    _header["from_date"],
                                       "from_hours":   _header["from_hours"],
                                       "from_minutes": _header["from_minutes"] }
            elif _header["type"] == "PROB%s" % (_header["probability"]):
                result += prob_str % { "probability": _header["probability"],
                                       "from_date":   _header["from_date"],
                                       "from_hours":  _header["from_hours"],
                                       "till_date":   _header["till_date"],
                                       "till_hours":  _header["till_hours"] }
            elif "PROB" in _header["type"] and "TEMPO" in _header["type"]:
                result += prob_tempo_str % { "probability": _header["probability"],
                                           "from_date":   _header["from_date"],
                                           "from_hours":  _header["from_hours"],
                                           "till_date":   _header["till_date"],
                                           "till_hours":  _header["till_hours"] }
            elif _header["type"] == "TEMPO":
                result += tempo_str % { "from_date":  _header["from_date"],
                                        "from_hours": _header["from_hours"],
                                        "till_date":  _header["till_date"],
                                        "till_hours": _header["till_hours"] }
            elif _header["type"] == "BECMG":
                result += becmg_str % { "from_date":  _header["from_date"],
                                        "from_hours": _header["from_hours"],
                                        "till_date":  _header["till_date"],
                                        "till_hours": _header["till_hours"] }

        return(result)

    def _decode_wind(self, wind):
        unit = ""
        result = ""

        if wind["direction"] == "000":
            return("calm")
        elif wind["direction"] == "VRB":
            result += "variable"
        else:
            result += "from %s degrees" % wind["direction"]

        if wind["unit"] == "KT":
            unit = "knots"
        elif wind["unit"] == "MPS":
            unit = "meters per second"
        else:
            # Unlikely, but who knows
            unit = "(unknown unit)"

        result += " at %s %s" % (wind["speed"], unit)

        if wind["gust"]:
            result += " gusting to %s %s" % (wind["gust"], unit)

        return(result)

    def _decode_visibility(self, visibility):
        result = ""

        if "more" in visibility:
            if visibility["more"]:
                result += "more than "

        result += visibility["range"]

        if visibility["unit"] == "SM":
            result += " statute miles"
        elif visibility["unit"] == "M":
            result += " meters"

        return(result)

    def _decode_clouds(self, clouds):
        result = ""
        i_result = ""
        list = []

        for layer in clouds:
            if layer["layer"] == "SKC" or layer["layer"] == "CLR":
                return "sky clear"

            if layer["layer"] == "NSC":
                return "no significant cloud"

            if layer["layer"] == "CAVOK":
                return "ceiling and visibility are OK"

            if layer["layer"] == "CAVU":
                return "ceiling and visibility unrestricted"

            if layer["layer"] == "VV///":
                return "Sky Obscured"

            if layer["layer"] == "SCT":
                layer_type = "scattered"
            elif layer["layer"] == "BKN":
                layer_type = "broken"
            elif layer["layer"] == "FEW":
                layer_type = "few"
            elif layer["layer"] == "OVC":
                layer_type = "overcast"

            if layer["type"] == "CB":
                type = "cumulonimbus"
            elif layer["type"] == "CU":
                type = "cumulus"
            elif layer["type"] == "TCU":
                type = "towering cumulus"
            elif layer["type"] == "CI":
                type = "cirrus"
            else:
                type = ""

            result = "%s %s clouds at %d feet" % (layer_type, type, int(layer["ceiling"])*100)

            # Remove extra whitespace, if any
            result = re.sub(r'\s+', ' ', result)

            list.append(result)

            layer = ""
            type = ""
            result = ""

        result = ", ".join(list)
        return(result)

    def _decode_weather(self, weather):
        # Dicts for translating the abbreviations
        dict_intensities = {
            "-" : "light",
            "+" : "heavy",
            "VC" : "in the vicinity",
            "RE" : "recent"
        }

        dict_modifiers = {
            "MI" : "shallow",
            "BC" : "patchy",
            "DR" : "low drifting",
            "BL" : "blowing",
            "SH" : "showers",
            "TS" : "thunderstorms",
            "FZ" : "freezing",
            "PR" : "partial"
        }

        dict_phenomenons = {
            "DZ" : "drizzle",
            "RA" : "rain",
            "SN" : "snow",
            "SG" : "snow grains",
            "IC" : "ice",
            "PL" : "ice pellets",
            "GR" : "hail",
            "GS" : "small snow/hail pellets",
            "UP" : "unknown precipitation",
            "BR" : "mist",
            "FG" : "fog",
            "FU" : "smoke",
            "DU" : "dust",
            "SA" : "sand",
            "HZ" : "haze",
            "PY" : "spray",
            "VA" : "volcanic ash",
            "PO" : "dust/sand whirl",
            "SQ" : "squall",
            "FC" : "funnel cloud",
            "SS" : "sand storm",
            "DS" : "dust storm",
        }

        weather_txt_blocks = []

        # Check for special cases first. If a certain combination is found
        # then skip parsing the whole weather string and return a defined string
        # immediately
        for group in weather:
            # +FC = Tornado or Waterspout
            if "+" in group["intensity"] and "FC" in group["phenomenon"]:
                weather_txt_blocks.append("tornado or waterspout")
                continue

            # Sort the elements of the weather string, if no special combi-
            # nation is found.
            intensities_pre = []
            intensities_post = []
            if "RE" in group["intensity"]:
                intensities_pre.append("RE")
                group["intensity"].remove("RE")
            for intensity in group["intensity"]:
                if intensity != "VC":
                    intensities_pre.append(intensity)
                else:
                    intensities_post.append(intensity)

            modifiers_pre = []
            modifiers_post = []
            for modifier in group["modifier"]:
                if modifier != "TS" and modifier != "SH":
                    modifiers_pre.append(modifier)
                else:
                    modifiers_post.append(modifier)

            phenomenons_pre = []
            phenomenons_post = []
            for phenomenon in group["phenomenon"]:
                if phenomenon != "UP":
                    phenomenons_pre.append(phenomenon)
                else:
                    phenomenons_post.append(phenomenon)

            # Build the human readable text from the single weather string
            # and append it to a list containing all the interpreted text
            # blocks from a TAF group
            weather_txt = ""
            for intensity in intensities_pre:
                weather_txt += dict_intensities[intensity] + " "

            for modifier in modifiers_pre:
                weather_txt += dict_modifiers[modifier] + " "

            phenomenons = phenomenons_pre + phenomenons_post
            cnt = len(phenomenons)
            for phenomenon in phenomenons:
                weather_txt += dict_phenomenons[phenomenon]
                if cnt > 2:
                    weather_txt += ", "
                if cnt == 2:
                    weather_txt += " and "
                cnt = cnt-1
            weather_txt += " "

            for modifier in modifiers_post:
                weather_txt += dict_modifiers[modifier] + " "

            for intensity in intensities_post:
                weather_txt += dict_intensities[intensity] + " "

            weather_txt_blocks.append(weather_txt.strip())

        # Put all the human readable stuff together and return the final
        # output as a string.
        weather_txt_full = ""
        for block in weather_txt_blocks[:-1]:
            weather_txt_full += block + " / "
        weather_txt_full += weather_txt_blocks[-1]

        return(weather_txt_full)

    def _decode_temperature(self, temperature, unit='C'):
        if temperature["air_prefix"] == 'M':
            air_c = int(temperature["air"])*-1
        else:
            air_c = int(temperature["air"])

        if temperature["dewpoint_prefix"] == 'M':
            dew_c = int(temperature["dewpoint"])*-1
        else:
            dew_c = int(temperature["dewpoint"])

        if unit == 'C':
            air_txt = air_c
            dew_txt = dew_c

        if unit == 'F':
            air_f = int(round(air_c*1.8+32))
            dew_f = int(round(dew_c*1.8+32))
            air_txt = air_f
            dew_txt = dew_f

        result = "air at %s°%s, dewpoint at %s%s" % (air_txt, unit, dew_txt, unit)
        return(result)

    def _decode_pressure(self, pressure):
        result = "%s hPa" % (pressure["athm_pressure"])
        return(result)

    def _decode_windshear(self, windshear):
        result = "at %s, wind %s at %s %s" % ((int(windshear["altitude"])*100), windshear["direction"], windshear["speed"], windshear["unit"])
        return(result)

    def _decode_maintenance(self, maintenance):
        if maintenance:
            return "Station is under maintenance check\n"

    def _get_ordinal_suffix(self, date):
        _date = str(date)

        suffix = ""

        if re.match(".*(1[12]|[04-9])$", _date):
            suffix = "th"
        elif re.match(".*1$", _date):
            suffix = "st"
        elif re.match(".*2$", _date):
            suffix = "nd"
        elif re.match(".*3$", _date):
            suffix = "rd"

        return(suffix)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Copyright (c) 2011 Timo Schmiade
# Copyright (c) 2012 Phil Jackson
# Copyright (c) 2013 Tao Sauvage
# Copyright (c) 2014 Sean Vig
# Copyright (c) 2014 Tycho Andersen
# Copyright (c) 2016 Christoph Lassner
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import mailbox
import os.path
from typing import Dict

from libqtile.widget import base

class Maildir(base.ThreadPoolText):
    """A simple widget showing the number of new mails in maildir mailboxes"""
    orientations = base.ORIENTATION_HORIZONTAL
    defaults = [
        ("maildir_path", "~/Mail", "path to the Maildir folder"),
        ("sub_folders", [], 'The subfolders to scan (e.g. [{"path": "INBOX", '
            '"label": "Home mail"}, {"path": "spam", "label": "Home junk"}]'),
        ("separator", " ", "the string to put between the subfolder strings."),
        ("total", False, "Whether or not to sum subfolders into a grand \
            total. The first label will be used."),
        ("hide_when_empty", False, "Whether not to display anything if "
         "the subfolder has no new mail"),
        ("empty_color", None, "Display color when no new mail is available"),
        ("nonempty_color", None, "Display color when new mail is available"),
        ("subfolder_fmt", "{label}: {value}",
         "Display format for one subfolder"),
    ]

    def __init__(self, **config):
        base.ThreadPoolText.__init__(self, "", **config)
        self.add_defaults(Maildir.defaults)

        # if it looks like a list of strings then we just convert them
        # and use the name as the label
        if isinstance(self.sub_folders[0], str):
            self.sub_folders = [
                {"path": folder, "label": folder}
                for folder in self.sub_folders
            ]

    def poll(self):
        """Scans the mailbox for new messages

        Returns
        =======
        A string representing the current mailbox state
        """
        state = {}

        def to_maildir_fmt(paths):
            for path in iter(paths):
                yield path.rsplit(":")[0]

        for sub_folder in self.sub_folders:
            path = os.path.join(os.path.expanduser(self.maildir_path),
                                sub_folder["path"])
            maildir = mailbox.Maildir(path)
            state[sub_folder["label"]] = 0

            for file in to_maildir_fmt(os.listdir(os.path.join(path, "new"))):
                if file in maildir:
                    state[sub_folder["label"]] += 1

        return self.format_text(state)

    def _format_one(self, label: str, value: int) -> str:
        if value == 0 and self.hide_when_empty:
            return ""

        s = self.subfolder_fmt.format(label=label, value=value)
        color = self.empty_color if value == 0 else self.nonempty_color

        if color is None:  # default to self.foreground
            return s

        return s.join((''.format(color),
                       ''))

    def format_text(self, state: Dict[str, int]) -> str:
        """Converts the state of the subfolders to a string

        Parameters
        ==========
        state: Dict[str, int]
            a dictionary mapping subfolder labels to new mail values

        Returns
        =======
        a string representation of the given state
        """
        if self.total:
            return self._format_one(self.sub_folders[0]["label"],
                                    sum(state.values()))
        else:
            return self.separator.join(
                self._format_one(*item) for item in state.items()
            )
<commit_msg>Fix Maildir widget defaults<commit_after># -*- coding: utf-8 -*-
# Copyright (c) 2011 Timo Schmiade
# Copyright (c) 2012 Phil Jackson
# Copyright (c) 2013 Tao Sauvage
# Copyright (c) 2014 Sean Vig
# Copyright (c) 2014 Tycho Andersen
# Copyright (c) 2016 Christoph Lassner
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import mailbox
import os.path
from typing import Dict

from libqtile.widget import base

class Maildir(base.ThreadPoolText):
    """A simple widget showing the number of new mails in maildir mailboxes"""
    orientations = base.ORIENTATION_HORIZONTAL
    defaults = [
        ("maildir_path", "~/Mail", "path to the Maildir folder"),
        (
            "sub_folders",
            [
                {"path": "INBOX", "label": "Home mail"},
                {"path": "spam", "label": "Home junk"}
            ],
            'List of subfolders to scan. Each subfolder is a dict of `path` '
            'and `label`.'
        ),
        ("separator", " ", "the string to put between the subfolder strings."),
        ("total", False, "Whether or not to sum subfolders into a grand \
            total. The first label will be used."),
        ("hide_when_empty", False, "Whether not to display anything if "
         "the subfolder has no new mail"),
        ("empty_color", None, "Display color when no new mail is available"),
        ("nonempty_color", None, "Display color when new mail is available"),
        ("subfolder_fmt", "{label}: {value}",
         "Display format for one subfolder"),
    ]

    def __init__(self, **config):
        base.ThreadPoolText.__init__(self, "", **config)
        self.add_defaults(Maildir.defaults)

        # if it looks like a list of strings then we just convert them
        # and use the name as the label
        if isinstance(self.sub_folders[0], str):
            self.sub_folders = [
                {"path": folder, "label": folder}
                for folder in self.sub_folders
            ]

    def poll(self):
        """Scans the mailbox for new messages

        Returns
        =======
        A string representing the current mailbox state
        """
        state = {}

        def to_maildir_fmt(paths):
            for path in iter(paths):
                yield path.rsplit(":")[0]

        for sub_folder in self.sub_folders:
            path = os.path.join(os.path.expanduser(self.maildir_path),
                                sub_folder["path"])
            maildir = mailbox.Maildir(path)
            state[sub_folder["label"]] = 0

            for file in to_maildir_fmt(os.listdir(os.path.join(path, "new"))):
                if file in maildir:
                    state[sub_folder["label"]] += 1

        return self.format_text(state)

    def _format_one(self, label: str, value: int) -> str:
        if value == 0 and self.hide_when_empty:
            return ""

        s = self.subfolder_fmt.format(label=label, value=value)
        color = self.empty_color if value == 0 else self.nonempty_color

        if color is None:  # default to self.foreground
            return s

        return s.join((''.format(color),
                       ''))

    def format_text(self, state: Dict[str, int]) -> str:
        """Converts the state of the subfolders to a string

        Parameters
        ==========
        state: Dict[str, int]
            a dictionary mapping subfolder labels to new mail values

        Returns
        =======
        a string representation of the given state
        """
        if self.total:
            return self._format_one(self.sub_folders[0]["label"],
                                    sum(state.values()))
        else:
            return self.separator.join(
                self._format_one(*item) for item in state.items()
            )
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
"""
@file
@brief Defines a :epkg:`sphinx` extension which if all parameters are documented.
"""
import inspect
from docutils import nodes
import sphinx
from sphinx.util import logging
from sphinx.util.docfields import DocFieldTransformer, _is_single_paragraph
from .import_object_helper import import_any_object

def check_typed_make_field(self,
                           types,     # type: Dict[unicode, List[nodes.Node]]
                           domain,    # type: unicode
                           items,     # type: Tuple
                           env=None,  # type: BuildEnvironment
                           # type inspect.Parameters (to check that all parameters are documented)
                           parameters=None,
                           function_name=None,  # str
                           docname=None,  # str
                           kind=None   # str
                           ):
    """
    Overwrites function
    `make_field <https://github.com/sphinx-doc/sphinx/blob/master/sphinx/util/docfields.py#L197>`_.
    Processes one argument of a function.

    @param      self            from original function
    @param      types           from original function
    @param      domain          from original function
    @param      items           from original function
    @param      env             from original function
    @param      parameters      list of known arguments for the function or method
    @param      function_name   function name these arguments belong to
    @param      docname         document which contains the object
    @param      kind            tells which kind of object *function_name* is (function, method or class)

    Example of warnings it raises:

    ::

        [docassert] 'onefunction' has no parameter 'a' (in '...project_name\\subproject\\myexampleb.py').
        [docassert] 'onefunction' has undocumented parameters 'a, b' (...project_name\\subproject\\myexampleb.py').

    """
    if parameters is None:
        parameters = None
        check_params = {}
    else:
        parameters = list(parameters)
        if kind == "method":
            parameters = parameters[1:]

        def kg(p):
            "local function"
            return p if isinstance(p, str) else p.name
        check_params = {kg(p): 0 for p in parameters}
    logger = logging.getLogger("docassert")

    def check_item(fieldarg, content, logger):
        "local function"
        if fieldarg not in check_params:
            if function_name is not None:
                logger.warning("[docassert] '{0}' has no parameter '{1}' (in '{2}').".format(
                    function_name, fieldarg, docname))
        else:
            check_params[fieldarg] += 1
            if check_params[fieldarg] > 1:
                logger.warning("[docassert] '{1}' of '{0}' is duplicated (in '{2}').".format(
                    function_name, fieldarg, docname))

    if isinstance(items, list):
        for fieldarg, content in items:
            check_item(fieldarg, content, logger)
        mini = None if len(check_params) == 0 else min(check_params.values())
        if mini == 0:
            check_params = list(check_params.items())
            nodoc = list(sorted(k for k, v in check_params if v == 0))
            if len(nodoc) > 0:
                if len(nodoc) == 1 and nodoc[0] == 'self':
                    # Behavior should be improved.
                    pass
                else:
                    logger.warning("[docassert] '{0}' has undocumented parameters '{1}' (in '{2}').".format(
                        function_name, ", ".join(nodoc), docname))
    else:
        # Documentation related to the return.
        pass

class OverrideDocFieldTransformer:
    """
    Overrides one function with assigning it to a method
    """

    def __init__(self, replaced):
        """
        Constructor

        @param      replaced        should be *DocFieldTransformer.transform*
        """
        self.replaced = replaced

    def override_transform(self, other_self, node):
        """
        Transform a single field list *node*.
        Overwrite function `transform <https://github.com/sphinx-doc/sphinx/blob/master/sphinx/util/docfields.py#L271>`_.
        It only adds extra verification and returns results from the replaced function.

        @param      other_self      the builder
        @param      node            node the replaced function changes or replace

        The function parses the original function and checks that the list of arguments declared
        by the function is the same the list of documented arguments.
        """
        typemap = other_self.typemap
        entries = []
        groupindices = {}  # type: Dict[unicode, int]
        types = {}  # type: Dict[unicode, Dict]

        # step 1: traverse all fields and collect field types and content
        for field in node:
            fieldname, fieldbody = field
            try:
                # split into field type and argument
                fieldtype, fieldarg = fieldname.astext().split(None, 1)
            except ValueError:
                # maybe an argument-less field type?
                fieldtype, fieldarg = fieldname.astext(), ''
            if fieldtype != "param":
                continue
            typedesc, is_typefield = typemap.get(fieldtype, (None, None))

            # sort out unknown fields
            if typedesc is None or typedesc.has_arg != bool(fieldarg):
                # either the field name is unknown, or the argument doesn't
                # match the spec; capitalize field name and be done with it
                new_fieldname = fieldtype[0:1].upper() + fieldtype[1:]
                if fieldarg:
                    new_fieldname += ' ' + fieldarg
                fieldname[0] = nodes.Text(new_fieldname)
                entries.append(field)
                continue

            typename = typedesc.name

            # collect the content, trying not to keep unnecessary paragraphs
            if _is_single_paragraph(fieldbody):
                content = fieldbody.children[0].children
            else:
                content = fieldbody.children

            # if the field specifies a type, put it in the types collection
            if is_typefield:
                # filter out only inline nodes; others will result in invalid
                # markup being written out
                content = [n for n in content if isinstance(
                    n, (nodes.Inline, nodes.Text))]
                if content:
                    types.setdefault(typename, {})[fieldarg] = content
                continue

            # also support syntax like ``:param type name:``
            if typedesc.is_typed:
                try:
                    argtype, argname = fieldarg.split(None, 1)
                except ValueError:
                    pass
                else:
                    types.setdefault(typename, {})[argname] = [
                        nodes.Text(argtype)]
                    fieldarg = argname

            translatable_content = nodes.inline(
                fieldbody.rawsource, translatable=True)
            translatable_content.document = fieldbody.parent.document
            translatable_content.source = fieldbody.parent.source
            translatable_content.line = fieldbody.parent.line
            translatable_content += content

            # Import object, get the list of parameters
            docs = fieldbody.parent.source.split(":docstring of")[-1].strip()

            myfunc = None
            funckind = None
            function_name = None
            excs = []
            try:
                myfunc, function_name, funckind = import_any_object(docs)
            except ImportError as e:
                excs.append(e)

            if myfunc is None:
                if len(excs) > 0:
                    reasons = "\n".join("   {0}".format(e) for e in excs)
                else:
                    reasons = "unknown"
                logger = logging.getLogger("docassert")
                logger.warning(
                    "[docassert] unable to import object '{0}', reasons:\n{1}".format(docs, reasons))
                myfunc = None

            if myfunc is None:
                signature = None
                parameters = None
            else:
                try:
                    signature = inspect.signature(myfunc)
                    parameters = signature.parameters
                except TypeError:
                    logger = logging.getLogger("docassert")
                    logger.warning(
                        "[docassert] unable to get signature of '{0}'.".format(docs))
                    signature = None
                    parameters = None

            # grouped entries need to be collected in one entry, while others
            # get one entry per field
            if typedesc.is_grouped:
                if typename in groupindices:
                    group = entries[groupindices[typename]]
                else:
                    groupindices[typename] = len(entries)
                    group = [typedesc, []]
                    entries.append(group)
                entry = typedesc.make_entry(fieldarg, [translatable_content])
                group[1].append(entry)
            else:
                entry = typedesc.make_entry(fieldarg, [translatable_content])
                entries.append([typedesc, entry])

        # step 2: all entries are collected, check the parameters list.
        try:
            env = other_self.directive.state.document.settings.env
        except AttributeError as e:
            logger = logging.getLogger("docassert")
            logger.warning("[docassert] {0}".format(e))
            env = None

        docname = fieldbody.parent.source.split(':docstring')[0]

        for entry in entries:
            if isinstance(entry, nodes.field):
                # raise NotImplementedError()
                logger = logging.getLogger("docassert")
                logger.warning(
                    "[docassert] unable to checl [nodes.field] {0}".format(entry))
            else:
                fieldtype, content = entry
                fieldtypes = types.get(fieldtype.name, {})
                check_typed_make_field(other_self, fieldtypes, other_self.directive.domain,
                                       content, env=env, parameters=parameters,
                                       function_name=function_name, docname=docname,
                                       kind=funckind)

        return self.replaced(other_self, node)

def setup_docassert(app):
    """
    Setup for ``docassert`` extension (sphinx).
    This changes ``DocFieldTransformer.transform`` and replaces
    it by a function which calls the current function and does
    extra checking on the list of parameters.

    .. warning:: This class does not handle methods if the parameter name
        for the class is different from *self*. Classes included in other
        classes are not properly handled.
    """
    inst = OverrideDocFieldTransformer(DocFieldTransformer.transform)

    def local_transform(me, node):
        "local function"
        return inst.override_transform(me, node)

    DocFieldTransformer.transform = local_transform
    return {'version': sphinx.__display_version__, 'parallel_read_safe': True}

def setup(app):
    "see @see fn setup_docassert"
    return setup_docassert(app)
<commit_msg>catch another exception<commit_after># -*- coding: utf-8 -*-
"""
@file
@brief Defines a :epkg:`sphinx` extension which if all parameters are documented.
"""
import inspect
from docutils import nodes
import sphinx
from sphinx.util import logging
from sphinx.util.docfields import DocFieldTransformer, _is_single_paragraph
from .import_object_helper import import_any_object

def check_typed_make_field(self,
                           types,     # type: Dict[unicode, List[nodes.Node]]
                           domain,    # type: unicode
                           items,     # type: Tuple
                           env=None,  # type: BuildEnvironment
                           # type inspect.Parameters (to check that all parameters are documented)
                           parameters=None,
                           function_name=None,  # str
                           docname=None,  # str
                           kind=None   # str
                           ):
    """
    Overwrites function
    `make_field <https://github.com/sphinx-doc/sphinx/blob/master/sphinx/util/docfields.py#L197>`_.
    Processes one argument of a function.

    @param      self            from original function
    @param      types           from original function
    @param      domain          from original function
    @param      items           from original function
    @param      env             from original function
    @param      parameters      list of known arguments for the function or method
    @param      function_name   function name these arguments belong to
    @param      docname         document which contains the object
    @param      kind            tells which kind of object *function_name* is (function, method or class)

    Example of warnings it raises:

    ::

        [docassert] 'onefunction' has no parameter 'a' (in '...project_name\\subproject\\myexampleb.py').
        [docassert] 'onefunction' has undocumented parameters 'a, b' (...project_name\\subproject\\myexampleb.py').

    """
    if parameters is None:
        parameters = None
        check_params = {}
    else:
        parameters = list(parameters)
        if kind == "method":
            parameters = parameters[1:]

        def kg(p):
            "local function"
            return p if isinstance(p, str) else p.name
        check_params = {kg(p): 0 for p in parameters}
    logger = logging.getLogger("docassert")

    def check_item(fieldarg, content, logger):
        "local function"
        if fieldarg not in check_params:
            if function_name is not None:
                logger.warning("[docassert] '{0}' has no parameter '{1}' (in '{2}').".format(
                    function_name, fieldarg, docname))
        else:
            check_params[fieldarg] += 1
            if check_params[fieldarg] > 1:
                logger.warning("[docassert] '{1}' of '{0}' is duplicated (in '{2}').".format(
                    function_name, fieldarg, docname))

    if isinstance(items, list):
        for fieldarg, content in items:
            check_item(fieldarg, content, logger)
        mini = None if len(check_params) == 0 else min(check_params.values())
        if mini == 0:
            check_params = list(check_params.items())
            nodoc = list(sorted(k for k, v in check_params if v == 0))
            if len(nodoc) > 0:
                if len(nodoc) == 1 and nodoc[0] == 'self':
                    # Behavior should be improved.
                    pass
                else:
                    logger.warning("[docassert] '{0}' has undocumented parameters '{1}' (in '{2}').".format(
                        function_name, ", ".join(nodoc), docname))
    else:
        # Documentation related to the return.
        pass

class OverrideDocFieldTransformer:
    """
    Overrides one function with assigning it to a method
    """

    def __init__(self, replaced):
        """
        Constructor

        @param      replaced        should be *DocFieldTransformer.transform*
        """
        self.replaced = replaced

    def override_transform(self, other_self, node):
        """
        Transform a single field list *node*.
        Overwrite function `transform <https://github.com/sphinx-doc/sphinx/blob/master/sphinx/util/docfields.py#L271>`_.
        It only adds extra verification and returns results from the replaced function.

        @param      other_self      the builder
        @param      node            node the replaced function changes or replace

        The function parses the original function and checks that the list of arguments declared
        by the function is the same the list of documented arguments.
        """
        typemap = other_self.typemap
        entries = []
        groupindices = {}  # type: Dict[unicode, int]
        types = {}  # type: Dict[unicode, Dict]

        # step 1: traverse all fields and collect field types and content
        for field in node:
            fieldname, fieldbody = field
            try:
                # split into field type and argument
                fieldtype, fieldarg = fieldname.astext().split(None, 1)
            except ValueError:
                # maybe an argument-less field type?
                fieldtype, fieldarg = fieldname.astext(), ''
            if fieldtype != "param":
                continue
            typedesc, is_typefield = typemap.get(fieldtype, (None, None))

            # sort out unknown fields
            if typedesc is None or typedesc.has_arg != bool(fieldarg):
                # either the field name is unknown, or the argument doesn't
                # match the spec; capitalize field name and be done with it
                new_fieldname = fieldtype[0:1].upper() + fieldtype[1:]
                if fieldarg:
                    new_fieldname += ' ' + fieldarg
                fieldname[0] = nodes.Text(new_fieldname)
                entries.append(field)
                continue

            typename = typedesc.name

            # collect the content, trying not to keep unnecessary paragraphs
            if _is_single_paragraph(fieldbody):
                content = fieldbody.children[0].children
            else:
                content = fieldbody.children

            # if the field specifies a type, put it in the types collection
            if is_typefield:
                # filter out only inline nodes; others will result in invalid
                # markup being written out
                content = [n for n in content if isinstance(
                    n, (nodes.Inline, nodes.Text))]
                if content:
                    types.setdefault(typename, {})[fieldarg] = content
                continue

            # also support syntax like ``:param type name:``
            if typedesc.is_typed:
                try:
                    argtype, argname = fieldarg.split(None, 1)
                except ValueError:
                    pass
                else:
                    types.setdefault(typename, {})[argname] = [
                        nodes.Text(argtype)]
                    fieldarg = argname

            translatable_content = nodes.inline(
                fieldbody.rawsource, translatable=True)
            translatable_content.document = fieldbody.parent.document
            translatable_content.source = fieldbody.parent.source
            translatable_content.line = fieldbody.parent.line
            translatable_content += content

            # Import object, get the list of parameters
            docs = fieldbody.parent.source.split(":docstring of")[-1].strip()

            myfunc = None
            funckind = None
            function_name = None
            excs = []
            try:
                myfunc, function_name, funckind = import_any_object(docs)
            except ImportError as e:
                excs.append(e)

            if myfunc is None:
                if len(excs) > 0:
                    reasons = "\n".join("   {0}".format(e) for e in excs)
                else:
                    reasons = "unknown"
                logger = logging.getLogger("docassert")
                logger.warning(
                    "[docassert] unable to import object '{0}', reasons:\n{1}".format(docs, reasons))
                myfunc = None

            if myfunc is None:
                signature = None
                parameters = None
            else:
                try:
                    signature = inspect.signature(myfunc)
                    parameters = signature.parameters
                except (TypeError, ValueError):
                    logger = logging.getLogger("docassert")
                    logger.warning(
                        "[docassert] unable to get signature of '{0}'.".format(docs))
                    signature = None
                    parameters = None

            # grouped entries need to be collected in one entry, while others
            # get one entry per field
            if typedesc.is_grouped:
                if typename in groupindices:
                    group = entries[groupindices[typename]]
                else:
                    groupindices[typename] = len(entries)
                    group = [typedesc, []]
                    entries.append(group)
                entry = typedesc.make_entry(fieldarg, [translatable_content])
                group[1].append(entry)
            else:
                entry = typedesc.make_entry(fieldarg, [translatable_content])
                entries.append([typedesc, entry])

        # step 2: all entries are collected, check the parameters list.
        try:
            env = other_self.directive.state.document.settings.env
        except AttributeError as e:
            logger = logging.getLogger("docassert")
            logger.warning("[docassert] {0}".format(e))
            env = None

        docname = fieldbody.parent.source.split(':docstring')[0]

        for entry in entries:
            if isinstance(entry, nodes.field):
                # raise NotImplementedError()
                logger = logging.getLogger("docassert")
                logger.warning(
                    "[docassert] unable to checl [nodes.field] {0}".format(entry))
            else:
                fieldtype, content = entry
                fieldtypes = types.get(fieldtype.name, {})
                check_typed_make_field(other_self, fieldtypes, other_self.directive.domain,
                                       content, env=env, parameters=parameters,
                                       function_name=function_name, docname=docname,
                                       kind=funckind)

        return self.replaced(other_self, node)

def setup_docassert(app):
    """
    Setup for ``docassert`` extension (sphinx).
    This changes ``DocFieldTransformer.transform`` and replaces
    it by a function which calls the current function and does
    extra checking on the list of parameters.

    .. warning:: This class does not handle methods if the parameter name
        for the class is different from *self*. Classes included in other
        classes are not properly handled.
    """
    inst = OverrideDocFieldTransformer(DocFieldTransformer.transform)

    def local_transform(me, node):
        "local function"
        return inst.override_transform(me, node)

    DocFieldTransformer.transform = local_transform
    return {'version': sphinx.__display_version__, 'parallel_read_safe': True}

def setup(app):
    "see @see fn setup_docassert"
    return setup_docassert(app)
<|endoftext|>