#  Sources (c) 2002-2004, 2006-2009, 2012
#    David Turner <david@freetype.org>
#
#
# this file contains definitions of classes needed to decompose
# C sources files into a series of multi-line "blocks". There are
# two kinds of blocks:
#
#   - normal blocks, which contain source code or ordinary comments
#
#   - documentation blocks, which have restricted formatting, and
#     whose text always start with a documentation markup tag like
#     "<Function>", "<Type>", etc..
#
# the routines used to process the content of documentation blocks
# are not contained here, but in "content.py"
#
# the classes and methods found here only deal with text parsing
# and basic documentation block extraction
#

import fileinput, re, sys, os, string

################################################################
##
##  BLOCK FORMAT PATTERN
##
##   A simple class containing compiled regular expressions used
##   to detect potential documentation format block comments within
##   C source code
##
##   note that the 'column' pattern must contain a group that will
##   be used to "unbox" the content of documentation comment blocks
##
class  SourceBlockFormat:

    def  __init__( self, id, start, column, end ):
        """create a block pattern, used to recognize special documentation blocks"""
        self.id     = id
        self.start  = re.compile( start, re.VERBOSE )
        self.column = re.compile( column, re.VERBOSE )
        self.end    = re.compile( end, re.VERBOSE )

#
# format 1 documentation comment blocks look like the following:
#
#    /************************************/
#    /*                                  */
#    /*                                  */
#    /*                                  */
#    /************************************/
#
# we define a few regular expressions here to detect them
#

start = r'''
  \s*      # any number of whitespace
  /\*{2,}/ # followed by '/' and at least two asterisks then '/'
  \s*$     # probably followed by whitespace
'''

column = r'''
  \s*      # any number of whitespace
  /\*{1}   # followed by '/' and precisely one asterisk
  ([^*].*) # followed by anything (group 1)
  \*{1}/   # followed by one asterisk and a '/'
  \s*$     # probably followed by whitespace
'''

re_source_block_format1 = SourceBlockFormat( 1, start, column, start )

#
# format 2 documentation comment blocks look like the following:
#
#    /************************************ (at least 2 asterisks)
#     *
#     *
#     *
#     *
#     **/       (1 or more asterisks at the end)
#
# we define a few regular expressions here to detect them
#
start = r'''
  \s*     # any number of whitespace
  /\*{2,} # followed by '/' and at least two asterisks
  \s*$    # probably followed by whitespace
'''

column = r'''
  \s*        # any number of whitespace
  \*{1}(?!/) # followed by precisely one asterisk not followed by `/'
  (.*)       # then anything (group1)
'''

end = r'''
  \s*  # any number of whitespace
  \*+/ # followed by at least one asterisk, then '/'
'''

re_source_block_format2 = SourceBlockFormat( 2, start, column, end )

#
# the list of supported documentation block formats, we could add new ones
# relatively easily
#
re_source_block_formats = [re_source_block_format1, re_source_block_format2]

#
# the following regular expressions corresponds to markup tags
# within the documentation comment blocks. they're equivalent
# despite their different syntax
#
# notice how each markup tag _must_ begin a new line
#
re_markup_tag1 = re.compile( r'''\s*<((?:\w|-)*)>''' )  # <xxxx> format
re_markup_tag2 = re.compile( r'''\s*@((?:\w|-)*):''' )  # @xxxx: format

#
# the list of supported markup tags, we could add new ones relatively
# easily
#
re_markup_tags = [re_markup_tag1, re_markup_tag2]

#
# used to detect a cross-reference, after markup tags have been stripped
#
re_crossref = re.compile( r'@((?:\w|-)*)(.*)' )

#
# used to detect italic and bold styles in paragraph text
#
re_italic = re.compile( r"_(\w(\w|')*)_(.*)" )     #  _italic_
re_bold   = re.compile( r"\*(\w(\w|')*)\*(.*)" )   #  *bold*

#
# used to detect the end of commented source lines
#
re_source_sep = re.compile( r'\s*/\*\s*\*/' )

#
# used to perform cross-reference within source output
#
re_source_crossref = re.compile( r'(\W*)(\w*)' )

#
# a list of reserved source keywords
#
re_source_keywords = re.compile( '''\\b ( typedef   |
                                          struct    |
                                          enum      |
                                          union     |
                                          const     |
                                          char      |
                                          int       |
                                          short     |
                                          long      |
                                          void      |
                                          signed    |
                                          unsigned  |
                                          \#include |
                                          \#define  |
                                          \#undef   |
                                          \#if      |
                                          \#ifdef   |
                                          \#ifndef  |
                                          \#else    |
                                          \#endif   ) \\b''', re.VERBOSE )

################################################################
##
##  SOURCE BLOCK CLASS
##
##   A SourceProcessor is in charge of reading a C source file
##   and decomposing it into a series of different "SourceBlocks".
##   each one of these blocks can be made of the following data:
##
##   - A documentation comment block that starts with "/**" and
##     whose exact format will be discussed later
##
##   - normal sources lines, including comments
##
##   the important fields in a text block are the following ones:
##
##     self.lines   : a list of text lines for the corresponding block
##
##     self.content : for documentation comment blocks only, this is the
##                    block content that has been "unboxed" from its
##                    decoration. This is None for all other blocks
##                    (i.e. sources or ordinary comments with no starting
##                     markup tag)
##
class  SourceBlock:

    def  __init__( self, processor, filename, lineno, lines ):
        self.processor = processor
        self.filename  = filename
        self.lineno    = lineno
        self.lines     = lines[:]
        self.format    = processor.format
        self.content   = []

        if self.format == None:
            return

        words = []

        # extract comment lines
        lines = []

        for line0 in self.lines:
            m = self.format.column.match( line0 )
            if m:
                lines.append( m.group( 1 ) )

        # now, look for a markup tag
        for l in lines:
            l = string.strip( l )
            if len( l ) > 0:
                for tag in re_markup_tags:
                    if tag.match( l ):
                        self.content = lines
                        return

    def  location( self ):
        return "(" + self.filename + ":" + repr( self.lineno ) + ")"

    # debugging only - not used in normal operations
    def  dump( self ):
        if self.content:
            print "{{{content start---"
            for l in self.content:
                print l
            print "---content end}}}"
            return

        fmt = ""
        if self.format:
            fmt = repr( self.format.id ) + " "

        for line in self.lines:
            print line

################################################################
##
##  SOURCE PROCESSOR CLASS
##
##   The SourceProcessor is in charge of reading a C source file
##   and decomposing it into a series of different "SourceBlock"
##   objects.
##
##   each one of these blocks can be made of the following data:
##
##   - A documentation comment block that starts with "/**" and
##     whose exact format will be discussed later
##
##   - normal sources lines, include comments
##
##
class  SourceProcessor:

    def  __init__( self ):
        """initialize a source processor"""
        self.blocks   = []
        self.filename = None
        self.format   = None
        self.lines    = []

    def  reset( self ):
        """reset a block processor, clean all its blocks"""
        self.blocks = []
        self.format = None

    def  parse_file( self, filename ):
        """parse a C source file, and add its blocks to the processor's list"""
        self.reset()

        self.filename = filename

        fileinput.close()
        self.format = None
        self.lineno = 0
        self.lines  = []

        for line in fileinput.input( filename ):
            # strip trailing newlines, important on Windows machines!
            if line[-1] == '\012':
                line = line[0:-1]

            if self.format == None:
                self.process_normal_line( line )
            else:
                if self.format.end.match( line ):
                    # that's a normal block end, add it to 'lines' and
                    # create a new block
                    self.lines.append( line )
                    self.add_block_lines()
                elif self.format.column.match( line ):
                    # that's a normal column line, add it to 'lines'
                    self.lines.append( line )
                else:
                    # humm.. this is an unexpected block end,
                    # create a new block, but don't process the line
                    self.add_block_lines()

                    # we need to process the line again
                    self.process_normal_line( line )

        # record the last lines
        self.add_block_lines()

    def  process_normal_line( self, line ):
        """process a normal line and check whether it is the start of a new block"""
        for f in re_source_block_formats:
            if f.start.match( line ):
                self.add_block_lines()
                self.format = f
                self.lineno = fileinput.filelineno()

        self.lines.append( line )

    def  add_block_lines( self ):
        """add the current accumulated lines and create a new block"""
        if self.lines != []:
            block = SourceBlock( self, self.filename, self.lineno, self.lines )

            self.blocks.append( block )
            self.format = None
            self.lines  = []

    # debugging only, not used in normal operations
    def  dump( self ):
        """print all blocks in a processor"""
        for b in self.blocks:
            b.dump()

# eof


import collections

import bacon
from bacon import native

class Style(object):
    def __init__(self, font, color=None, background_color=None):
        self.font = font
        self.color = color
        self.background_color = background_color

class GlyphRun(object):
    def __init__(self, style, text, glyphs=None):
        if glyphs is None:
            glyphs = style.font.get_glyphs(text)
        self.glyphs = glyphs
        self.style = style
        self.advance = sum(g.advance for g in self.glyphs)

    def __repr__(self):
        return 'GlyphRun("%s")' % (''.join(g.char for g in self.glyphs))

class GlyphLine(object):
    def __init__(self, runs, content_width = None):
        self.runs = runs
        if content_width is None:
            content_width = sum(run.advance for run in runs)
        self.content_width = content_width
        self.ascent = min(run.style.font.ascent for run in runs)
        self.descent = max(run.style.font.descent for run in runs)
        self.x = 0
        self.y = 0

    def __repr__(self):
        return 'GlyphLine(runs=%r)' % self.runs

@native.enum
class Alignment(object):
    left = 0
    center = 1
    right = 2

@native.enum
class VerticalAlignment(object):
    baseline = 0
    top = 1
    center = 2
    bottom = 3

@native.enum
class Overflow(object):
    none = 0
    wrap = 1
    wrap_characters = 2

class GlyphLayout(object):
    '''Caches a layout of glyphs rendering a given string with bounding rectangle, layout metrics.
    '''
    def __init__(self, runs, x, y, width=None, height=None, align=Alignment.left, vertical_align=VerticalAlignment.baseline, overflow=Overflow.wrap):
        self._runs = runs
        self._x = x
        self._y = y
        self._width = width
        self._height = height
        self._align = align
        self._vertical_align = vertical_align
        self._overflow = overflow
        self._dirty = True

        self._content_width = None
        self._content_height = None
        self._lines = None

    def _get_runs(self):
        return self._runs
    def _set_runs(self, runs):
        self._runs = runs
        self._dirty = True
    runs = property(_get_runs, _set_runs)

    def _get_x(self):
        return self._x
    def _set_x(self, x):
        if x != self._x:
            self._x = x
            self._dirty = True
    x = property(_get_x, _set_x)

    def _get_y(self):
        return self._y
    def _set_y(self, y):
        if y != self._y:
            self._y = y
            self._dirty = True
    y = property(_get_y, _set_y)

    def _get_width(self):
        return self._width
    def _set_width(self, width):
        if width != self._width:
            self._width = width
            self._dirty = True
    width = property(_get_width, _set_width)

    def _get_height(self):
        return self._height
    def _set_height(self, height):
        if height != self._height:
            self._height = height
            self._dirty = True
    height = property(_get_height, _set_height)

    def _get_align(self):
        return self._align
    def _set_align(self, align):
        if align != self._align:
            self._align = align
            self._dirty = True
    align = property(_get_align, _set_align)

    def _get_vertical_align(self):
        return self._vertical_align
    def _set_vertical_align(self, vertical_align):
        if vertical_align != self._vertical_align:
            self._vertical_align = vertical_align
            self._dirty = True
    vertical_align = property(_get_vertical_align, _set_vertical_align)

    def _get_overflow(self):
        return self._overflow
    def _set_overflow(self, overflow):
        if overflow != self._overflow:
            self._overflow = overflow
            self._dirty = True
    overflow = property(_get_overflow, _set_height)

    @property
    def lines(self):
        if self._dirty:
            self._update()
        return self._lines

    @property
    def content_width(self):
        if self._dirty:
            self._update()
        return self._content_width

    @property
    def content_height(self):
        if self._dirty:
            self._update()
        return self._content_height

    def _update(self):
        self._dirty = False

        content_width = sum(run.advance for run in self._runs)
        if (self._width is None or
            self._overflow == Overflow.none or
            content_width <= self._width):
            self._lines = [GlyphLine(self._runs,
                                     content_width=content_width)]
        else:
            self._update_overflow()

        self._content_width = max(line.content_width for line in self.lines)
        self._content_height = sum(line.descent - line.ascent for line in self.lines)

        self._update_line_position()

    def _update_overflow(self):
        remaining_runs = collections.deque(self._runs)
        line_runs = []
        lines = []
        x = 0
        while remaining_runs:
            line_runs.append(remaining_runs.popleft())
            x += line_runs[-1].advance
            if x > self._width:
                if self._overflow == Overflow.wrap:
                    self._break_runs_word(x, line_runs, remaining_runs)
                elif self._overflow == Overflow.wrap_characters:
                    self._break_runs_character(x, line_runs, remaining_runs)
                if line_runs:
                    lines.append(GlyphLine(line_runs))
                    line_runs = []
                    x = 0

        if line_runs:
            lines.append(GlyphLine(line_runs))
        self._lines = lines

    def _break_runs_word(self, x, line_runs, remaining_runs):
        # Scan backwards through each glyph in line_runs until suitable break
        # is found.  Push remaining glyphs into remaining_runs
        start_x = x
        width = self._width
        for run_i in range(len(line_runs) - 1, -1, -1):
            run = line_runs[run_i]
            glyphs = run.glyphs
            for i in range(len(glyphs) - 1, -1, -1):
                x -= glyphs[i].advance
                if x >= width:
                    continue

                if glyphs[i]._char in u' \u200B':
                    if run_i != 0 or i != 0:
                        return self._break(line_runs, remaining_runs, run_i, i, i + 1)

        # No breaks found.  Repeat scan, break on character
        self._break_runs_character(start_x, line_runs, remaining_runs)

    def _break_runs_character(self, x, line_runs, remaining_runs):
        # Scan backwards through each glyph in line_runs until suitable break
        # is found.  Push remaining glyphs into remaining_runs
        width = self._width
        for run_i in range(len(line_runs) - 1, -1, -1):
            run = line_runs[run_i]
            glyphs = run.glyphs
            for i in range(len(glyphs) - 1, -1, -1):
                x -= glyphs[i].advance
                if x >= width:
                    continue

                # Force a break on one glyph if we couldn't break at all
                if run_i == i:
                    i += 1
                return self._break(line_runs, remaining_runs, run_i, i, i)

    def _break(self, line_runs, remaining_runs, run_i, end_glyph_i, start_glyph_i):
        split_run = line_runs[run_i]

        # Move entire runs to the right of run_i into remaining_runs
        for i in range(len(line_runs) - 1, run_i, -1):
            remaining_runs.appendleft(line_runs[i])
        del line_runs[run_i + 1:]

        # line_runs gets glyphs up to end_glyph_i of split_run
        line_runs[-1] = GlyphRun(split_run.style, '', split_run.glyphs[:end_glyph_i])

        # remaining_runs gets the right side of start_glyph_i
        remaining_runs.appendleft(GlyphRun(split_run.style, '', split_run.glyphs[start_glyph_i:]))

    def _update_line_position(self):
        if not self._lines:
            return

        x = self._x
        y = self._y
        width = self._width
        height = self._height
        align = self._align
        vertical_align = self._vertical_align

        if self._width is not None:
            # Align relative to box, not pivot
            if align == Alignment.center:
                x += width / 2
            elif align == Alignment.right:
                x += width

        if height is not None:
            # Align relative to box, not pivot
            if vertical_align == VerticalAlignment.center:
                y += height / 2
            elif vertical_align == VerticalAlignment.bottom:
                y += height
            elif vertical_align == VerticalAlignment.baseline:
                vertical_align = VerticalAlignment.top

        # Align first baseline vertically against pivot
        line = self.lines[0]
        if vertical_align == VerticalAlignment.center:
            y -= self._content_height / 2
        elif vertical_align == VerticalAlignment.bottom:
            y -= self._content_height + line.descent
        elif vertical_align == VerticalAlignment.baseline:
            y += line.ascent

        # Layout lines
        start_x = int(x)
        y = int(y)
        for line in self._lines:
            x = start_x
            if align == Alignment.center:
                x -= int(line.content_width / 2)
            elif align == Alignment.right:
                x -= line.content_width

            y -= self._lines[0].ascent

            line.x = x
            line.y = y

            y += self._lines[0].descent

def draw_string(font, text, x, y, width=None, height=None, align=Alignment.left, vertical_align=VerticalAlignment.baseline):
    '''Draw a string with the given font.

    :note: Text alignment and word-wrapping is not yet implemented.  The text is rendered with the left edge and
        baseline at ``(x, y)``.

    :param font: the :class:`Font` to render text with
    :param text: a string of text to render.
    '''
    style = Style(font)
    run = GlyphRun(style, text)
    glyph_layout = GlyphLayout([run], x, y, width, height, align, vertical_align)
    draw_glyph_layout(glyph_layout)

def draw_glyph_layout(glyph_layout):
    '''Draw a prepared :class:`GlyphLayout`
    '''
    pushed_color = False

    # Draw lines
    for line in glyph_layout.lines:
        x = line.x
        y = line.y

        for run in line.runs:
            style = run.style
            if style.color is not None:
                if not pushed_color:
                    bacon.push_color()
                    pushed_color = True
                bacon.set_color(*style.color)
            elif pushed_color:
                bacon.pop_color()
                pushed_color = False

            for glyph in run.glyphs:
                if glyph.image:
                    bacon.draw_image(glyph.image, x + glyph.offset_x, y - glyph.offset_y)
                x += glyph.advance

    if pushed_color:
        bacon.pop_color()


"""
Various utility stuff that is useful across the codebase.
"""

import datetime
import re

import pytz
from lxml import etree

# This can be used as a tzinfo arg to the datetime functions/methods.
UTC_TIMEZONE = pytz.utc

def parse_iso_8601_time_str(time_str):
    """
    Parses a standard ISO 8601 time string. The Route53 API uses these here
    and there.

    :param str time_str: An ISO 8601 time string.
    :rtype: datetime.datetime
    :returns: A timezone aware (UTC) datetime.datetime instance.
    """
    if re.search('\.\d{3}Z$', time_str):
        submitted_at = datetime.datetime.strptime(time_str, \
            '%Y-%m-%dT%H:%M:%S.%fZ')
    else:
        submitted_at = datetime.datetime.strptime(time_str, \
            '%Y-%m-%dT%H:%M:%SZ')
    # Parse the string, and make it explicitly UTC.
    return submitted_at.replace(tzinfo=UTC_TIMEZONE)

def prettyprint_xml(element):
    """
    A rough and dirty way to prettyprint an Element with indention.

    :param lxml.etree._Element element: The Element or ElementTree to format.
    :rtype: str
    :returns: A prettyprinted representation of the element.
    """

    return etree.tostring(element, pretty_print=True).decode('utf-8')


#  ToHTML (c) 2002, 2003, 2005, 2006, 2007, 2008
#    David Turner <david@freetype.org>

from sources import *
from content import *
from formatter import *

import time

# The following defines the HTML header used by all generated pages.
html_header_1 = """\
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>\
"""

html_header_2 = """\
 API Reference</title>

"""

html_header_3 = """

[<a href="\
"""

html_header_3i = """

[Index]

[TOC]

\
"""

html_header_5t = """\
">Index]

\
"""

html_header_6 = """\
 API Reference

"""

# The HTML footer used by all generated pages.
html_footer = """\

\
"""

# The header and footer used for each section.
section_title_header = "
"
section_title_footer = "
"

# The header and footer used for code segments.
code_header = '
'
code_footer = '
'

# Paragraph header and footer.
para_header = "
"
para_footer = "
"

# Block header and footer.
block_header        = '

'
block_footer_start  = """\

[Index]

[TOC]

"""

# Description header/footer.
description_header = '

'
description_footer = "

"

# Marker header/inter/footer combination.
marker_header = '

'
marker_inter  = "

"
marker_footer = "

"

# Header location header/footer.
header_location_header = '

'
header_location_footer = "

"

# Source code extracts header/footer.
source_header = '

\n'
source_footer = "\n

"

# Chapter header/inter/footer.
chapter_header = '

'
chapter_inter  = '

'
chapter_footer = '

'

# Index footer.
index_footer_start = """\

[TOC]

"""

# TOC footer.
toc_footer_start = """\

[Index]

"""

# source language keyword coloration/styling
keyword_prefix = ''
keyword_suffix = ''

section_synopsis_header = '
Synopsis
'
section_synopsis_footer = ''

# Translate a single line of source to HTML.  This will convert
# a "<" into "&lt.", ">" into "&gt.", etc.
def  html_quote( line ):
    result = string.replace( line, "&", "&" )
    result = string.replace( result, "<", "<" )
    result = string.replace( result, ">", ">" )
    return result

# same as 'html_quote', but ignores left and right brackets
def  html_quote0( line ):
    return string.replace( line, "&", "&" )

def  dump_html_code( lines, prefix = "" ):
    # clean the last empty lines
    l = len( self.lines )
    while l > 0 and string.strip( self.lines[l - 1] ) == "":
        l = l - 1

    # The code footer should be directly appended to the last code
    # line to avoid an additional blank line.
    print prefix + code_header,
    for line in self.lines[0 : l + 1]:
        print '\n' + prefix + html_quote( line ),
    print prefix + code_footer,

class  HtmlFormatter( Formatter ):

    def  __init__( self, processor, project_title, file_prefix ):
        Formatter.__init__( self, processor )

        global html_header_1, html_header_2, html_header_3
        global html_header_4, html_header_5, html_footer

        if file_prefix:
            file_prefix = file_prefix + "-"
        else:
            file_prefix = ""

        self.headers           = processor.headers
        self.project_title     = project_title
        self.file_prefix       = file_prefix
        self.html_header       = html_header_1 + project_title +              \
                                 html_header_2 +                              \
                                 html_header_3 + file_prefix + "index.html" + \
                                 html_header_4 + file_prefix + "toc.html" +   \
                                 html_header_5 + project_title +              \
                                 html_header_6

        self.html_index_header = html_header_1 + project_title +             \
                                 html_header_2 +                             \
                                 html_header_3i + file_prefix + "toc.html" + \
                                 html_header_5 + project_title +             \
                                 html_header_6

        self.html_toc_header   = html_header_1 + project_title +              \
                                 html_header_2 +                              \
                                 html_header_3 + file_prefix + "index.html" + \
                                 html_header_5t + project_title +             \
                                 html_header_6

        self.html_footer       = "generated on " +     \
                                 time.asctime( time.localtime( time.time() ) ) + \
                                 "" + html_footer

        self.columns = 3

    def  make_section_url( self, section ):
        return self.file_prefix + section.name + ".html"

    def  make_block_url( self, block ):
        return self.make_section_url( block.section ) + "#" + block.name

    def  make_html_words( self, words ):
        """ convert a series of simple words into some HTML text """
        line = ""
        if words:
            line = html_quote( words[0] )
            for w in words[1:]:
                line = line + " " + html_quote( w )

        return line

    def  make_html_word( self, word ):
        """analyze a simple word to detect cross-references and styling"""
        # look for cross-references
        m = re_crossref.match( word )
        if m:
            try:
                name = m.group( 1 )
                rest = m.group( 2 )
                block = self.identifiers[name]
                url   = self.make_block_url( block )
                return '' + name + '' + rest
            except:
                # we detected a cross-reference to an unknown item
                sys.stderr.write( \
                   "WARNING: undefined cross reference '" + name + "'.\n" )
                return '?' + name + '?' + rest

        # look for italics and bolds
        m = re_italic.match( word )
        if m:
            name = m.group( 1 )
            rest = m.group( 3 )
            return '' + name + '' + rest

        m = re_bold.match( word )
        if m:
            name = m.group( 1 )
            rest = m.group( 3 )
            return '' + name + '' + rest

        return html_quote( word )

    def  make_html_para( self, words ):
        """ convert words of a paragraph into tagged HTML text, handle xrefs """
        line = ""
        if words:
            line = self.make_html_word( words[0] )
            for word in words[1:]:
                line = line + " " + self.make_html_word( word )
            # convert `...' quotations into real left and right single quotes
            line = re.sub( r"(^|\W)`(.*?)'(\W|$)",  \
                           r'\1‘\2’\3', \
                           line )
            # convert tilde into non-breakable space
            line = string.replace( line, "~", " " )

        return para_header + line + para_footer

    def  make_html_code( self, lines ):
        """ convert a code sequence to HTML """
        line = code_header + '\n'
        for l in lines:
            line = line + html_quote( l ) + '\n'

        return line + code_footer

    def  make_html_items( self, items ):
        """ convert a field's content into some valid HTML """
        lines = []
        for item in items:
            if item.lines:
                lines.append( self.make_html_code( item.lines ) )
            else:
                lines.append( self.make_html_para( item.words ) )

        return string.join( lines, '\n' )

    def  print_html_items( self, items ):
        print self.make_html_items( items )

    def  print_html_field( self, field ):
        if field.name:
            print "

" + field.name + "	"

        print self.make_html_items( field.items )

        if field.name:
            print "

"

    def  html_source_quote( self, line, block_name = None ):
        result = ""
        while line:
            m = re_source_crossref.match( line )
            if m:
                name   = m.group( 2 )
                prefix = html_quote( m.group( 1 ) )
                length = len( m.group( 0 ) )

                if name == block_name:
                    # this is the current block name, if any
                    result = result + prefix + '' + name + ''
                elif re_source_keywords.match( name ):
                    # this is a C keyword
                    result = result + prefix + keyword_prefix + name + keyword_suffix
                elif self.identifiers.has_key( name ):
                    # this is a known identifier
                    block = self.identifiers[name]
                    result = result + prefix + '' + name + ''
                else:
                    result = result + html_quote( line[:length] )

                line = line[length:]
            else:
                result = result + html_quote( line )
                line   = []

        return result

    def  print_html_field_list( self, fields ):
        print "

"
        print "
"
        for field in fields:
            if len( field.name ) > 22:
              print "
" + field.name + "
"
              print "
	"
            else:
              print "
" + field.name + "	"

            self.print_html_items( field.items )
            print "
"
        print "
"

    def  print_html_markup( self, markup ):
        table_fields = []
        for field in markup.fields:
            if field.name:
                # we begin a new series of field or value definitions, we
                # will record them in the 'table_fields' list before outputting
                # all of them as a single table
                #
                table_fields.append( field )
            else:
                if table_fields:
                    self.print_html_field_list( table_fields )
                    table_fields = []

                self.print_html_items( field.items )

        if table_fields:
            self.print_html_field_list( table_fields )

    #
    #  Formatting the index
    #
    def  index_enter( self ):
        print self.html_index_header
        self.index_items = {}

    def  index_name_enter( self, name ):
        block = self.identifiers[name]
        url   = self.make_block_url( block )
        self.index_items[name] = url

    def  index_exit( self ):
        # block_index already contains the sorted list of index names
        count = len( self.block_index )
        rows  = ( count + self.columns - 1 ) / self.columns

        print "
"
        for r in range( rows ):
            line = "
"
            for c in range( self.columns ):
                i = r + c * rows
                if i < count:
                    bname = self.block_index[r + c * rows]
                    url   = self.index_items[bname]
                    line = line + '' + bname + '	'
                else:
                    line = line + '	'
            line = line + "
"
            print line

        print "
"

        print index_footer_start +            \
              self.file_prefix + "toc.html" + \
              index_footer_end

        print self.html_footer

        self.index_items = {}

    def  index_dump( self, index_filename = None ):
        if index_filename == None:
            index_filename = self.file_prefix + "index.html"

        Formatter.index_dump( self, index_filename )

    #
    #  Formatting the table of content
    #
    def  toc_enter( self ):
        print self.html_toc_header
        print "
Table of Contents
"

    def  toc_chapter_enter( self, chapter ):
        print  chapter_header + string.join( chapter.title ) + chapter_inter
        print "
"

    def  toc_section_enter( self, section ):
        print '
'
        print '' + \
               section.title + '	'

        print self.make_html_para( section.abstract )

    def  toc_section_exit( self, section ):
        print "
"

    def  toc_chapter_exit( self, chapter ):
        print "
"
        print chapter_footer

    def  toc_index( self, index_filename ):
        print chapter_header +                                      \
              'Global Index' + \
              chapter_inter + chapter_footer

    def  toc_exit( self ):
        print toc_footer_start +                \
              self.file_prefix + "index.html" + \
              toc_footer_end

        print self.html_footer

    def  toc_dump( self, toc_filename = None, index_filename = None ):
        if toc_filename == None:
            toc_filename = self.file_prefix + "toc.html"

        if index_filename == None:
            index_filename = self.file_prefix + "index.html"

        Formatter.toc_dump( self, toc_filename, index_filename )

    #
    #  Formatting sections
    #
    def  section_enter( self, section ):
        print self.html_header

        print section_title_header
        print section.title
        print section_title_footer

        maxwidth = 0
        for b in section.blocks.values():
            if len( b.name ) > maxwidth:
                maxwidth = len( b.name )

        width = 70  # XXX magic number
        if maxwidth <> 0:
            # print section synopsis
            print section_synopsis_header
            print "
"

            columns = width / maxwidth
            if columns < 1:
                columns = 1

            count = len( section.block_names )
            rows  = ( count + columns - 1 ) / columns

            for r in range( rows ):
                line = "
"
                for c in range( columns ):
                    i = r + c * rows
                    line = line + '	'
                    if i < count:
                        name = section.block_names[i]
                        line = line + '' + name + ''

                    line = line + '	'
                line = line + "
"
                print line

            print "

"
            print section_synopsis_footer

        print description_header
        print self.make_html_items( section.description )
        print description_footer

    def  block_enter( self, block ):
        print block_header

        # place html anchor if needed
        if block.name:
            print '
' + block.name + '
'

        # dump the block C source lines now
        if block.code:
            header = ''
            for f in self.headers.keys():
                if block.source.filename.find( f ) >= 0:
                    header = self.headers[f] + ' (' + f + ')'
                    break;

#           if not header:
#               sys.stderr.write( \
#                 'WARNING: No header macro for ' + block.source.filename + '.\n' )

            if header:
                print header_location_header
                print 'Defined in ' + header + '.'
                print header_location_footer

            print source_header
            for l in block.code:
                print self.html_source_quote( l, block.name )
            print source_footer

    def  markup_enter( self, markup, block ):
        if markup.tag == "description":
            print description_header
        else:
            print marker_header + markup.tag + marker_inter

        self.print_html_markup( markup )

    def  markup_exit( self, markup, block ):
        if markup.tag == "description":
            print description_footer
        else:
            print marker_footer

    def  block_exit( self, block ):
        print block_footer_start + self.file_prefix + "index.html" + \
              block_footer_middle + self.file_prefix + "toc.html" +  \
              block_footer_end

    def  section_exit( self, section ):
        print html_footer

    def  section_dump_all( self ):
        for section in self.sections:
            self.section_dump( section, self.file_prefix + section.name + '.html' )

# eof


#!/usr/bin/env python
#
#  DocMaker (c) 2002, 2004, 2008 David Turner <david@freetype.org>
#
# This program is a re-write of the original DocMaker took used
# to generate the API Reference of the FreeType font engine
# by converting in-source comments into structured HTML.
#
# This new version is capable of outputting XML data, as well
# as accepts more liberal formatting options.
#
# It also uses regular expression matching and substitution
# to speed things significantly.
#

from sources   import *
from content   import *
from utils     import *
from formatter import *
from tohtml    import *

import utils

import sys, os, time, string, glob, getopt

def  usage():
    print "\nDocMaker Usage information\n"
    print "  docmaker [options] file1 [file2 ...]\n"
    print "using the following options:\n"
    print "  -h : print this page"
    print "  -t : set project title, as in '-t \"My Project\"'"
    print "  -o : set output directory, as in '-o mydir'"
    print "  -p : set documentation prefix, as in '-p ft2'"
    print ""
    print "  --title  : same as -t, as in '--title=\"My Project\"'"
    print "  --output : same as -o, as in '--output=mydir'"
    print "  --prefix : same as -p, as in '--prefix=ft2'"

def  main( argv ):
    """main program loop"""

    global output_dir

    try:
        opts, args = getopt.getopt( sys.argv[1:], \
                                    "ht:o:p:",    \
                                    ["help", "title=", "output=", "prefix="] )
    except getopt.GetoptError:
        usage()
        sys.exit( 2 )

    if args == []:
        usage()
        sys.exit( 1 )

    # process options
    #
    project_title  = "Project"
    project_prefix = None
    output_dir     = None

    for opt in opts:
        if opt[0] in ( "-h", "--help" ):
            usage()
            sys.exit( 0 )

        if opt[0] in ( "-t", "--title" ):
            project_title = opt[1]

        if opt[0] in ( "-o", "--output" ):
            utils.output_dir = opt[1]

        if opt[0] in ( "-p", "--prefix" ):
            project_prefix = opt[1]

    check_output()

    # create context and processor
    source_processor  = SourceProcessor()
    content_processor = ContentProcessor()

    # retrieve the list of files to process
    file_list = make_file_list( args )
    for filename in file_list:
        source_processor.parse_file( filename )
        content_processor.parse_sources( source_processor )

    # process sections
    content_processor.finish()

    formatter = HtmlFormatter( content_processor, project_title, project_prefix )

    formatter.toc_dump()
    formatter.index_dump()
    formatter.section_dump_all()

# if called from the command line
#
if __name__ == '__main__':
    main( sys.argv )

# eof


from route53.xml_parsers.common_hosted_zone import parse_hosted_zone, parse_delegation_set

def get_hosted_zone_by_id_parser(root, connection):
    """
    Parses the API responses for the
    :py:meth:`route53.connection.Route53Connection.get_hosted_zone_by_id` method.

    :param lxml.etree._Element root: The root node of the etree parsed
        response from the API.
    :param Route53Connection connection: The connection instance used to
        query the API.
    :rtype: HostedZone
    :returns: The requested HostedZone.
    """

    e_zone = root.find('./{*}HostedZone')
    # This pops out a HostedZone instance.
    hosted_zone =  parse_hosted_zone(e_zone, connection)
    # Now we'll fill in the nameservers.
    e_delegation_set = root.find('./{*}DelegationSet')
    # Modifies the HostedZone in place.
    parse_delegation_set(hosted_zone, e_delegation_set)
    return hosted_zone


# -*- coding: utf-8 -*-

# Copyright 2006 Joe Wreschnig
#           2014 Ben Ockmore
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.

"""Read and write MPEG-4 audio files with iTunes metadata.

This module will read MPEG-4 audio information and metadata,
as found in Apple's MP4 (aka M4A, M4B, M4P) files.

There is no official specification for this format. The source code
for TagLib, FAAD, and various MPEG specifications at

* http://developer.apple.com/documentation/QuickTime/QTFF/
* http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt
* http://standards.iso.org/ittf/PubliclyAvailableStandards/\
c041828_ISO_IEC_14496-12_2005(E).zip
* http://wiki.multimedia.cx/index.php?title=Apple_QuickTime

were all consulted.
"""

import struct
import sys

from mutagen import FileType, Metadata, StreamInfo
from mutagen._constants import GENRES
from mutagen._util import cdata, insert_bytes, delete_bytes, DictProxy, utf8
from mutagen._compat import reraise, PY2, string_types, text_type, chr_, iteritems

class error(IOError):
    pass

class MP4MetadataError(error):
    pass

class MP4StreamInfoError(error):
    pass

class MP4MetadataValueError(ValueError, MP4MetadataError):
    pass

# This is not an exhaustive list of container atoms, but just the
# ones this module needs to peek inside.
_CONTAINERS = [b"moov", b"udta", b"trak", b"mdia", b"meta", b"ilst",
               b"stbl", b"minf", b"moof", b"traf"]
_SKIP_SIZE = {b"meta": 4}

__all__ = ['MP4', 'Open', 'delete', 'MP4Cover', 'MP4FreeForm']

class MP4Cover(bytes):
    """A cover artwork.

    Attributes:

    * imageformat -- format of the image (either FORMAT_JPEG or FORMAT_PNG)
    """
    FORMAT_JPEG = 0x0D
    FORMAT_PNG = 0x0E

    def __new__(cls, data, *args, **kwargs):
        return bytes.__new__(cls, data)

    def __init__(self, data, imageformat=FORMAT_JPEG):
        self.imageformat = imageformat
        try:
            self.format
        except AttributeError:
            self.format = imageformat

class MP4FreeForm(bytes):
    """A freeform value.

    Attributes:

    * dataformat -- format of the data (either FORMAT_TEXT or FORMAT_DATA)
    """

    FORMAT_DATA = 0x0
    FORMAT_TEXT = 0x1

    def __new__(cls, data, *args, **kwargs):
        return bytes.__new__(cls, data)

    def __init__(self, data, dataformat=FORMAT_TEXT):
        self.dataformat = dataformat

class Atom(object):
    """An individual atom.

    Attributes:

    * children -- list child atoms (or None for non-container atoms)
    * length -- length of this atom, including length and name
    * name -- four byte name of the atom, as bytes
    * offset -- location in the constructor-given fileobj of this atom

    This structure should only be used internally by Mutagen.
    """

    children = None

    def __init__(self, fileobj, level=0):
        self.offset = fileobj.tell()
        self.length, self.name = struct.unpack(">I4s", fileobj.read(8))
        if self.length == 1:
            self.length, = struct.unpack(">Q", fileobj.read(8))
            if self.length < 16:
                raise MP4MetadataError(
                    "64 bit atom length can only be 16 and higher")
        elif self.length == 0:
            if level != 0:
                raise MP4MetadataError(
                    "only a top-level atom can have zero length")
            # Only the last atom is supposed to have a zero-length, meaning it
            # extends to the end of file.
            fileobj.seek(0, 2)
            self.length = fileobj.tell() - self.offset
            fileobj.seek(self.offset + 8, 0)
        elif self.length < 8:
            raise MP4MetadataError(
                "atom length can only be 0, 1 or 8 and higher")

        if self.name in _CONTAINERS:
            self.children = []
            fileobj.seek(_SKIP_SIZE.get(self.name, 0), 1)
            while fileobj.tell() < self.offset + self.length:
                self.children.append(Atom(fileobj, level + 1))
        else:
            fileobj.seek(self.offset + self.length, 0)

    # Takes two bytes arguments
    @staticmethod
    def render(name, data):
        """Render raw atom data."""
        # this raises OverflowError if Py_ssize_t can't handle the atom data
        size = len(data) + 8
        if size <= 0xFFFFFFFF:
            return struct.pack(">I4s", size, name) + data
        else:
            return struct.pack(">I4sQ", 1, name, size + 8) + data

    def findall(self, name, recursive=False):
        """Recursively find all child atoms by specified name."""
        if self.children is not None:
            for child in self.children:
                if child.name == name:
                    yield child
                if recursive:
                    for atom in child.findall(name, True):
                        yield atom

    def __getitem__(self, remaining):
        """Look up a child atom, potentially recursively.

        e.g. atom['udta', 'meta'] => <Atom name='meta' ...>
        """
        if not remaining:
            return self
        elif self.children is None:
            raise KeyError("%r is not a container" % self.name)
        for child in self.children:
            if child.name == remaining[0]:
                return child[remaining[1:]]
        else:
            raise KeyError("%r not found" % remaining[0])

    def __repr__(self):
        cls = self.__class__.__name__
        if self.children is None:
            return "<%s name=%r length=%r offset=%r>" % (
                cls, self.name, self.length, self.offset)
        else:
            children = "\n".join([" " + line for child in self.children
                                  for line in repr(child).splitlines()])
            return "<%s name=%r length=%r offset=%r\n%s>" % (
                cls, self.name, self.length, self.offset, children)

class Atoms(object):
    """Root atoms in a given file.

    Attributes:

    * atoms -- a list of top-level atoms as Atom objects

    This structure should only be used internally by Mutagen.
    """

    def __init__(self, fileobj):
        self.atoms = []
        fileobj.seek(0, 2)
        end = fileobj.tell()
        fileobj.seek(0)
        while fileobj.tell() + 8 <= end:
            self.atoms.append(Atom(fileobj))

    def path(self, *names):
        """Look up and return the complete path of an atom.

        For example, atoms.path('moov', 'udta', 'meta') will return a
        list of three atoms, corresponding to the moov, udta, and meta
        atoms.
        """

        path = [self]
        for name in names:
            path.append(path[-1][name, ])
        return path[1:]

    def __contains__(self, names):
        try:
            self[names]
        except KeyError:
            return False
        return True

    def __getitem__(self, names):
        """Look up a child atom.

        'names' may be a list of atoms (['moov', 'udta']) or a string
        specifying the complete path ('moov.udta').
        """
        if isinstance(names, bytes):
            names = names.split(b".")
        for child in self.atoms:
            if child.name == names[0]:
                return child[names[1:]]
        else:
            raise KeyError("%s not found" % names[0])

    def __repr__(self):
        return '\n'.join(repr(child) for child in self.atoms)

class MP4Tags(DictProxy, Metadata):
    r"""Dictionary containing Apple iTunes metadata list key/values.

    Keys are four byte identifiers, except for freeform ('----')
    keys. Values are usually unicode strings, but some atoms have a
    special structure:

    Text values (multiple values per key are supported):

    * '\\xa9nam' -- track title
    * '\\xa9alb' -- album
    * '\\xa9ART' -- artist
    * 'aART' -- album artist
    * '\\xa9wrt' -- composer
    * '\\xa9day' -- year
    * '\\xa9cmt' -- comment
    * 'desc' -- description (usually used in podcasts)
    * 'purd' -- purchase date
    * '\\xa9grp' -- grouping
    * '\\xa9gen' -- genre
    * '\\xa9lyr' -- lyrics
    * 'purl' -- podcast URL
    * 'egid' -- podcast episode GUID
    * 'catg' -- podcast category
    * 'keyw' -- podcast keywords
    * '\\xa9too' -- encoded by
    * 'cprt' -- copyright
    * 'soal' -- album sort order
    * 'soaa' -- album artist sort order
    * 'soar' -- artist sort order
    * 'sonm' -- title sort order
    * 'soco' -- composer sort order
    * 'sosn' -- show sort order
    * 'tvsh' -- show name

    Boolean values:

    * 'cpil' -- part of a compilation
    * 'pgap' -- part of a gapless album
    * 'pcst' -- podcast (iTunes reads this only on import)

    Tuples of ints (multiple values per key are supported):

    * 'trkn' -- track number, total tracks
    * 'disk' -- disc number, total discs

    Others:

    * 'tmpo' -- tempo/BPM, 16 bit int
    * 'covr' -- cover artwork, list of MP4Cover objects (which are
      tagged strs)
    * 'gnre' -- ID3v1 genre. Not supported, use '\\xa9gen' instead.

    The freeform '----' frames use a key in the format '----:mean:name'
    where 'mean' is usually 'com.apple.iTunes' and 'name' is a unique
    identifier for this frame. The value is a str, but is probably
    text that can be decoded as UTF-8. Multiple values per key are
    supported.

    MP4 tag data cannot exist outside of the structure of an MP4 file,
    so this class should not be manually instantiated.

    Unknown non-text tags are removed.
    """

    def load(self, atoms, fileobj):
        try:
            ilst = atoms[b"moov.udta.meta.ilst"]
        except KeyError as key:
            raise MP4MetadataError(key)
        for atom in ilst.children:
            fileobj.seek(atom.offset + 8)
            data = fileobj.read(atom.length - 8)
            if len(data) != (atom.length - 8):
                raise MP4MetadataError("Not enough data")

            if atom.name in self.__atoms:
                info = self.__atoms[atom.name]
                info[0](self, atom, data, *info[2:])
            else:
                # unknown atom, try as text and skip if it fails
                # FIXME: keep them somehow
                try:
                    self.__parse_text(atom, data)
                except MP4MetadataError:
                    continue

    @classmethod
    def _can_load(cls, atoms):
        return b"moov.udta.meta.ilst" in atoms

    @staticmethod
    def __get_sort_stats(item):
        (key, v) = item
        # iTunes always writes the tags in order of "relevance", try
        # to copy it as closely as possible.
        order = [b"\xa9nam", b"\xa9ART", b"\xa9wrt", b"\xa9alb",
                 b"\xa9gen", b"gnre", b"trkn", b"disk",
                 b"\xa9day", b"cpil", b"pgap", b"pcst", b"tmpo",
                 b"\xa9too", b"----", b"covr", b"\xa9lyr"]
        order = dict(zip(order, range(len(order))))
        last = len(order)
        # If there's no key-based way to distinguish, order by length.
        # If there's still no way, go by string comparison on the
        # values, so we at least have something determinstic.

        try:
            length = len(v)
        except TypeError:
            length = 0

        return (order.get(key[:4], last), length, v)

    def save(self, filename):
        """Save the metadata to the given filename."""

        values = []
        items = sorted(self.items(), key=MP4Tags.__get_sort_stats )
        for key, value in items:
            info = self.__atoms.get(key[:4], (None, type(self).__render_text))
            try:
                values.append(info[1](self, key, value, *info[2:]))
            except (TypeError, ValueError) as s:
                reraise(MP4MetadataValueError, s, sys.exc_info()[2])
        data = Atom.render(b"ilst", b"".join(values))

        # Find the old atoms.
        fileobj = open(filename, "rb+")
        try:
            atoms = Atoms(fileobj)
            try:
                path = atoms.path(b"moov", b"udta", b"meta", b"ilst")
            except KeyError:
                self.__save_new(fileobj, atoms, data)
            else:
                self.__save_existing(fileobj, atoms, path, data)
        finally:
            fileobj.close()

    def __pad_ilst(self, data, length=None):
        if length is None:
            length = ((len(data) + 1023) & ~1023) - len(data)
        return Atom.render(b"free", b"\x00" * length)

    def __save_new(self, fileobj, atoms, ilst):
        hdlr = Atom.render(b"hdlr", b"\x00" * 8 + b"mdirappl" + b"\x00" * 9)
        meta = Atom.render(
            b"meta", b"\x00\x00\x00\x00" + hdlr + ilst + self.__pad_ilst(ilst))
        try:
            path = atoms.path(b"moov", b"udta")
        except KeyError:
            # moov.udta not found -- create one
            path = atoms.path(b"moov")
            meta = Atom.render(b"udta", meta)
        offset = path[-1].offset + 8
        insert_bytes(fileobj, len(meta), offset)
        fileobj.seek(offset)
        fileobj.write(meta)
        self.__update_parents(fileobj, path, len(meta))
        self.__update_offsets(fileobj, atoms, len(meta), offset)

    def __save_existing(self, fileobj, atoms, path, data):
        # Replace the old ilst atom.
        ilst = path.pop()
        offset = ilst.offset
        length = ilst.length

        # Check for padding "free" atoms
        meta = path[-1]
        index = meta.children.index(ilst)
        try:
            prev = meta.children[index-1]
            if prev.name == b"free":
                offset = prev.offset
                length += prev.length
        except IndexError:
            pass
        try:
            next = meta.children[index+1]
            if next.name == b"free":
                length += next.length
        except IndexError:
            pass

        delta = len(data) - length
        if delta > 0 or (delta < 0 and delta > -8):
            data += self.__pad_ilst(data)
            delta = len(data) - length
            insert_bytes(fileobj, delta, offset)
        elif delta < 0:
            data += self.__pad_ilst(data, -delta - 8)
            delta = 0

        fileobj.seek(offset)
        fileobj.write(data)
        self.__update_parents(fileobj, path, delta)
        self.__update_offsets(fileobj, atoms, delta, offset)

    def __update_parents(self, fileobj, path, delta):
        """Update all parent atoms with the new size."""
        for atom in path:
            fileobj.seek(atom.offset)
            size = cdata.uint_be(fileobj.read(4))
            if size == 1:  # 64bit
                # skip name (4B) and read size (8B)
                size = cdata.ulonglong_be(fileobj.read(12)[4:])
                fileobj.seek(atom.offset + 8)
                fileobj.write(cdata.to_ulonglong_be(size + delta))
            else:  # 32bit
                fileobj.seek(atom.offset)
                fileobj.write(cdata.to_uint_be(size + delta))

    def __update_offset_table(self, fileobj, fmt, atom, delta, offset):
        """Update offset table in the specified atom."""
        if atom.offset > offset:
            atom.offset += delta
        fileobj.seek(atom.offset + 12)
        data = fileobj.read(atom.length - 12)
        fmt = fmt % cdata.uint_be(data[:4])
        offsets = struct.unpack(fmt, data[4:])
        offsets = [o + (0, delta)[offset < o] for o in offsets]
        fileobj.seek(atom.offset + 16)
        fileobj.write(struct.pack(fmt, *offsets))

    def __update_tfhd(self, fileobj, atom, delta, offset):
        if atom.offset > offset:
            atom.offset += delta
        fileobj.seek(atom.offset + 9)
        data = fileobj.read(atom.length - 9)
        flags = cdata.uint_be(b"\x00" + data[:3])
        if flags & 1:
            o = cdata.ulonglong_be(data[7:15])
            if o > offset:
                o += delta
            fileobj.seek(atom.offset + 16)
            fileobj.write(cdata.to_ulonglong_be(o))

    def __update_offsets(self, fileobj, atoms, delta, offset):
        """Update offset tables in all 'stco' and 'co64' atoms."""
        if delta == 0:
            return
        moov = atoms[b"moov"]
        for atom in moov.findall(b'stco', True):
            self.__update_offset_table(fileobj, ">%dI", atom, delta, offset)
        for atom in moov.findall(b'co64', True):
            self.__update_offset_table(fileobj, ">%dQ", atom, delta, offset)
        try:
            for atom in atoms[b"moof"].findall(b'tfhd', True):
                self.__update_tfhd(fileobj, atom, delta, offset)
        except KeyError:
            pass

    def __parse_data(self, atom, data):
        pos = 0
        while pos < atom.length - 8:
            length, name, flags = struct.unpack(">I4sI", data[pos:pos+12])
            if name != b"data":
                raise MP4MetadataError(
                    "unexpected atom %r inside %r" % (name, atom.name))
            yield flags, data[pos+16:pos+length]
            pos += length

    def __render_data(self, key, flags, value):
        return Atom.render(key, b''.join(
            Atom.render(b"data", struct.pack(">2I", flags, 0) + data)
            for data in value))

    def __parse_freeform(self, atom, data):
        length = cdata.uint_be(data[:4])
        mean = data[12:length]
        pos = length
        length = cdata.uint_be(data[pos:pos+4])
        name = data[pos+12:pos+length]
        pos += length
        value = []
        while pos < atom.length - 8:
            length, atom_name = struct.unpack(">I4s", data[pos:pos+8])
            if atom_name != b"data":
                raise MP4MetadataError(
                    "unexpected atom %r inside %r" % (atom_name, atom.name))

            version = ord(data[pos+8:pos+8+1])
            if version != 0:
                raise MP4MetadataError("Unsupported version: %r" % version)

            flags = struct.unpack(">I", b"\x00" + data[pos+9:pos+12])[0]
            value.append(MP4FreeForm(data[pos+16:pos+length],
                                     dataformat=flags))
            pos += length
        if value:
            self[atom.name + b":" + mean + b":" + name] = value

    def __render_freeform(self, key, value):
        mean, name = key.split(b":", 2)[1:]
        mean = struct.pack(">I4sI", len(mean) + 12, b"mean", 0) + mean
        name = struct.pack(">I4sI", len(name) + 12, b"name", 0) + name
        if isinstance(value, bytes):
            value = [value]
        data = b""
        for v in value:
            flags = MP4FreeForm.FORMAT_TEXT
            if isinstance(v, MP4FreeForm):
                flags = v.dataformat
            data += struct.pack(">I4s2I", len(v) + 16, b"data", flags, 0)
            data += v
        return Atom.render(b"----", mean + name + data)

    def __parse_pair(self, atom, data):
        self[atom.name] = [struct.unpack(">2H", d[2:6]) for
                           flags, d in self.__parse_data(atom, data)]

    def __render_pair(self, key, value):
        data = []
        for (track, total) in value:
            if 0 <= track < 1 << 16 and 0 <= total < 1 << 16:
                data.append(struct.pack(">4H", 0, track, total, 0))
            else:
                raise MP4MetadataValueError(
                    "invalid numeric pair %r" % ((track, total),))
        return self.__render_data(key, 0, data)

    def __render_pair_no_trailing(self, key, value):
        data = []
        for (track, total) in value:
            if 0 <= track < 1 << 16 and 0 <= total < 1 << 16:
                data.append(struct.pack(">3H", 0, track, total))
            else:
                raise MP4MetadataValueError(
                    "invalid numeric pair %r" % ((track, total),))
        return self.__render_data(key, 0, data)

    def __parse_genre(self, atom, data):
        # Translate to a freeform genre.
        genre = cdata.short_be(data[16:18])
        if b"\xa9gen" not in self:
            try:
                self[b"\xa9gen"] = [GENRES[genre - 1]]
            except IndexError:
                pass

    def __parse_tempo(self, atom, data):
        self[atom.name] = [cdata.ushort_be(value[1]) for
                           value in self.__parse_data(atom, data)]

    def __render_tempo(self, key, value):
        try:
            if not isinstance(value, list):
                raise TypeError

            if len(value) == 0:
                return self.__render_data(key, 0x15, b"")

            if min(value) < 0 or max(value) >= 2**16:
                raise MP4MetadataValueError(
                    "invalid 16 bit integers: %r" % value)
        except TypeError:
            raise MP4MetadataValueError(
                "tmpo must be a list of 16 bit integers")

        values = [cdata.to_ushort_be(v) for v in value]
        return self.__render_data(key, 0x15, values)

    def __parse_bool(self, atom, data):
        try:
            self[atom.name] = bool(ord(data[16:17]))
        except TypeError:
            self[atom.name] = False

    def __render_bool(self, key, value):
        return self.__render_data(key, 0x15, [chr_(bool(value))])

    def __parse_cover(self, atom, data):
        self[atom.name] = []
        pos = 0
        while pos < atom.length - 8:
            length, name, imageformat = struct.unpack(">I4sI",
                                                      data[pos:pos+12])
            if name != b"data":
                if name == b"name":
                    pos += length
                    continue
                raise MP4MetadataError(
                    "unexpected atom %r inside 'covr'" % name)
            if imageformat not in (MP4Cover.FORMAT_JPEG, MP4Cover.FORMAT_PNG):
                imageformat = MP4Cover.FORMAT_JPEG
            cover = MP4Cover(data[pos+16:pos+length], imageformat)
            self[atom.name].append(cover)
            pos += length

    def __render_cover(self, key, value):
        atom_data = []
        for cover in value:
            try:
                imageformat = cover.imageformat
            except AttributeError:
                imageformat = MP4Cover.FORMAT_JPEG
            atom_data.append(Atom.render(
                b"data", struct.pack(">2I", imageformat, 0) + cover))
        return Atom.render(key, b"".join(atom_data))

    def __parse_text(self, atom, data, expected_flags=1):
        value = [text.decode('utf-8', 'replace') for flags, text
                 in self.__parse_data(atom, data)
                 if flags == expected_flags]
        if value:
            self[atom.name] = value

    def __render_text(self, key, value, flags=1):
        if isinstance(value, (text_type, bytes)):
            value = [value]
        return self.__render_data(
            key, flags, [utf8(v) for v in value])

    def delete(self, filename):
        """Remove the metadata from the given filename."""

        self.clear()
        self.save(filename)

    __atoms = {
        b"----": (__parse_freeform, __render_freeform),
        b"trkn": (__parse_pair, __render_pair),
        b"disk": (__parse_pair, __render_pair_no_trailing),
        b"gnre": (__parse_genre, None),
        b"tmpo": (__parse_tempo, __render_tempo),
        b"cpil": (__parse_bool, __render_bool),
        b"pgap": (__parse_bool, __render_bool),
        b"pcst": (__parse_bool, __render_bool),
        b"covr": (__parse_cover, __render_cover),
        b"purl": (__parse_text, __render_text, 0),
        b"egid": (__parse_text, __render_text, 0),
    }

    # the text atoms we know about which should make loading fail if parsing
    # any of them fails
    for name in [b"\xa9nam", b"\xa9alb", b"\xa9ART", b"aART", b"\xa9wrt", b"\xa9day",
                 b"\xa9cmt", b"desc", b"purd", b"\xa9grp", b"\xa9gen", b"\xa9lyr",
                 b"catg", b"keyw", b"\xa9too", b"cprt", b"soal", b"soaa", b"soar",
                 b"sonm", b"soco", b"sosn", b"tvsh"]:
        __atoms[name] = (__parse_text, __render_text)

    def pprint(self):
        values = []
        for key, value in iteritems(self):
            if key == b"covr":
                values.append("%r=%s" % (key, ", ".join(
                    [("[%d bytes of data]" % len(data)) for data in value])))
            elif isinstance(value, list):
                values.append("%r=%s" % (key, " / ".join(map(text_type, value))))
            else:
                values.append("%r=%s" % (key, value))
        return "\n".join(values)

class MP4Info(StreamInfo):
    """MPEG-4 stream information.

    Attributes:

    * bitrate -- bitrate in bits per second, as an int
    * length -- file length in seconds, as a float
    * channels -- number of audio channels
    * sample_rate -- audio sampling rate in Hz
    * bits_per_sample -- bits per sample
    """

    bitrate = 0
    channels = 0
    sample_rate = 0
    bits_per_sample = 0

    def __init__(self, atoms, fileobj):
        for trak in list(atoms[b"moov"].findall(b"trak")):
            hdlr = trak[b"mdia", b"hdlr"]
            fileobj.seek(hdlr.offset)
            data = fileobj.read(hdlr.length)
            if data[16:20] == b"soun":
                break
        else:
            raise MP4StreamInfoError("track has no audio data")

        mdhd = trak[b"mdia", b"mdhd"]
        fileobj.seek(mdhd.offset)
        data = fileobj.read(mdhd.length)
        if ord(data[8:9]) == 0:
            offset = 20
            fmt = ">2I"
        else:
            offset = 28
            fmt = ">IQ"
        end = offset + struct.calcsize(fmt)
        unit, length = struct.unpack(fmt, data[offset:end])
        self.length = float(length) / unit

        try:
            atom = trak[b"mdia", b"minf", b"stbl", b"stsd"]
            fileobj.seek(atom.offset)
            data = fileobj.read(atom.length)
            if data[20:24] == b"mp4a":
                length = cdata.uint_be(data[16:20])
                (self.channels, self.bits_per_sample, _,
                 self.sample_rate) = struct.unpack(">3HI", data[40:50])
                # ES descriptor type
                if data[56:60] == b"esds" and ord(data[64:65]) == 0x03:
                    pos = 65
                    # skip extended descriptor type tag, length, ES ID
                    # and stream priority
                    if data[pos:pos+3] == b"\x80\x80\x80":
                        pos += 3
                    pos += 4
                    # decoder config descriptor type
                    if ord(data[pos:pos+1]) == 0x04:
                        pos += 1
                        # skip extended descriptor type tag, length,
                        # object type ID, stream type, buffer size
                        # and maximum bitrate
                        if data[pos:pos+3] == b"\x80\x80\x80":
                            pos += 3
                        pos += 10
                        # average bitrate
                        self.bitrate = cdata.uint_be(data[pos:pos+4])
        except (ValueError, KeyError):
            # stsd atoms are optional
            pass

    def pprint(self):
        return "MPEG-4 audio, %.2f seconds, %d bps" % (
            self.length, self.bitrate)

class MP4(FileType):
    """An MPEG-4 audio file, probably containing AAC.

    If more than one track is present in the file, the first is used.
    Only audio ('soun') tracks will be read.

    :ivar info: :class:`MP4Info`
    :ivar tags: :class:`MP4Tags`
    """

    MP4Tags = MP4Tags

    _mimes = ["audio/mp4", "audio/x-m4a", "audio/mpeg4", "audio/aac"]

    def load(self, filename):
        self.filename = filename
        fileobj = open(filename, "rb")
        try:
            atoms = Atoms(fileobj)

            # ftyp is always the first atom in a valid MP4 file
            if not atoms.atoms or atoms.atoms[0].name != b"ftyp":
                raise error("Not a MP4 file")

            try:
                self.info = MP4Info(atoms, fileobj)
            except error:
                raise
            except Exception as err:
                reraise(MP4StreamInfoError, err, sys.exc_info()[2])

            if not MP4Tags._can_load(atoms):
                self.tags = None
            else:
                try:
                    self.tags = self.MP4Tags(atoms, fileobj)
                except error:
                    raise
                except Exception as err:
                    reraise(MP4MetadataError, err, sys.exc_info()[2])
        finally:
            fileobj.close()

    def add_tags(self):
        if self.tags is None:
            self.tags = self.MP4Tags()
        else:
            raise error("an MP4 tag already exists")

    @staticmethod
    def score(filename, fileobj, header_data):
        return (b"ftyp" in header_data) + (b"mp4" in header_data)

Open = MP4

def delete(filename):
    """Remove tags from a file."""

    MP4(filename).delete()


from ctypes import *
import time

import bacon
from bacon.core import lib
from bacon import native
from bacon import commands
from bacon import controller
from bacon import keyboard
from bacon import graphics
from bacon import mouse_input
from bacon import shader
from bacon import window

class Game(object):
    '''Base class for all Bacon games.  An instance of this class is passed to :func:`run`.  Override methods on
    this class to handle game events such as :func:`on_tick`.  A complete example of a game::

        class MyGame(bacon.Game):
            def on_tick(self):
                # Update and draw game here.
                pass

        # Start the game
        bacon.run(MyGame())

    '''

    def on_init(self):
        '''Called once when the game starts.  You can use this to do any initialization that
        requires the graphics device to have been initialized; for example, rendering to a texture.'''
        pass

    def on_tick(self):
        '''Called once per frame to update and render the game.  You may only call
        drawing functions within the scope of this method.'''
        clear(1, 0, 1, 1)

    def on_key(self, key, pressed):
        '''Called when a key on the keyboard is pressed or released.

        :param key: key code, one of :class:`Keys` enumeration
        :param pressed: ``True`` if the key was pressed, otherwise ``False``
        '''
        pass

    def on_mouse_button(self, button, pressed):
        '''Called when a mouse button is pressed or released.

        :param button: button index, of :class:`MouseButton` enumeration
        :param pressed: ``True`` if the button was pressed, otherwise ``False``
        '''
        pass

    def on_mouse_scroll(self, dx, dy):
        '''Called when the mouse scroll wheel is scrolled.  Most mice have a scroll wheel that moves in
        the ``y`` axis only; Apple trackpads and mice support scrolling in ``x`` as well.

        :note: units are aribitrary and not currently consistent across platforms

        :param dx: relative scroll amount along the ``x`` axis
        :param dy: relative scroll amount along the ``y`` axis
        '''
        pass

    def on_resize(self, width, height):
        '''Called when size of the window changes.

        :param width: width of the drawable area of the window, in pixels
        :param height: height of the drawable area of the window, in pixels
        '''
        pass

    def on_controller_connected(self, controller):
        '''Called when a game controller is connected.

        :param controller: the :class:`Controller` that is now available for polling and events
        '''
        pass

    def on_controller_disconnected(self, controller):
        '''Called when a game controller is disconnected.  You should use the `controller` parameter only
        to identify a previously used controller; its properties and values will no longer be available.

        :param controller: the :class:`Controller` that was disconnected
        '''
        pass

    def on_controller_button(self, controller, button, pressed):
        '''Called when a button on a game controller is pressed or released.

        :param controller: the :class:`Controller` containing the button
        :param button: button index, of :class:`ControllerButtons` enumeration
        :param pressed: ``True`` if the button was pressed, otherwise ``False``
        '''
        pass

    def on_controller_axis(self, controller, axis, value):
        '''Called when an axis on a game controller is moved.

        :param controller: the :class:`Controller` containing the axis
        :param button: axis index, of :class:`ControllerAxes` enumeration
        :param value: absolute position of the axis, between ``-1.0`` and ``1.0``
        '''
        pass

if not native._mock_native:
    _time_uniform = shader.ShaderUniform('g_Time', shader.ShaderUniformType.float_)

#: Number of seconds since the last frame.  This is a convenience value for timing animations.
bacon.timestep = 0.0

def _first_tick_callback():
    global _tick_callback_handle
    global _last_frame_time
    global _start_time
    global timestep

    _start_time = time.time()
    _last_frame_time = _start_time
    bacon.timestep = 0.0

    _tick_callback_handle = lib.TickCallback(_tick_callback)
    lib.SetTickCallback(_tick_callback_handle)

    # Exceptions on startup (either during on_init or the first on_tick) stop the
    # game loop immediately, since they're likely to be showstoppers.
    try:
        bacon._current_game.on_init()

        _last_frame_time = time.time() - _start_time
        _tick_callback()
    except:
        _tick_callback_handle = lib.TickCallback(_error_tick_callback)
        lib.SetTickCallback(_tick_callback_handle)
        raise

def _error_tick_callback():
    lib.Stop()

def _tick_callback():
    global _last_frame_time

    now_time = time.time() - _start_time
    bacon.timestep = now_time - _last_frame_time
    _last_frame_time = now_time

    _time_uniform.value = now_time

    graphics._target_stack = [None]
    window._begin_frame()
    mouse_input.mouse._update_position()

    try:
        bacon._current_game.on_tick()
    except:
        _tick_callback_handle = lib.TickCallback(_error_tick_callback)
        lib.SetTickCallback(_tick_callback_handle)
        raise

    window._end_frame()
    commands.flush()

bacon._current_game = None

def run(game):
    '''Start running the game.  The window is created and shown at this point, and then
    the main event loop is entered.  'game.on_tick' and other event handlers are called
    repeatedly until the game exits.

    If a game is already running, this function replaces the :class:`Game` instance that
    receives events.
    '''
    if bacon._current_game:
        bacon._current_game = game
        return

    global _tick_callback_handle
    bacon._current_game = game

    # Window handler
    window_resize_callback_handle = lib.WindowResizeEventHandler(window._window_resize_event_handler)
    lib.SetWindowResizeEventHandler(window_resize_callback_handle)

    # Key handler
    key_callback_handle = lib.KeyEventHandler(keyboard._key_event_handler)
    lib.SetKeyEventHandler(key_callback_handle)

    # Mouse handlers
    mouse_button_callback_handle = lib.MouseButtonEventHandler(mouse_input._mouse_button_event_handler)
    lib.SetMouseButtonEventHandler(mouse_button_callback_handle)
    mouse_scroll_callback_handle = lib.MouseScrollEventHandler(mouse_input._mouse_scroll_event_handler)
    lib.SetMouseScrollEventHandler(mouse_scroll_callback_handle)

    # Controller handlers
    controller_connected_handle = lib.ControllerConnectedEventHandler(controller._controller_connected_event_handler)
    lib.SetControllerConnectedEventHandler(controller_connected_handle)
    controller_button_handle = lib.ControllerButtonEventHandler(controller._controller_button_event_handler)
    lib.SetControllerButtonEventHandler(controller_button_handle)
    controller_axis_handle = lib.ControllerAxisEventHandler(controller._controller_axis_event_handler)
    lib.SetControllerAxisEventHandler(controller_axis_handle)

    # Tick handler
    _tick_callback_handle = lib.TickCallback(_first_tick_callback)
    lib.SetTickCallback(_tick_callback_handle)

    lib.Run()
    bacon._current_game = None
    _tick_callback_handle = None

    lib.SetWindowResizeEventHandler(lib.WindowResizeEventHandler(0))
    lib.SetKeyEventHandler(lib.KeyEventHandler(0))
    lib.SetMouseButtonEventHandler(lib.MouseButtonEventHandler(0))
    lib.SetMouseScrollEventHandler(lib.MouseScrollEventHandler(0))
    lib.SetControllerConnectedEventHandler(lib.ControllerConnectedEventHandler(0))
    lib.SetControllerButtonEventHandler(lib.ControllerButtonEventHandler(0))
    lib.SetControllerAxisEventHandler(lib.ControllerAxisEventHandler(0))
    lib.SetTickCallback(lib.TickCallback(0))

def quit():
    '''Stop the game loop and exit the application before the next :func:`on_tick` is called.
    '''
    lib.Stop()


from ctypes import *

import bacon
from bacon.core import lib
from bacon import native

ControllerProfiles = native.ControllerProfiles
ControllerButtons = native.ControllerButtons
ControllerAxes = native.ControllerAxes

def _get_controller_property_string(controller_index, property):
    buffer = create_string_buffer(256)
    length = c_int(sizeof(buffer))
    try:
        lib.GetControllerPropertyString(controller_index, property, buffer, byref(length))
    except:
        return None
    return buffer.value.decode('utf-8')

def _get_controller_property_int(controller_index, property):
    value = c_int()
    try:
        lib.GetControllerPropertyInt(controller_index, property, byref(value))
    except:
        return None
    return value.value

class Controller(object):
    '''Represents the state of a connected game controller.  ``Controller`` instances are created automatically when
    a physical game controller device is detected, and destroyed when they are disconnected.  To obtain a reference
    to a ``Controller``, override :func:`Game.on_controller_connected`.
    '''

    def __init__(self, controller_index):
        self._controller_index = controller_index

        # Controller state
        self._buttons = 0
        self._axes = {}

        # Controller properties
        self._supported_axes_mask = _get_controller_property_int(controller_index, native.ControllerProperties.supported_axes_mask)
        self._supported_buttons_mask = _get_controller_property_int(controller_index, native.ControllerProperties.supported_buttons_mask)

        for bit in range(16):
            self._axes[1 << bit] = 0.0

        #: A numeric vendor ID that can be used to identify the type of device, when combined with the :attr:`product_id`.
        self.vendor_id = _get_controller_property_int(controller_index, native.ControllerProperties.vendor_id)

        #: A numeric product ID that can be used to identify the type of device, when combined with the :attr:`vendor_id`.
        self.product_id = _get_controller_property_int(controller_index, native.ControllerProperties.product_id)

        #: A human-readable name of the device that can be used to identify it to the user.
        self.name = _get_controller_property_string(controller_index, native.ControllerProperties.name)

        #: The supported profile of the device, a member of the enumeration :class:`GameControllerProfiles`.  Game controllers
        #: with the ``generic`` profile do not provide semantic meanings to any of the buttons or axes, and must usually be configured
        #: by the player unless the device type is known by the game.
        #:
        #: The ``standard`` profile describes a game controller with no analogue sticks or triggers.
        #:
        #: The ``extended`` profile describes a game controller with a layout compatible with an Xbox 360 controller.
        #:
        #: For a game controller to appear in the ``standard`` or ``extended`` profiles, it must have been discovered by a
        #: suitable SDK on the host platform: on Windows, this is XInput, which only supports the Xbox 360 controller.  On
        #: OS X and iOS, this is the GameController SDK, which supports a limited range of new devices.
        self.profile = _get_controller_property_int(controller_index, native.ControllerProperties.profile)

        self.mapping = ControllerMapping.get(self)
        if self.mapping:
            self.profile = self.mapping.profile

    @property
    def controller_index(self):
        '''The index of the controller, between 0 and 4 (read-only).  Typically this is assigned in the order the
        controllers are detected, however some controllers may have an intrinsic "player number" that is exposed
        through this number.  No two controllers will have the same controller index.
        '''
        return self._controller_index

    def has_axis(self, axis):
        '''Returns ``True`` if the controller has the requested axis, a value of enumeration :class:`ControllerAxes`.
        '''
        return (axis & self._supported_axes_mask) != 0

    def get_axis(self, axis):
        '''Get the absolute value of the requested axis.

        :param axis: An axis, one of the values in :class:`ControllerAxes`.
        :return: The absolute position of the axis, between -1.0 and 0.0.
        '''
        try:
            return self._axes[axis]
        except KeyError:
            return 0.0

    def has_button(self, button):
        '''Returns ``True`` if the controller has the requested button, a value of enumeration :class:`ControllerButtons`.
        '''
        return (button & self._supported_buttons_mask) != 0

    def get_button_state(self, button):
        '''Get the pressed state of the requested button.

        :param button: A button, one of the values in :class:`ControllerButtons`.
        :return: ``True``` if the button is currently pressed, otherwise ``False``.
        '''
        return (button & self._buttons) != 0

    @property
    def connected(self):
        '''``True`` if the controller is still connected; ``False`` if it has been disconnected.

        Once a controller has been disconnected, it is never reconnected (if the same physical device is reconnected, a new
        ``Controller`` instance is created).
        '''
        return self._controller_index is not None

    # Axes

    @property
    def left_thumb_x(self):
        '''The absolute X axis value of the left thumb-stick, or the main stick on a joystick.'''
        return self.get_axis(ControllerAxes.left_thumb_x)

    @property
    def left_thumb_y(self):
        '''The absolute Y axis value of the left thumb-stick, or the main stick on a joystick.'''
        return self.get_axis(ControllerAxes.left_thumb_y)

    @property
    def right_thumb_x(self):
        '''The absolute X axis value of the right thumb-stick, or the twist axis on a joystick.'''
        return self.get_axis(ControllerAxes.right_thumb_x)

    @property
    def right_thumb_y(self):
        '''The absolute Y axis value of the right thumb-stick, or the throttle control on a joystick.'''
        return self.get_axis(ControllerAxes.right_thumb_y)

    @property
    def left_trigger(self):
        '''The absolute left trigger value, between 0.0 and 1.0.  Available only on game controllers with the
        ``extended`` profile.
        '''
        return self.get_axis(ControllerAxes.left_trigger)

    @property
    def right_trigger(self):
        '''The absolute right trigger value, between 0.0 and 1.0.  Available only on game controllers with the
        ``extended`` profile.
        '''
        return self.get_axis(ControllerAxes.right_trigger)

    # Buttons

    @property
    def start(self):
        '''``True`` if the start button is pressed.  Available only on game controllers with the ``standard`` or
        ``extended`` profiles.
        '''
        return self._buttons & ControllerButtons.start

    @property
    def back(self):
        '''``True`` if the back button is pressed.  Available only on the Xbox 360 controller on Windows.'''
        return self._buttons & ControllerButtons.back

    @property
    def select(self):
        '''``True`` if the select button is pressed.'''
        return self._buttons & ControllerButtons.select

    @property
    def action_up(self):
        '''``True`` if the up action button ("Y" on Xbox 360) is pressed.  Available only on game controllers with
        the ``standard`` or ``extended`` profiles.'''
        return self._buttons & ControllerButtons.action_up

    @property
    def action_down(self):
        '''``True`` if the down action button ("A" on Xbox 360) is pressed.  Available only on game controllers with
        the ``standard`` or ``extended`` profiles.'''
        return self._buttons & ControllerButtons.action_down

    @property
    def action_left(self):
        '''``True`` if the left action button ("X" on Xbox 360) is pressed.  Available only on game controllers with
        the ``standard`` or ``extended`` profiles.'''
        return self._buttons & ControllerButtons.action_left

    @property
    def action_right(self):
        '''``True`` if the right action button ("B" on Xbox 360) is pressed.  Available only on game controllers with
        the ``standard`` or ``extended`` profiles.'''
        return self._buttons & ControllerButtons.action_right

    @property
    def dpad_up(self):
        '''``True`` if the up directional pad button is pressed.  The d-pad also corresponds to the POV or hat control
        on a joystick.'''
        return self._buttons & ControllerButtons.dpad_up

    @property
    def dpad_down(self):
        '''``True`` if the down directional pad button is pressed.  The d-pad also corresponds to the POV or hat control
        on a joystick.'''
        return self._buttons & ControllerButtons.dpad_down

    @property
    def dpad_left(self):
        '''``True`` if the left directional pad button is pressed.  The d-pad also corresponds to the POV or hat control
        on a joystick.'''
        return self._buttons & ControllerButtons.dpad_left

    @property
    def dpad_right(self):
        '''``True`` if the right directional pad button is pressed.  The d-pad also corresponds to the POV or hat control
        on a joystick.'''
        return self._buttons & ControllerButtons.dpad_right

    @property
    def left_shoulder(self):
        '''``True`` if the left shoulder button is pressed.  Available only on game controllers with the ``standard`` or
        ``extended`` profiles.
        '''
        return self._buttons & ControllerButtons.left_shoulder

    @property
    def right_shoulder(self):
        '''``True`` if the right shoulder button is pressed.  Available only on game controllers with the ``standard`` or
        ``extended`` profiles.
        '''
        return self._buttons & ControllerButtons.right_shoulder

    @property
    def left_thumb(self):
        '''``True`` if the left thumb stick is depressed.  Available only on game controllers with the
        ``extended`` profile.
        '''
        return self._buttons & ControllerButtons.left_thumb

    @property
    def right_thumb(self):
        '''``True`` if the right thumb stick is depressed.  Available only on game controllers with the
        ``extended`` profile.
        '''
        return self._buttons & ControllerButtons.right_thumb

class ControllerMapping(object):
    '''Map buttons and axes on a device controller to game button and axes.  Typically used to map generic
    inputs to semantic inputs associated with a controller profile.

    :param buttons: dictionary mapping button to button, each key and value is either a string or enum from :class:`GameControllerButtons`
    :param axes: dictionary mapping axis to axis, each key and value is either a string or enum from :class:`GameControllerAxes`
    :param dead_zones: dictionary mapping axis to its dead zone.  Axis input is rescaled to zero out +/- the dead zone.
    :param profile: the mapped profile, a string or enum from :class:`GameControllerProfiles`
    '''

    # Registry of (vendor_id, product_id) tuples to ControllerMapping
    _registry = {}

    @classmethod
    def register(cls, vendor_id, product_id, mapping):
        '''Register a mapping for controllers with the given vendor and product IDs.  The mapping will
        replace any existing mapping for these IDs for controllers not yet connected.

        :param vendor_id: the vendor ID of the controller, as reported by :attr:`Controller.vendor_id`
        :param product_id: the vendor ID of the controller, as reported by :attr:`Controller.product_id`
        :param mapping: a :class:`ControllerMapping` to apply
        '''
        cls._registry[(vendor_id, product_id)] = mapping

    @classmethod
    def get(cls, controller):
        '''Find a mapping that can apply to the given controller.  Returns None if unsuccessful.

        :param controller: :class:`Controller` to look up
        :return: :class:`ControllerMapping`
        '''
        try:
            return cls._registry[(controller.vendor_id, controller.product_id)]
        except KeyError:
            return None

    def __init__(self, buttons={}, axes={}, dead_zones={}, profile=ControllerProfiles.generic):
        self.buttons = {}
        for k, v in buttons.items():
            k = ControllerButtons.parse(k)
            v = ControllerButtons.parse(v)
            self.buttons[k] = v
        self.axes = {}
        for k, v in axes.items():
            k = ControllerAxes.parse(k)
            v = ControllerAxes.parse(v)
            self.axes[k] = v
        self.dead_zones = {}
        for k, v in dead_zones.items():
            k = ControllerAxes.parse(k)
            self.dead_zones[k] = float(v)
        self.profile = ControllerProfiles.parse(profile)

# Map controller index to controller
_controllers = {}

def _controller_connected_event_handler(controller_index, connected):
    # Invalidate any existing controller with the same index
    controller = None
    try:
        controller = _controllers[controller_index]
        controller._controller_index = None
    except KeyError:
        pass

    if connected:
        _controllers[controller_index] = controller = Controller(controller_index)
        bacon._current_game.on_controller_connected(controller)
    else:
        del _controllers[controller_index]
        bacon._current_game.on_controller_disconnected(controller)

def _controller_button_event_handler(controller_index, button, pressed):
    try:
        controller = _controllers[controller_index]
    except KeyError:
        return

    if controller.mapping:
        try:
            button = controller.mapping.buttons[button]
        except KeyError:
            pass

    if pressed:
        controller._buttons |= button
    else:
        controller._buttons &= ~button

    bacon._current_game.on_controller_button(controller, button, pressed)

def _controller_axis_event_handler(controller_index, axis, value):
    try:
        controller = _controllers[controller_index]
    except KeyError:
        return

    if controller.mapping:
        try:
            axis = controller.mapping.axes[axis]
        except KeyError:
            pass

        try:
            dead_zone = controller.mapping.dead_zones[axis]
            if value < -dead_zone:
                value = (value + dead_zone) / (1.0 - dead_zone)
            elif value > dead_zone:
                value = (value - dead_zone) / (1.0 - dead_zone)
            else:
                value = 0.0

        except KeyError:
            pass

    if controller._axes[axis] != value:
        controller._axes[axis] = value
        bacon._current_game.on_controller_axis(controller, axis, value)