<commit_before>from __future__ import division, print_function, unicode_literals

import subprocess, os, pickle, time, argparse
import robofab.world, WriteFeaturesKernFDK, WriteFeaturesMarkFDK
import hindkit as kit

class Builder(object):

    def __init__(
        self,
        family,
        fontrevision = '1.000',
        devanagari_offset_matrix = ((0, 0), (0, 0)),
    ):

        self.family = family
        self.fontrevision = fontrevision
        self.devanagari_offset_matrix = devanagari_offset_matrix

        for module in [
            'kerning',
            'mark_positioning',
            'mark_to_mark_positioning',
            'devanagari_matra_i_variants',
        ]:
            if self.family.__dict__['has_' + module]:
                self.__dict__['enable_' + module] = True
            else:
                self.__dict__['enable_' + module] = False

        self.prepare_styles = False
        self.prepare_features = False
        self.compile = False

        self.makeinstances = False
        self.checkoutlines = False
        self.autohint = False

        self.do_style_linking = False
        self.use_os_2_version_4 = False
        self.prefer_typo_metrics = False
        self.is_width_weight_slope_only = False

        self.parser = argparse.ArgumentParser()
        options = self.parser.add_argument_group(
            title = 'build options',
            description = 'execute `python build.py` to run stages as specified in build.py, or append arguments to override.'
        )
        options.add_argument(
            '-s', '--stages', action = 'store',
            help = '"1" for "prepare_styles", "2" for "prepare_features", and "3" for "compile".'
        )
        options.add_argument(
            '-o', '--options', action = 'store',
            help = '"0" for none, "1" for "makeinstances", "2" for "checkoutlines", and "3" for "autohint".'
        )

    def _parse_args(self):
        args = self.parser.parse_args()
        if args.stages:
            stages = str(args.stages)
            self.prepare_styles = True if '1' in stages else False
            self.prepare_features = True if '2' in stages else False
            self.compile = True if '3' in stages else False
        if args.options:
            options = str(args.options)
            self.makeinstances = True if '1' in options else False
            self.checkoutlines = True if '2' in options else False
            self.autohint = True if '3' in options else False

    def _check_master_files(self):
        is_missing = False
        message_lines = ['[WARNING] UFO master files missing:']
        for master in self.family.masters:
            if not os.path.exists(master.path):
                is_missing = True
                message_lines.append('\t' + master.path)
        if is_missing:
            message_lines.extend(['', '[Note] Exit.', ''])
            raise SystemExit('\n'.join(message_lines))

    def set_options(self, options = []):

        for supported_option in [

            'prepare_styles',
            'prepare_features',
            'compile',

            'makeinstances',
            'checkoutlines',
            'autohint',

            'do_style_linking',
            'use_os_2_version_4',
            'prefer_typo_metrics',
            'is_width_weight_slope_only',

        ]:
            if supported_option in options:
                self.__dict__[supported_option] = True

    def generate_designspace(self):

        process = subprocess.Popen(
            ['AFDKOPython', 'AFDKOPython/generate_designspace.py'],
            stdin = subprocess.PIPE,
            cwd = kit.__path__[0],
        )
        process.communicate(pickle.dumps(self.family.dump()))

    def generate_fmndb(self):

        lines = []
        for style in self.family.styles:
            lines.append('')
            lines.append('[{}]'.format(style.output_full_name_postscript))
            lines.append('  f = {}'.format(self.family.output_name))
            lines.append('  s = {}'.format(style.name))

            if style.name != 'Regular':
                if self.do_style_linking and (style.is_bold or style.is_italic):
                    if style.is_bold:
                        lines.append('  # IsBoldStyle')
                    if style.is_italic:
                        lines.append('  # IsItalicStyle')
                else:
                    lines.append('  l = {}'.format(style.output_full_name))

        with open(kit.paths.FMNDN, 'w') as f:
            f.write(kit.templates.FMNDB_HEAD)
            f.write('\n'.join(lines))
            f.write('\n')

    def build(self, additional_arguments = []):

        self._parse_args()

        print()
        print('[Note] {} Building...\n'.format(time.strftime('%H:%M:%S')))

        if self.makeinstances:
            self.enabled_styles = self.family.styles
        else:
            if len(self.family.masters) == 1 and len(self.family.styles) == 1:
                self.enabled_styles = self.family.styles
            else:
                self.enabled_styles = [self.family.styles[0], self.family.styles[-1]]

        if self.family.script in ['Devanagari', 'Gujarati']:
            from hindkit.scripts import devanagari
            devanagari.SCRIPT_PREFIX = kit.linguistics.INDIC_SCRIPTS[self.family.script.lower()]['abbreviation']

        if self.prepare_styles:

            self._check_master_files()

            if self.enable_mark_positioning:

                print('[Note] Generating the glyph class for combining marks...\n')

                glyph_classes = []

                glyph_classes.extend([('generated_MARKS', glyph_filter_marks)])

                if self.enable_devanagari_matra_i_variants:
                    print('[Note] Generating glyph classes for mI and mII matching...\n')
                    glyph_classes.extend([
                        ('generated_MATRA_I_ALTS', devanagari.glyph_filter_matra_i_alts),
                        ('generated_BASES_FOR_MATRA_I', devanagari.glyph_filter_bases_for_matra_i),
                        ('generated_BASES_FOR_WIDE_MATRA_II', devanagari.glyph_filter_bases_for_wide_matra_ii),
                    ])

                generate_glyph_classes(
                    self.family,
                    self.family.masters[0].open_font(),
                    glyph_classes,
                    output_path = 'features/GENERATED_classes.fea'
                )

            print('[Note] Resetting instance directories...\n')
            subprocess.call(['rm', '-fr', kit.paths.INSTANCES])
            subprocess.call(['mkdir', kit.paths.INSTANCES])
            for style in self.enabled_styles:
                subprocess.call(['mkdir', style.directory])

            if self.makeinstances:

                print('[Note] Start interpolating masters...\n')

                # Prepare makeInstancesUFO arguments

                arguments = ['-d', 'font.designspace']

                if not self.checkoutlines:
                    arguments.append('-c')
                if not self.autohint:
                    arguments.append('-a')

                # Run makeInstancesUFO

                subprocess.call(['makeInstancesUFO'] + arguments)

                # Remove the log file

                subprocess.call(['rm', '-f', 'mutatorMath.log'])

                print()
                print('[Note] Done interpolating masters.\n')

            else:

                print('[Note] Copying masters to be instances.\n')

                for index, (master, style) in enumerate(zip(self.family.masters, self.enabled_styles)):

                    subprocess.call(['cp', '-fr', master.path, style.path])

                    font = style.open_font()
                    font.info.postscriptFontName = style.output_full_name_postscript

                    if index != 0:
                        font.groups.update(self.family.masters[0].open_font().groups)
                    font.save()

                    if self.checkoutlines:
                        subprocess.call(['checkOutlinesUFO', '-e', '-all', style.path])

                    if self.autohint:
                        subprocess.call(['autohint', '-q','-nb', style.path])

        if self.prepare_features and (self.enable_kerning or self.enable_mark_positioning):

            self._check_master_files()

            for style in self.enabled_styles:

                print('[Note] Generating features for "{}":\n'.format(style.name))

                if self.enable_kerning:
                    WriteFeaturesKernFDK.KernDataClass(
                        font = style.open_font(),
                        folderPath = style.directory,
                        minKern = 3,
                        writeTrimmed = False,
                        writeSubtables = True,
                        fileName = 'kern.fea',
                    )

                if self.enable_mark_positioning:

                    WriteFeaturesMarkFDK.kCombMarksClassName = 'generated_MARKS'

                    WriteFeaturesMarkFDK.MarkDataClass(
                        font = style.open_font(),
                        folderPath = style.directory,
                        trimCasingTags = False,
                        genMkmkFeature = self.enable_mark_to_mark_positioning,
                        writeClassesFile = True,
                        indianScriptsFormat = (
                            True if self.family.script.lower() in kit.linguistics.INDIC_SCRIPTS
                            else False
                        ),
                    )

                    if self.enable_devanagari_matra_i_variants:

                        print()
                        print('\t[Note] mI matching...')

                        light, bold = self.devanagari_offset_matrix
                        light_min, light_max = light
                        bold_min, bold_max = bold

                        ratio = style.interpolation_value / self.family.masters[-1].interpolation_value

                        offset_tuple = (
                            light_min + (bold_min - light_min) * ratio,
                            light_max + (bold_max - light_max) * ratio,
                        )

                        devanagari.match_matra_i_alts(
                            style,
                            offset_range = offset_tuple
                        )

                print()

            print('[Note] Done generating features.\n')

        if self.compile:

            print('[Note] Resetting the build directory...\n')
            subprocess.call(['rm', '-fr', kit.paths.BUILD])
            subprocess.call(['mkdir', kit.paths.BUILD])

            for style in self.enabled_styles:

                print('[Note] Compiling OTFs for "{}":'.format(style.name))

                font = style.open_font()
                if font.info.postscriptFontName != style.output_full_name_postscript:
                    font.info.postscriptFontName = style.output_full_name_postscript
                    print('\n[Note] Fixed the PostScript name.')
                    font.save()

                with open(os.path.join(style.directory, 'features'), 'w') as file:
                    file.write(kit.templates.FEATURES)

                with open(os.path.join(style.directory, 'weightclass.fea'), 'w') as file:
                    file.write(kit.templates.WEIGHTCLASS.format(str(style.weight_class)))

                otf_name = style.output_full_name_postscript + '.otf'
                otf_path = os.path.join(kit.paths.BUILD, otf_name)

                # Prepare makeotf arguments

                arguments = [
                    '-f', style.path,
                    '-o', otf_path,
                    '-mf', kit.paths.FMNDN,
                    '-gf', self.family.goadb_path,
                    '-r',
                    '-shw',
                    '-rev', self.fontrevision
                ]

                # Style linking:

                if self.do_style_linking:
                    if style.is_bold:
                        arguments.append('-b')
                    if style.is_italic:
                        arguments.append('-i')

                # New bits in OS/2.fsSelection

                if self.use_os_2_version_4:
                    for digit, boolean in [
                        ('7', self.prefer_typo_metrics),
                        ('8', self.is_width_weight_slope_only),
                        ('9', style.is_oblique),
                    ]:
                        arguments.append('-osbOn' if boolean else '-osbOff')
                        arguments.append(digit)

                arguments.extend(additional_arguments)

                # Run makeotf

                subprocess.call(['makeotf'] + arguments)

                # Remove the project file

                project_file_path = os.path.join(style.directory, 'current.fpr')
                subprocess.call(['rm', '-f', project_file_path])

                # Copy the compiled font file to Adobe's fonts folder

                if os.path.exists(otf_path) and os.path.exists(kit.paths.OUTPUT):
                    subprocess.call(['cp', '-f', otf_path, kit.paths.OUTPUT])

                print()

            print('[Note] Done compiling OTFs.\n')

        print('[Note] {} Done building.\n'.format(time.strftime('%H:%M:%S')))

def sort_glyphs(glyph_order, glyph_names):
    sorted_glyphs = (
        [i for i in glyph_order if i in glyph_names] +
        [i for i in glyph_names if i not in glyph_order]
    )
    return sorted_glyphs

def compose_glyph_class_def_lines(class_name, glyph_names):
    if glyph_names:
        glyph_class_def_lines = (
            ['@{} = ['.format(class_name)] +
            ['  {}'.format(glyph_name) for glyph_name in glyph_names] +
            ['];', '']
        )
    else:
        glyph_class_def_lines = ['# @{} = [];'.format(class_name), '']
    return glyph_class_def_lines

def glyph_filter_marks(glyph):
    has_mark_anchor = False
    for anchor in glyph.anchors:
        if anchor.name.startswith('_'):
            has_mark_anchor = True
            break
    return has_mark_anchor

def generate_glyph_classes(family, font, glyph_classes, output_path = None):
    glyph_order = [
        development_name for
        production_name, development_name, unicode_mapping in
        family.goadb
    ]
    output_lines = []
    for class_name, filter_function in glyph_classes:
        glyph_names = [glyph.name for glyph in filter(filter_function, font)]
        glyph_names = sort_glyphs(glyph_order, glyph_names)
        font.groups.update({class_name: glyph_names})
        if output_path:
            output_lines.extend(
                compose_glyph_class_def_lines(class_name, glyph_names)
            )
    font.save()
    if output_path:
        with open(output_path, 'w') as file:
            file.write('\n'.join(output_lines))
<commit_msg>Remove an unused import<commit_after>from __future__ import division, print_function, unicode_literals

import subprocess, os, pickle, time, argparse
import WriteFeaturesKernFDK, WriteFeaturesMarkFDK
import hindkit as kit

class Builder(object):

    def __init__(
        self,
        family,
        fontrevision = '1.000',
        devanagari_offset_matrix = ((0, 0), (0, 0)),
    ):

        self.family = family
        self.fontrevision = fontrevision
        self.devanagari_offset_matrix = devanagari_offset_matrix

        for module in [
            'kerning',
            'mark_positioning',
            'mark_to_mark_positioning',
            'devanagari_matra_i_variants',
        ]:
            if self.family.__dict__['has_' + module]:
                self.__dict__['enable_' + module] = True
            else:
                self.__dict__['enable_' + module] = False

        self.prepare_styles = False
        self.prepare_features = False
        self.compile = False

        self.makeinstances = False
        self.checkoutlines = False
        self.autohint = False

        self.do_style_linking = False
        self.use_os_2_version_4 = False
        self.prefer_typo_metrics = False
        self.is_width_weight_slope_only = False

        self.parser = argparse.ArgumentParser()
        options = self.parser.add_argument_group(
            title = 'build options',
            description = 'execute `python build.py` to run stages as specified in build.py, or append arguments to override.'
        )
        options.add_argument(
            '-s', '--stages', action = 'store',
            help = '"1" for "prepare_styles", "2" for "prepare_features", and "3" for "compile".'
        )
        options.add_argument(
            '-o', '--options', action = 'store',
            help = '"0" for none, "1" for "makeinstances", "2" for "checkoutlines", and "3" for "autohint".'
        )

    def _parse_args(self):
        args = self.parser.parse_args()
        if args.stages:
            stages = str(args.stages)
            self.prepare_styles = True if '1' in stages else False
            self.prepare_features = True if '2' in stages else False
            self.compile = True if '3' in stages else False
        if args.options:
            options = str(args.options)
            self.makeinstances = True if '1' in options else False
            self.checkoutlines = True if '2' in options else False
            self.autohint = True if '3' in options else False

    def _check_master_files(self):
        is_missing = False
        message_lines = ['[WARNING] UFO master files missing:']
        for master in self.family.masters:
            if not os.path.exists(master.path):
                is_missing = True
                message_lines.append('\t' + master.path)
        if is_missing:
            message_lines.extend(['', '[Note] Exit.', ''])
            raise SystemExit('\n'.join(message_lines))

    def set_options(self, options = []):

        for supported_option in [

            'prepare_styles',
            'prepare_features',
            'compile',

            'makeinstances',
            'checkoutlines',
            'autohint',

            'do_style_linking',
            'use_os_2_version_4',
            'prefer_typo_metrics',
            'is_width_weight_slope_only',

        ]:
            if supported_option in options:
                self.__dict__[supported_option] = True

    def generate_designspace(self):

        process = subprocess.Popen(
            ['AFDKOPython', 'AFDKOPython/generate_designspace.py'],
            stdin = subprocess.PIPE,
            cwd = kit.__path__[0],
        )
        process.communicate(pickle.dumps(self.family.dump()))

    def generate_fmndb(self):

        lines = []
        for style in self.family.styles:
            lines.append('')
            lines.append('[{}]'.format(style.output_full_name_postscript))
            lines.append('  f = {}'.format(self.family.output_name))
            lines.append('  s = {}'.format(style.name))

            if style.name != 'Regular':
                if self.do_style_linking and (style.is_bold or style.is_italic):
                    if style.is_bold:
                        lines.append('  # IsBoldStyle')
                    if style.is_italic:
                        lines.append('  # IsItalicStyle')
                else:
                    lines.append('  l = {}'.format(style.output_full_name))

        with open(kit.paths.FMNDN, 'w') as f:
            f.write(kit.templates.FMNDB_HEAD)
            f.write('\n'.join(lines))
            f.write('\n')

    def build(self, additional_arguments = []):

        self._parse_args()

        print()
        print('[Note] {} Building...\n'.format(time.strftime('%H:%M:%S')))

        if self.makeinstances:
            self.enabled_styles = self.family.styles
        else:
            if len(self.family.masters) == 1 and len(self.family.styles) == 1:
                self.enabled_styles = self.family.styles
            else:
                self.enabled_styles = [self.family.styles[0], self.family.styles[-1]]

        if self.family.script in ['Devanagari', 'Gujarati']:
            from hindkit.scripts import devanagari
            devanagari.SCRIPT_PREFIX = kit.linguistics.INDIC_SCRIPTS[self.family.script.lower()]['abbreviation']

        if self.prepare_styles:

            self._check_master_files()

            if self.enable_mark_positioning:

                print('[Note] Generating the glyph class for combining marks...\n')

                glyph_classes = []

                glyph_classes.extend([('generated_MARKS', glyph_filter_marks)])

                if self.enable_devanagari_matra_i_variants:
                    print('[Note] Generating glyph classes for mI and mII matching...\n')
                    glyph_classes.extend([
                        ('generated_MATRA_I_ALTS', devanagari.glyph_filter_matra_i_alts),
                        ('generated_BASES_FOR_MATRA_I', devanagari.glyph_filter_bases_for_matra_i),
                        ('generated_BASES_FOR_WIDE_MATRA_II', devanagari.glyph_filter_bases_for_wide_matra_ii),
                    ])

                generate_glyph_classes(
                    self.family,
                    self.family.masters[0].open_font(),
                    glyph_classes,
                    output_path = 'features/GENERATED_classes.fea'
                )

            print('[Note] Resetting instance directories...\n')
            subprocess.call(['rm', '-fr', kit.paths.INSTANCES])
            subprocess.call(['mkdir', kit.paths.INSTANCES])
            for style in self.enabled_styles:
                subprocess.call(['mkdir', style.directory])

            if self.makeinstances:

                print('[Note] Start interpolating masters...\n')

                # Prepare makeInstancesUFO arguments

                arguments = ['-d', 'font.designspace']

                if not self.checkoutlines:
                    arguments.append('-c')
                if not self.autohint:
                    arguments.append('-a')

                # Run makeInstancesUFO

                subprocess.call(['makeInstancesUFO'] + arguments)

                # Remove the log file

                subprocess.call(['rm', '-f', 'mutatorMath.log'])

                print()
                print('[Note] Done interpolating masters.\n')

            else:

                print('[Note] Copying masters to be instances.\n')

                for index, (master, style) in enumerate(zip(self.family.masters, self.enabled_styles)):

                    subprocess.call(['cp', '-fr', master.path, style.path])

                    font = style.open_font()
                    font.info.postscriptFontName = style.output_full_name_postscript

                    if index != 0:
                        font.groups.update(self.family.masters[0].open_font().groups)
                    font.save()

                    if self.checkoutlines:
                        subprocess.call(['checkOutlinesUFO', '-e', '-all', style.path])

                    if self.autohint:
                        subprocess.call(['autohint', '-q','-nb', style.path])

        if self.prepare_features and (self.enable_kerning or self.enable_mark_positioning):

            self._check_master_files()

            for style in self.enabled_styles:

                print('[Note] Generating features for "{}":\n'.format(style.name))

                if self.enable_kerning:
                    WriteFeaturesKernFDK.KernDataClass(
                        font = style.open_font(),
                        folderPath = style.directory,
                        minKern = 3,
                        writeTrimmed = False,
                        writeSubtables = True,
                        fileName = 'kern.fea',
                    )

                if self.enable_mark_positioning:

                    WriteFeaturesMarkFDK.kCombMarksClassName = 'generated_MARKS'

                    WriteFeaturesMarkFDK.MarkDataClass(
                        font = style.open_font(),
                        folderPath = style.directory,
                        trimCasingTags = False,
                        genMkmkFeature = self.enable_mark_to_mark_positioning,
                        writeClassesFile = True,
                        indianScriptsFormat = (
                            True if self.family.script.lower() in kit.linguistics.INDIC_SCRIPTS
                            else False
                        ),
                    )

                    if self.enable_devanagari_matra_i_variants:

                        print()
                        print('\t[Note] mI matching...')

                        light, bold = self.devanagari_offset_matrix
                        light_min, light_max = light
                        bold_min, bold_max = bold

                        ratio = style.interpolation_value / self.family.masters[-1].interpolation_value

                        offset_tuple = (
                            light_min + (bold_min - light_min) * ratio,
                            light_max + (bold_max - light_max) * ratio,
                        )

                        devanagari.match_matra_i_alts(
                            style,
                            offset_range = offset_tuple
                        )

                print()

            print('[Note] Done generating features.\n')

        if self.compile:

            print('[Note] Resetting the build directory...\n')
            subprocess.call(['rm', '-fr', kit.paths.BUILD])
            subprocess.call(['mkdir', kit.paths.BUILD])

            for style in self.enabled_styles:

                print('[Note] Compiling OTFs for "{}":'.format(style.name))

                font = style.open_font()
                if font.info.postscriptFontName != style.output_full_name_postscript:
                    font.info.postscriptFontName = style.output_full_name_postscript
                    print('\n[Note] Fixed the PostScript name.')
                    font.save()

                with open(os.path.join(style.directory, 'features'), 'w') as file:
                    file.write(kit.templates.FEATURES)

                with open(os.path.join(style.directory, 'weightclass.fea'), 'w') as file:
                    file.write(kit.templates.WEIGHTCLASS.format(str(style.weight_class)))

                otf_name = style.output_full_name_postscript + '.otf'
                otf_path = os.path.join(kit.paths.BUILD, otf_name)

                # Prepare makeotf arguments

                arguments = [
                    '-f', style.path,
                    '-o', otf_path,
                    '-mf', kit.paths.FMNDN,
                    '-gf', self.family.goadb_path,
                    '-r',
                    '-shw',
                    '-rev', self.fontrevision
                ]

                # Style linking:

                if self.do_style_linking:
                    if style.is_bold:
                        arguments.append('-b')
                    if style.is_italic:
                        arguments.append('-i')

                # New bits in OS/2.fsSelection

                if self.use_os_2_version_4:
                    for digit, boolean in [
                        ('7', self.prefer_typo_metrics),
                        ('8', self.is_width_weight_slope_only),
                        ('9', style.is_oblique),
                    ]:
                        arguments.append('-osbOn' if boolean else '-osbOff')
                        arguments.append(digit)

                arguments.extend(additional_arguments)

                # Run makeotf

                subprocess.call(['makeotf'] + arguments)

                # Remove the project file

                project_file_path = os.path.join(style.directory, 'current.fpr')
                subprocess.call(['rm', '-f', project_file_path])

                # Copy the compiled font file to Adobe's fonts folder

                if os.path.exists(otf_path) and os.path.exists(kit.paths.OUTPUT):
                    subprocess.call(['cp', '-f', otf_path, kit.paths.OUTPUT])

                print()

            print('[Note] Done compiling OTFs.\n')

        print('[Note] {} Done building.\n'.format(time.strftime('%H:%M:%S')))

def sort_glyphs(glyph_order, glyph_names):
    sorted_glyphs = (
        [i for i in glyph_order if i in glyph_names] +
        [i for i in glyph_names if i not in glyph_order]
    )
    return sorted_glyphs

def compose_glyph_class_def_lines(class_name, glyph_names):
    if glyph_names:
        glyph_class_def_lines = (
            ['@{} = ['.format(class_name)] +
            ['  {}'.format(glyph_name) for glyph_name in glyph_names] +
            ['];', '']
        )
    else:
        glyph_class_def_lines = ['# @{} = [];'.format(class_name), '']
    return glyph_class_def_lines

def glyph_filter_marks(glyph):
    has_mark_anchor = False
    for anchor in glyph.anchors:
        if anchor.name.startswith('_'):
            has_mark_anchor = True
            break
    return has_mark_anchor

def generate_glyph_classes(family, font, glyph_classes, output_path = None):
    glyph_order = [
        development_name for
        production_name, development_name, unicode_mapping in
        family.goadb
    ]
    output_lines = []
    for class_name, filter_function in glyph_classes:
        glyph_names = [glyph.name for glyph in filter(filter_function, font)]
        glyph_names = sort_glyphs(glyph_order, glyph_names)
        font.groups.update({class_name: glyph_names})
        if output_path:
            output_lines.extend(
                compose_glyph_class_def_lines(class_name, glyph_names)
            )
    font.save()
    if output_path:
        with open(output_path, 'w') as file:
            file.write('\n'.join(output_lines))
<|endoftext|>


<commit_before>import discord
from discord.ext import commands

class Mycog:
    def __init__(self, bot):
        self.bot = bot
    @commands.command()

    async def autoupdate(self):
        self.update()

def setup(bot):
    bot.add_cog(Mycog(bot))
<commit_msg>WE'RE CLOSE<commit_after>import discord
from discord.ext import commands

class Mycog:
    def __init__(self, bot):
        self.bot = bot
    @commands.command()

    async def autoupdate(self):
        self.bot.update()

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


<commit_before>#! /usr/bin/env python
#
# Takes about 15" for 1400 images on laptop with a local fast disk (100% cpu)
# But 60" on the Xeon, but at 300% cpu
#
import matplotlib.pyplot as plt
import numpy as np
import sys

table = sys.argv[1]
png   = table + '.png'
twopi = 2*np.pi

#   table of decimal hour time and median sky brightness (50,000 is very bright)
(t,s) = np.loadtxt(table).T
print("Sky: ",s.min(),s.max())
print("Time:",t.min(),t.max())

t0 = t[0]
t1 = t[-1]
print(t0,t1)

# tmin is the sunrise, from t1 (6), should be near 90
# tmax is the sunset, from t0 (18)                270
tmin = (6-t1)*15  +  90
tmax = (18-t0)*15 + 270

print(tmin,tmax)
x = (12-t) * twopi / 24.0
y = s.max()-s

print(x.min(),x.max())
print(y.min(),y.max())

fig, ax = plt.subplots(1, 1, subplot_kw=dict(projection='polar'))

ax.plot(x, y)
ax.set_theta_zero_location('S')
ax.xaxis.set_major_formatter(plt.NullFormatter())
ax.xaxis.set_major_formatter(plt.NullFormatter())
ax.yaxis.set_major_formatter(plt.NullFormatter())
ax.yaxis.set_major_formatter(plt.NullFormatter())

if False:
    # always same pie, an extra hour either side
    tmin=75
    tmax=285
print(tmin,tmax)
ax.set_thetamin(tmin)
ax.set_thetamax(tmax)

ya = 0.2 * y
yb = 0.4 * y
yc = 0.8 * y
yd = 0.8 * y
ye = 0.9 * y
ax.fill_between(x,0, ya,facecolor='green',alpha=0.1)
ax.fill_between(x,ya,yb,facecolor='green',alpha=0.3)
ax.fill_between(x,yb,yc,facecolor='green',alpha=0.5)
ax.fill_between(x,yc,yd,facecolor='green',alpha=0.7)
ax.fill_between(x,yd,ye,facecolor='green',alpha=0.85)
ax.fill_between(x,ye,y ,facecolor='green',alpha=1)

# needs tweaking
plt.text(3.14,50000,'midnight',horizontalalignment='center')
plt.text(1.1,42000,'sunrise')
plt.text(5.1,48000,'sunset')
plt.text(5.5,20000,'imagine a moon')

plt.title("%s sky: %g %g  %g-%g h" % (table,s.min(),s.max(),t0,t1))
plt.savefig(png)
plt.show()

print("Written ",png)

<commit_msg>allow a grid<commit_after>#! /usr/bin/env python
#
# Takes about 15" for 1400 images on laptop with a local fast disk (100% cpu)
# But 60" on the Xeon, but at 300% cpu
#
import matplotlib.pyplot as plt
import numpy as np
import sys

twopi = 2*np.pi

def plot1(table,ax,Qtitle):
    #   table of decimal hour time and median sky brightness (50,000 is very bright)
    (t,s) = np.loadtxt(table).T
    print("Sky: ",s.min(),s.max())
    print("Time:",t.min(),t.max())

    t0 = t[0]
    t1 = t[-1]
    print(t0,t1)

    # tmin is the sunrise, from t1 (6), should be near 90
    # tmax is the sunset, from t0 (18)                270
    tmin = (6-t1)*15  +  90
    tmax = (18-t0)*15 + 270

    smax = 64000

    print(tmin,tmax)
    x = (12-t) * twopi / 24.0
    y = s.max()-s
    y = smax-s

    print(x.min(),x.max())
    print(y.min(),y.max())

    ax.plot(x, y)
    ax.set_theta_zero_location('S')
    ax.set_ylim([0,smax])
    ax.xaxis.set_major_formatter(plt.NullFormatter())
    ax.xaxis.set_major_formatter(plt.NullFormatter())
    ax.yaxis.set_major_formatter(plt.NullFormatter())
    ax.yaxis.set_major_formatter(plt.NullFormatter())

    if False:
        # always same pie, an extra hour either side
        tmin=75
        tmax=285
    print(tmin,tmax)
    ax.set_thetamin(tmin)
    ax.set_thetamax(tmax)
    y1 = y
    if False:
        y1 = y*0 + smax

    ya = 0.2 * y1
    yb = 0.4 * y1
    yc = 0.8 * y1
    yd = 0.8 * y1
    ye = 0.9 * y1
    ax.fill_between(x,0, ya,facecolor='green',alpha=0.1)
    ax.fill_between(x,ya,yb,facecolor='green',alpha=0.3)
    ax.fill_between(x,yb,yc,facecolor='green',alpha=0.5)
    ax.fill_between(x,yc,yd,facecolor='green',alpha=0.7)
    ax.fill_between(x,yd,ye,facecolor='green',alpha=0.85)
    ax.fill_between(x,ye,y ,facecolor='green',alpha=1)

    if Qtitle:
        plt.title("%s sky: %g-%g  %.3f-%.3f h" % (table,s.min(),s.max(),t0,t1))

ntable = len(sys.argv[1:])
table = sys.argv[1]
png   = table + '.png'

if ntable == 1:
    Qtitle = True
else:
    Qtitle = False

nx = int(np.sqrt(ntable))
ny = ntable // nx

print(nx,ny)

fig, ax = plt.subplots(ny, nx, subplot_kw=dict(projection='polar'))

if Qtitle:
    plot1(table,ax,True)
else:
    k = 1
    for i in range(nx):
        for j in range(ny):
            plot1(sys.argv[k],ax[j][i],False)
            k = k+1

if Qtitle:
    # needs tweaking
    plt.text(3.14,50000,'midnight',horizontalalignment='center')
    plt.text(1.1,42000,'sunrise')
    plt.text(5.1,48000,'sunset')
    plt.text(5.5,20000,'imagine a moon')

plt.savefig(png)
plt.show()

print("Written ",png)

<|endoftext|>


<commit_before>#!/usr/bin/python2.6
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Perform various tasks related to updating Portage packages."""

import glob
import logging
import optparse
import os
import parallel_emerge
import portage
import re
import sys

sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
import chromite.lib.cros_build_lib as cros_lib

class Updater(object):
  """A class to perform various tasks related to updating Portage packages."""

  def __init__(self, options, args):
    self._options = options
    self._args = args

  @staticmethod
  def _GetPreOrderDepGraphPackage(deps_graph, package, pkglist, visited):
    """Collect packages from |deps_graph| into |pkglist| in pre-order."""
    if package in visited:
      return
    visited.add(package)
    for parent in deps_graph[package]['provides']:
      Updater._GetPreOrderDepGraphPackage(deps_graph, parent, pkglist, visited)
    pkglist.append(package)

  @staticmethod
  def _GetPreOrderDepGraph(deps_graph):
    """Return packages from |deps_graph| in pre-order."""
    pkglist = []
    visited = set()
    for package in deps_graph:
      Updater._GetPreOrderDepGraphPackage(deps_graph, package, pkglist, visited)
    return pkglist

  @staticmethod
  def _IsStableEBuild(ebuild_path):
    """Returns true if |ebuild_path| is a stable ebuild, false otherwise."""
    with open(ebuild_path, 'r') as ebuild:
      for line in ebuild:
        if line.startswith('KEYWORDS='):
          # TODO(petkov): Support non-x86 targets.
          return re.search(r'[ "]x86[ "]', line)
    return False

  def _SplitEBuildPath(self, ebuild_path):
    """Split a full ebuild path into (overlay, cat, pn, pv)."""
    logging.debug('ebuild: %s', ebuild_path)
    (ebuild_path, ebuild) = os.path.splitext(ebuild_path)
    (ebuild_path, pv) = os.path.split(ebuild_path)
    (ebuild_path, pn) = os.path.split(ebuild_path)
    (ebuild_path, cat) = os.path.split(ebuild_path)
    (ebuild_path, overlay) = os.path.split(ebuild_path)
    logging.debug('%s | %s | %s | %s', overlay, cat, pn, pv)
    return (overlay, cat, pn, pv)

  def _FindLatestVersion(self, upstream, cpv):
    """Returns the latest cpv in |upstream| if it's newer than |cpv|."""
    latest_cpv = cpv
    (cat, pn, version, rev) = portage.versions.catpkgsplit(cpv)
    pkgpath = os.path.join(upstream, cat, pn)
    for ebuild_path in glob.glob(os.path.join(pkgpath, '%s*.ebuild' % pn)):
      if not Updater._IsStableEBuild(ebuild_path): continue
      (overlay, cat, pn, pv) = self._SplitEBuildPath(ebuild_path)
      upstream_cpv = os.path.join(cat, pv)
      if portage.versions.pkgcmp(portage.versions.pkgsplit(upstream_cpv),
                                 portage.versions.pkgsplit(latest_cpv)) > 0:
        latest_cpv = upstream_cpv
    logging.debug('cpv: %s latest_cpv: %s', cpv, latest_cpv)
    if latest_cpv != cpv: return latest_cpv
    return None

  def _FindLatestVersions(self, cpvlist):
    """Given a list of cpvs, returns a list of cpv/latest_cpv info maps."""
    checkout_gentoo = False
    upstream = self._options.upstream
    if not upstream:
      checkout_gentoo = True
      upstream = os.path.join(self._options.srcroot, 'third_party', 'portage')
    infolist = []
    dash_q = ''
    if not self._options.verbose: dash_q = '-q'
    try:
      # TODO(petkov): Currently portage's master branch is stale so we need to
      # checkout latest upstream. At some point portage's master branch will be
      # upstream so there will be no need to chdir/checkout. At that point we
      # can also fuse this loop into the caller and avoid generating a separate
      # list.
      if checkout_gentoo:
        cros_lib.RunCommand(['/bin/sh', '-c',
                             'cd %s && git checkout %s cros/gentoo' % (
                                 upstream, dash_q)],
                            print_cmd=self._options.verbose);
      for cpv in cpvlist:
        # No need to report or try to upgrade chromeos-base packages.
        if cpv.startswith('chromeos-base/'): continue
        latest_cpv = self._FindLatestVersion(upstream, cpv)
        infolist.append({'cpv': cpv, 'latest_cpv': latest_cpv})
    finally:
      if checkout_gentoo:
        cros_lib.RunCommand(['/bin/sh', '-c',
                             'cd %s && git checkout %s cros/master' % (
                                 upstream, dash_q)],
                            print_cmd=self._options.verbose);
    return infolist

  def _PrintUpgrades(self):
    argv = ['--emptytree']
    argv.append('--board=%s' % self._options.board)
    if not self._options.verbose:
      argv.append('--quiet')
    if self._options.rdeps:
      argv.append('--root-deps=rdeps')
    argv.extend(self._args)

    deps = parallel_emerge.DepGraphGenerator()
    deps.Initialize(argv)
    deps_tree, deps_info = deps.GenDependencyTree({})
    deps_graph = deps.GenDependencyGraph(deps_tree, deps_info, {})
    cpvlist = Updater._GetPreOrderDepGraph(deps_graph)

    infolist = self._FindLatestVersions(cpvlist)
    for info in infolist:
      # TODO(petkov): Use internal portage utilities to find the overlay instead
      # of equery to improve performance, if possible.
      cpv = info['cpv']
      equery = ['equery-%s' % self._options.board, 'which', cpv]
      ebuild_path = cros_lib.RunCommand(equery, print_cmd=self._options.verbose,
                                        redirect_stdout=True).output.strip()
      (overlay, cat, pn, pv) = self._SplitEBuildPath(ebuild_path)
      info['overlay'] = overlay

    for info in infolist:
      update = ''
      if info['latest_cpv']: update = ' -> %s' % info['latest_cpv']
      print '[%s] %s%s' % (info['overlay'], info['cpv'], update)

  def Run(self):
    """Runs the updater based on the supplied options and arguments.

    Currently just lists all package dependencies in pre-order along with
    potential upgrades."""
    self._PrintUpgrades()

def main():
  usage = 'Usage: %prog [options] packages...'
  parser = optparse.OptionParser(usage=usage)
  parser.add_option('--board', dest='board', type='string', action='store',
                    default=None, help="Target board [default: '%default']")
  parser.add_option('--rdeps', dest='rdeps', action='store_true',
                    default=False,
                    help="Use runtime dependencies only")
  parser.add_option('--srcroot', dest='srcroot', type='string', action='store',
                    default='%s/trunk/src' % os.environ['HOME'],
                    help="Path to root src directory [default: '%default']")
  parser.add_option('--upstream', dest='upstream', type='string',
                    action='store', default=None,
                    help="Latest upstream repo location [default: '%default']")
  parser.add_option('--verbose', dest='verbose', action='store_true',
                    default=False,
                    help="Enable verbose output (for debugging)")

  (options, args) = parser.parse_args()

  if (options.verbose): logging.basicConfig(level=logging.DEBUG)

  if not options.board:
    parser.print_help()
    cros_lib.Die('board is required')

  if not args:
    parser.print_help()
    cros_lib.Die('no packages provided')

  updater = Updater(options, args)
  updater.Run()

if __name__ == '__main__':
  main()
<commit_msg>Refactor cros_update.py to make it possible to support upgrades.<commit_after>#!/usr/bin/python2.6
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Perform various tasks related to updating Portage packages."""

import glob
import logging
import optparse
import os
import parallel_emerge
import portage
import re
import sys

sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
import chromite.lib.cros_build_lib as cros_lib

class Updater(object):
  """A class to perform various tasks related to updating Portage packages."""

  def __init__(self, options, args):
    self._options = options
    self._args = args

  @staticmethod
  def _GetPreOrderDepGraphPackage(deps_graph, package, pkglist, visited):
    """Collect packages from |deps_graph| into |pkglist| in pre-order."""
    if package in visited:
      return
    visited.add(package)
    for parent in deps_graph[package]['provides']:
      Updater._GetPreOrderDepGraphPackage(deps_graph, parent, pkglist, visited)
    pkglist.append(package)

  @staticmethod
  def _GetPreOrderDepGraph(deps_graph):
    """Return packages from |deps_graph| in pre-order."""
    pkglist = []
    visited = set()
    for package in deps_graph:
      Updater._GetPreOrderDepGraphPackage(deps_graph, package, pkglist, visited)
    return pkglist

  @staticmethod
  def _IsStableEBuild(ebuild_path):
    """Returns true if |ebuild_path| is a stable ebuild, false otherwise."""
    with open(ebuild_path, 'r') as ebuild:
      for line in ebuild:
        if line.startswith('KEYWORDS='):
          # TODO(petkov): Support non-x86 targets.
          return re.search(r'[ "]x86[ "]', line)
    return False

  def _SplitEBuildPath(self, ebuild_path):
    """Split a full ebuild path into (overlay, cat, pn, pv)."""
    logging.debug('ebuild: %s', ebuild_path)
    (ebuild_path, ebuild) = os.path.splitext(ebuild_path)
    (ebuild_path, pv) = os.path.split(ebuild_path)
    (ebuild_path, pn) = os.path.split(ebuild_path)
    (ebuild_path, cat) = os.path.split(ebuild_path)
    (ebuild_path, overlay) = os.path.split(ebuild_path)
    logging.debug('%s | %s | %s | %s', overlay, cat, pn, pv)
    return (overlay, cat, pn, pv)

  def _FindLatestVersion(self, upstream, cpv):
    """Returns the latest cpv in |upstream| if it's newer than |cpv|."""
    latest_cpv = cpv
    (cat, pn, version, rev) = portage.versions.catpkgsplit(cpv)
    pkgpath = os.path.join(upstream, cat, pn)
    for ebuild_path in glob.glob(os.path.join(pkgpath, '%s*.ebuild' % pn)):
      if not Updater._IsStableEBuild(ebuild_path): continue
      (overlay, cat, pn, pv) = self._SplitEBuildPath(ebuild_path)
      upstream_cpv = os.path.join(cat, pv)
      if portage.versions.pkgcmp(portage.versions.pkgsplit(upstream_cpv),
                                 portage.versions.pkgsplit(latest_cpv)) > 0:
        latest_cpv = upstream_cpv
    logging.debug('cpv: %s latest_cpv: %s', cpv, latest_cpv)
    if latest_cpv != cpv: return latest_cpv
    return None

  def _FindLatestVersions(self, infolist):
    """Given a list of cpv info maps, adds the latest_cpv to the infos."""
    checkout_gentoo = False
    upstream = self._options.upstream
    if not upstream:
      checkout_gentoo = True
      upstream = os.path.join(self._options.srcroot, 'third_party', 'portage')
    dash_q = ''
    if not self._options.verbose: dash_q = '-q'
    try:
      # TODO(petkov): Currently portage's master branch is stale so we need to
      # checkout latest upstream. At some point portage's master branch will be
      # upstream so there will be no need to chdir/checkout. At that point we
      # can also fuse this loop into the caller and avoid generating a separate
      # list.
      if checkout_gentoo:
        cros_lib.RunCommand(['/bin/sh', '-c',
                             'cd %s && git checkout %s cros/gentoo' % (
                                 upstream, dash_q)],
                            print_cmd=self._options.verbose);
      for info in infolist:
        # No need to report or try to upgrade chromeos-base packages.
        if info['cpv'].startswith('chromeos-base/'): continue
        latest_cpv = self._FindLatestVersion(upstream, info['cpv'])
        info['latest_cpv'] = latest_cpv
    finally:
      if checkout_gentoo:
        cros_lib.RunCommand(['/bin/sh', '-c',
                             'cd %s && git checkout %s cros/master' % (
                                 upstream, dash_q)],
                            print_cmd=self._options.verbose);

  def _GetCurrentVersions(self):
    """Returns a list of cpvs of the current package dependencies."""
    argv = ['--emptytree']
    argv.append('--board=%s' % self._options.board)
    if not self._options.verbose:
      argv.append('--quiet')
    if self._options.rdeps:
      argv.append('--root-deps=rdeps')
    argv.extend(self._args)

    deps = parallel_emerge.DepGraphGenerator()
    deps.Initialize(argv)
    deps_tree, deps_info = deps.GenDependencyTree({})
    deps_graph = deps.GenDependencyGraph(deps_tree, deps_info, {})
    return Updater._GetPreOrderDepGraph(deps_graph)

  def _GetInfoListWithOverlays(self, cpvlist):
    """Returns a list of cpv/overlay info maps corresponding to |cpvlist|."""
    infolist = []
    for cpv in cpvlist:
      # TODO(petkov): Use internal portage utilities to find the overlay instead
      # of equery to improve performance, if possible.
      equery = ['equery-%s' % self._options.board, 'which', cpv]
      ebuild_path = cros_lib.RunCommand(equery, print_cmd=self._options.verbose,
                                        redirect_stdout=True).output.strip()
      (overlay, cat, pn, pv) = self._SplitEBuildPath(ebuild_path)
      infolist.append({'cpv': cpv, 'overlay': overlay})

    return infolist

  def Run(self):
    """Runs the updater based on the supplied options and arguments.

    Currently just lists all package dependencies in pre-order along with
    potential upgrades."""
    cpvlist = self._GetCurrentVersions()
    infolist = self._GetInfoListWithOverlays(cpvlist)
    self._FindLatestVersions(infolist)
    for info in infolist:
      update = ''
      if info['latest_cpv']: update = ' -> %s' % info['latest_cpv']
      print '[%s] %s%s' % (info['overlay'], info['cpv'], update)

def main():
  usage = 'Usage: %prog [options] packages...'
  parser = optparse.OptionParser(usage=usage)
  parser.add_option('--board', dest='board', type='string', action='store',
                    default=None, help="Target board [default: '%default']")
  parser.add_option('--rdeps', dest='rdeps', action='store_true',
                    default=False,
                    help="Use runtime dependencies only")
  parser.add_option('--srcroot', dest='srcroot', type='string', action='store',
                    default='%s/trunk/src' % os.environ['HOME'],
                    help="Path to root src directory [default: '%default']")
  parser.add_option('--upstream', dest='upstream', type='string',
                    action='store', default=None,
                    help="Latest upstream repo location [default: '%default']")
  parser.add_option('--verbose', dest='verbose', action='store_true',
                    default=False,
                    help="Enable verbose output (for debugging)")

  (options, args) = parser.parse_args()

  if (options.verbose): logging.basicConfig(level=logging.DEBUG)

  if not options.board:
    parser.print_help()
    cros_lib.Die('board is required')

  if not args:
    parser.print_help()
    cros_lib.Die('no packages provided')

  updater = Updater(options, args)
  updater.Run()

if __name__ == '__main__':
  main()
<|endoftext|>


<commit_before>from collections import namedtuple
import operator

from django.db import models
from django.utils.datastructures import SortedDict
from django.utils.safestring import mark_safe
from django.utils.html import escape
from django.utils.http import urlencode
from django.utils.text import capfirst

FILTER_ADD = 'add'
FILTER_REMOVE = 'remove'

FilterChoice = namedtuple('FilterChoice', 'label count params link_type')

class FilterOptions(object):
    """
    Defines some common options for all Filters.

    A FilterOption instance can be used when defining the 'fields' attribute of
    a FilterSet. The actual choice of Filter subclass will be done by the
    FilterSet in this case.
    """
    def __init__(self, query_param=None, order_by_count=False):
        self.query_param = query_param
        self.order_by_count = order_by_count

class Filter(FilterOptions):
    """
    A Filter creates links/URLs that correspond to some DB filtering,
    and can apply the information from a URL to filter a QuerySet.
    """
    def __init__(self, field, model, **kwargs):
        # State: Filter objects are created as class attributes of FilterSets,
        # and so cannot carry any request specific state. They only have
        # configuration information.
        self.field = field
        self.model = model
        if kwargs.get('query_param', None) is None:
            kwargs['query_param'] = field
        self.field_obj = self.model._meta.get_field(self.field)
        super(Filter, self).__init__(**kwargs)

    def apply_filter(self, qs, params):
        p_val = self.choices_from_params(params)
        while len(p_val) > 0:
            qs = qs.filter(**{self.field: p_val.pop()})
        return qs

    def choices_from_params(self, params):
        """
        For the params passed in (i.e. from query string), retrive a list of
        already 'chosen' options.
        """
        raise NotImplementedError()

    def param_from_choices(self, choice):
        """
        For a list of choices, return the parameter that should be created.
        """
        raise NotImplementedError()

    def build_params(self, params, add=None, remove=None):
        params = params.copy()
        chosen = self.choices_from_params(params)
        if remove:
            chosen.remove(remove)
        else:
            if add not in chosen:
                chosen.append(add)
        if chosen:
            params[self.query_param] = self.param_from_choices(chosen)
        else:
            del params[self.query_param]
        params.pop('page', None) # links should reset paging
        return params

    def sort_choices(self, qs, params, choices):
        """
        Sorts the choices by applying order_by_count if applicable.

        See also sort_choices_custom.
        """
        if self.order_by_count:
            choices.sort(key=operator.attrgetter('count'), reverse=True)
        else:
            choices = self.sort_choices_custom(qs, params, choices)
        return choices

    def sort_choices_custom(self, qs, params, choices):
        """
        Override this to provide a custom sorting method for a field. If sorting
        can be better done in the DB, it should be done in the get_choices_add
        method.
        """
        return choices

    def display_choice(self, choice):
        retval = unicode(choice)
        if retval == u'':
            return u'(empty)'
        else:
            return retval

    def get_choices(self, qs, params):
        """
        Returns a list of namedtuples containing (label (as a string), count,
        params)
        """
        raise NotImplementedError()

class SingleValueFilterMixin(object):

    def choices_from_params(self, params):
        if self.query_param in params:
            return [params[self.query_param]]
        else:
            return []

    def param_from_choices(self, choices):
        # There can be only one
        return unicode(choices[0])

    def get_values_counts(self, qs, params):
        """
        Returns a SortedDict dictionary of {value: count}.

        The order is the underlying order produced by sorting ascending on the
        DB field.
        """
        values_counts = qs.values_list(self.field).order_by(self.field).annotate(models.Count(self.field))

        count_dict = SortedDict()
        for val, count in values_counts:
            count_dict[val] = count
        return count_dict

    def get_choices(self, qs, params):
        choices_remove = self.get_choices_remove(qs, params)
        if len(choices_remove) > 0:
            return choices_remove
        else:
            choices_add = self.get_choices_add(qs, params)
            if len(choices_add) == 1:
                # No point giving people a choice of one
                return []
            else:
                return self.sort_choices(qs, params, choices_add)

    def get_choices_add(self, qs, params):
        raise NotImplementedError()

    def get_choices_remove(self, qs, params):
        choices = self.choices_from_params(params)
        return [FilterChoice(self.display_choice(choice),
                             None, # Don't need count for removing
                             self.build_params(params, remove=choice),
                             FILTER_REMOVE)
                for choice in choices]

class ValuesFilter(SingleValueFilterMixin, Filter):
    """
    Fallback Filter for various kinds of simple values.
    """
    def get_choices_add(self, qs, params):
        """
        Called by 'get_choices', this is usually the one to override.
        """
        count_dict = self.get_values_counts(qs, params)
        return [FilterChoice(self.display_choice(val),
                             count,
                             self.build_params(params, add=val),
                             FILTER_ADD)
                for val, count in count_dict.items()]

class ChoicesFilter(ValuesFilter):
    """
    Filter for fields that have 'choices' defined.
    """
    # Need to do the following:
    # 1) ensure we only display options that are in 'choices'
    # 2) ensure the order is the same as in choices
    # 3) make display value = the second element in choices' tuples.
    def __init__(self, *args, **kwargs):
        super(ChoicesFilter, self).__init__(*args, **kwargs)
        # For performance we cache this rather than build in
        self.choices_dict = dict(self.field_obj.flatchoices)

    def display_choice(self, choice):
        # 3) above
        return self.choices_dict.get(choice, choice)

    def get_choices_add(self, qs, params):
        count_dict = self.get_values_counts(qs, params)
        choices = []
        for val, display in self.field_obj.choices:
            # 1), 2) above
            if val in count_dict:
                # We could use the value 'display' here, but for consistency
                # call display_choice() in case it is overriden.
                choices.append(FilterChoice(self.display_choice(val),
                                            count_dict[val],
                                            self.build_params(params, add=val),
                                            FILTER_ADD))
        return choices

class ForeignKeyFilter(SingleValueFilterMixin, Filter):
    """
    Filter for ForeignKey fields.
    """
    def __init__(self, *args, **kwargs):
        super(ForeignKeyFilter, self).__init__(*args, **kwargs)
        self.rel_model = self.field_obj.rel.to
        self.rel_field = self.field_obj.rel.get_related_field()

    def display_choice(self, choice):
        lookup = {self.rel_field.name: choice}
        return self.rel_model.objects.get(**lookup)

    def get_choices_add(self, qs, params):
        count_dict = self.get_values_counts(qs, params)
        lookup = {self.rel_field.name + '__in': count_dict.keys()}
        objs = self.rel_model.objects.filter(**lookup)
        choices = []

        for o in objs:
            pk = getattr(o, self.rel_field.attname)
            choices.append(FilterChoice(unicode(o),
                                        count_dict[pk],
                                        self.build_params(params, add=pk),
                                        FILTER_ADD))
        return choices

class MultiValueFilterMixin(object):

    def choices_from_params(self, params):
        if self.query_param in params:
            return map(int, params[self.query_param].split(','))
        else:
            return []

    def param_from_choices(self, choices):
        return ','.join(map(unicode, choices))

    def get_choices(self, qs, params):
        # In general, can filter multiple times, so we can have multiple remove
        # links, and multiple add links, at the same time.
        choices_remove = self.get_choices_remove(qs, params)
        choices_add = self.get_choices_add(qs, params)
        if len(choices_add) == 1:
            # No point adding a filter of nothing
            choices_add = []
        else:
            choices_add = self.sort_choices(qs, params, choices_add)
        return choices_remove + choices_add

class ManyToManyFilter(MultiValueFilterMixin, Filter):
    def __init__(self, *args, **kwargs):
        super(ManyToManyFilter, self).__init__(*args, **kwargs)
        self.rel_model = self.field_obj.rel.to

    def get_choices_add(self, qs, params):
        # It is easiest to base queries around the intermediate table, in order
        # to get counts.
        through = self.field_obj.rel.through
        rel_model = self.rel_model

        assert rel_model != self.model, "Can't cope with this yet..."

        fkey_to_this_table = [f for f in through._meta.fields
                              if f.rel is not None and f.rel.to is self.model][0]
        fkey_to_other_table = [f for f in through._meta.fields
                               if f.rel is not None and f.rel.to is rel_model][0]

        # We need to limit items by what is in the main QuerySet (which might
        # already be filtered).
        main_filter = {fkey_to_this_table.name + '__in':qs}
        m2m_objs = through.objects.filter(**main_filter)

        # We need to exclude items in other table that we have already filtered
        # on, because they are not interesting.
        exclude_filter = {fkey_to_other_table.name + '__in': self.choices_from_params(params)}
        m2m_objs = m2m_objs.exclude(**exclude_filter)

        # Now get counts:
        field_name = fkey_to_other_table.name
        values_counts = m2m_objs.values_list(field_name).order_by(field_name).annotate(models.Count(field_name))

        count_dict = SortedDict()
        for val, count in values_counts:
            count_dict[val] = count

        # Now, need to lookup objects on related table, to display them.
        objs = rel_model.objects.filter(pk__in=count_dict.keys())

        choices = []
        for o in objs:
            pk = o.pk
            choices.append(FilterChoice(unicode(o),
                                        count_dict[pk],
                                        self.build_params(params, add=pk),
                                        FILTER_ADD))
        return choices

    def get_choices_remove(self, qs, params):
        choices = self.choices_from_params(params)
        # Do a query in bulk to get objs corresponding to choices.
        objs = self.rel_model.objects.filter(pk__in=choices)

        # We want to preserve order of items in params, so use a dict:
        obj_dict = dict([(obj.pk, obj) for obj in objs])

        return [FilterChoice(unicode(obj_dict[choice]),
                             None, # Don't need count for removing
                             self.build_params(params, remove=choice),
                             FILTER_REMOVE)
                for choice in choices]

def non_breaking_spaces(val):
    return u' '.join(escape(part) for part in val.split(u' '))

class FilterSet(object):

    def __init__(self, queryset, params, request=None):
        self.params = dict(params.items())
        self.initial_queryset = queryset
        self.model = queryset.model
        self.filters = self.setup_filters()
        self.qs = self.apply_filters(queryset, params)

    def apply_filters(self, queryset, params):
        for f in self.filters:
            queryset = f.apply_filter(queryset, params)
        return queryset

    def render_filter(self, filter_, qs, params):
        out = []
        field_obj = self.model._meta.get_field(filter_.field)
        label = capfirst(field_obj.verbose_name)
        for c in filter_.get_choices(qs, params):
            if c.link_type == FILTER_REMOVE:
                out.append(u'%s [«] '
                           % (escape(c.label), escape(u'?' + urlencode(c.params))))
            else:
                out.append(u'%s (%d)   '
                           % (escape(u'?' + urlencode(c.params)), non_breaking_spaces(c.label), c.count))
        return (u'
%s: %s
'
                % (escape(label), u''.join(out)))

    def render(self):
        return mark_safe(u'\n'.join(self.render_filter(f, self.qs, self.params) for f in self.filters))

    def get_fields(self):
        return self.fields

    def get_filter_for_field(self, field, **kwargs):
        f, model, direct, m2m = self.model._meta.get_field_by_name(field)
        if f.rel is not None:
            if m2m:
                klass = ManyToManyFilter
            else:
                klass = ForeignKeyFilter
        elif f.choices:
            klass = ChoicesFilter
        else:
            klass = ValuesFilter
        return klass(field, self.model, **kwargs)

    def setup_filters(self):
        filters = []
        for i, f in enumerate(self.get_fields()):
            if isinstance(f, basestring):
                f = self.get_filter_for_field(f)
            else:
                # (field name, FilterOptions)
                field = f[0]
                opts = f[1].__dict__.copy()
                f = self.get_filter_for_field(field, **opts)
            filters.append(f)
        return filters

    def __unicode__(self):
        return self.render()
<commit_msg>Improved rendered HTML, and made more customisable<commit_after>from collections import namedtuple
import operator

from django.db import models
from django.utils.datastructures import SortedDict
from django.utils.safestring import mark_safe
from django.utils.html import escape
from django.utils.http import urlencode
from django.utils.text import capfirst

FILTER_ADD = 'add'
FILTER_REMOVE = 'remove'

FilterChoice = namedtuple('FilterChoice', 'label count params link_type')

class FilterOptions(object):
    """
    Defines some common options for all Filters.

    A FilterOption instance can be used when defining the 'fields' attribute of
    a FilterSet. The actual choice of Filter subclass will be done by the
    FilterSet in this case.
    """
    def __init__(self, query_param=None, order_by_count=False):
        self.query_param = query_param
        self.order_by_count = order_by_count

class Filter(FilterOptions):
    """
    A Filter creates links/URLs that correspond to some DB filtering,
    and can apply the information from a URL to filter a QuerySet.
    """
    def __init__(self, field, model, **kwargs):
        # State: Filter objects are created as class attributes of FilterSets,
        # and so cannot carry any request specific state. They only have
        # configuration information.
        self.field = field
        self.model = model
        if kwargs.get('query_param', None) is None:
            kwargs['query_param'] = field
        self.field_obj = self.model._meta.get_field(self.field)
        super(Filter, self).__init__(**kwargs)

    def apply_filter(self, qs, params):
        p_val = self.choices_from_params(params)
        while len(p_val) > 0:
            qs = qs.filter(**{self.field: p_val.pop()})
        return qs

    def choices_from_params(self, params):
        """
        For the params passed in (i.e. from query string), retrive a list of
        already 'chosen' options.
        """
        raise NotImplementedError()

    def param_from_choices(self, choice):
        """
        For a list of choices, return the parameter that should be created.
        """
        raise NotImplementedError()

    def build_params(self, params, add=None, remove=None):
        params = params.copy()
        chosen = self.choices_from_params(params)
        if remove:
            chosen.remove(remove)
        else:
            if add not in chosen:
                chosen.append(add)
        if chosen:
            params[self.query_param] = self.param_from_choices(chosen)
        else:
            del params[self.query_param]
        params.pop('page', None) # links should reset paging
        return params

    def sort_choices(self, qs, params, choices):
        """
        Sorts the choices by applying order_by_count if applicable.

        See also sort_choices_custom.
        """
        if self.order_by_count:
            choices.sort(key=operator.attrgetter('count'), reverse=True)
        else:
            choices = self.sort_choices_custom(qs, params, choices)
        return choices

    def sort_choices_custom(self, qs, params, choices):
        """
        Override this to provide a custom sorting method for a field. If sorting
        can be better done in the DB, it should be done in the get_choices_add
        method.
        """
        return choices

    def display_choice(self, choice):
        retval = unicode(choice)
        if retval == u'':
            return u'(empty)'
        else:
            return retval

    def get_choices(self, qs, params):
        """
        Returns a list of namedtuples containing (label (as a string), count,
        params)
        """
        raise NotImplementedError()

class SingleValueFilterMixin(object):

    def choices_from_params(self, params):
        if self.query_param in params:
            return [params[self.query_param]]
        else:
            return []

    def param_from_choices(self, choices):
        # There can be only one
        return unicode(choices[0])

    def get_values_counts(self, qs, params):
        """
        Returns a SortedDict dictionary of {value: count}.

        The order is the underlying order produced by sorting ascending on the
        DB field.
        """
        values_counts = qs.values_list(self.field).order_by(self.field).annotate(models.Count(self.field))

        count_dict = SortedDict()
        for val, count in values_counts:
            count_dict[val] = count
        return count_dict

    def get_choices(self, qs, params):
        choices_remove = self.get_choices_remove(qs, params)
        if len(choices_remove) > 0:
            return choices_remove
        else:
            choices_add = self.get_choices_add(qs, params)
            if len(choices_add) == 1:
                # No point giving people a choice of one
                return []
            else:
                return self.sort_choices(qs, params, choices_add)

    def get_choices_add(self, qs, params):
        raise NotImplementedError()

    def get_choices_remove(self, qs, params):
        choices = self.choices_from_params(params)
        return [FilterChoice(self.display_choice(choice),
                             None, # Don't need count for removing
                             self.build_params(params, remove=choice),
                             FILTER_REMOVE)
                for choice in choices]

class ValuesFilter(SingleValueFilterMixin, Filter):
    """
    Fallback Filter for various kinds of simple values.
    """
    def get_choices_add(self, qs, params):
        """
        Called by 'get_choices', this is usually the one to override.
        """
        count_dict = self.get_values_counts(qs, params)
        return [FilterChoice(self.display_choice(val),
                             count,
                             self.build_params(params, add=val),
                             FILTER_ADD)
                for val, count in count_dict.items()]

class ChoicesFilter(ValuesFilter):
    """
    Filter for fields that have 'choices' defined.
    """
    # Need to do the following:
    # 1) ensure we only display options that are in 'choices'
    # 2) ensure the order is the same as in choices
    # 3) make display value = the second element in choices' tuples.
    def __init__(self, *args, **kwargs):
        super(ChoicesFilter, self).__init__(*args, **kwargs)
        # For performance we cache this rather than build in
        self.choices_dict = dict(self.field_obj.flatchoices)

    def display_choice(self, choice):
        # 3) above
        return self.choices_dict.get(choice, choice)

    def get_choices_add(self, qs, params):
        count_dict = self.get_values_counts(qs, params)
        choices = []
        for val, display in self.field_obj.choices:
            # 1), 2) above
            if val in count_dict:
                # We could use the value 'display' here, but for consistency
                # call display_choice() in case it is overriden.
                choices.append(FilterChoice(self.display_choice(val),
                                            count_dict[val],
                                            self.build_params(params, add=val),
                                            FILTER_ADD))
        return choices

class ForeignKeyFilter(SingleValueFilterMixin, Filter):
    """
    Filter for ForeignKey fields.
    """
    def __init__(self, *args, **kwargs):
        super(ForeignKeyFilter, self).__init__(*args, **kwargs)
        self.rel_model = self.field_obj.rel.to
        self.rel_field = self.field_obj.rel.get_related_field()

    def display_choice(self, choice):
        lookup = {self.rel_field.name: choice}
        return self.rel_model.objects.get(**lookup)

    def get_choices_add(self, qs, params):
        count_dict = self.get_values_counts(qs, params)
        lookup = {self.rel_field.name + '__in': count_dict.keys()}
        objs = self.rel_model.objects.filter(**lookup)
        choices = []

        for o in objs:
            pk = getattr(o, self.rel_field.attname)
            choices.append(FilterChoice(unicode(o),
                                        count_dict[pk],
                                        self.build_params(params, add=pk),
                                        FILTER_ADD))
        return choices

class MultiValueFilterMixin(object):

    def choices_from_params(self, params):
        if self.query_param in params:
            return map(int, params[self.query_param].split(','))
        else:
            return []

    def param_from_choices(self, choices):
        return ','.join(map(unicode, choices))

    def get_choices(self, qs, params):
        # In general, can filter multiple times, so we can have multiple remove
        # links, and multiple add links, at the same time.
        choices_remove = self.get_choices_remove(qs, params)
        choices_add = self.get_choices_add(qs, params)
        if len(choices_add) == 1:
            # No point adding a filter of nothing
            choices_add = []
        else:
            choices_add = self.sort_choices(qs, params, choices_add)
        return choices_remove + choices_add

class ManyToManyFilter(MultiValueFilterMixin, Filter):
    def __init__(self, *args, **kwargs):
        super(ManyToManyFilter, self).__init__(*args, **kwargs)
        self.rel_model = self.field_obj.rel.to

    def get_choices_add(self, qs, params):
        # It is easiest to base queries around the intermediate table, in order
        # to get counts.
        through = self.field_obj.rel.through
        rel_model = self.rel_model

        assert rel_model != self.model, "Can't cope with this yet..."

        fkey_to_this_table = [f for f in through._meta.fields
                              if f.rel is not None and f.rel.to is self.model][0]
        fkey_to_other_table = [f for f in through._meta.fields
                               if f.rel is not None and f.rel.to is rel_model][0]

        # We need to limit items by what is in the main QuerySet (which might
        # already be filtered).
        main_filter = {fkey_to_this_table.name + '__in':qs}
        m2m_objs = through.objects.filter(**main_filter)

        # We need to exclude items in other table that we have already filtered
        # on, because they are not interesting.
        exclude_filter = {fkey_to_other_table.name + '__in': self.choices_from_params(params)}
        m2m_objs = m2m_objs.exclude(**exclude_filter)

        # Now get counts:
        field_name = fkey_to_other_table.name
        values_counts = m2m_objs.values_list(field_name).order_by(field_name).annotate(models.Count(field_name))

        count_dict = SortedDict()
        for val, count in values_counts:
            count_dict[val] = count

        # Now, need to lookup objects on related table, to display them.
        objs = rel_model.objects.filter(pk__in=count_dict.keys())

        choices = []
        for o in objs:
            pk = o.pk
            choices.append(FilterChoice(unicode(o),
                                        count_dict[pk],
                                        self.build_params(params, add=pk),
                                        FILTER_ADD))
        return choices

    def get_choices_remove(self, qs, params):
        choices = self.choices_from_params(params)
        # Do a query in bulk to get objs corresponding to choices.
        objs = self.rel_model.objects.filter(pk__in=choices)

        # We want to preserve order of items in params, so use a dict:
        obj_dict = dict([(obj.pk, obj) for obj in objs])

        return [FilterChoice(unicode(obj_dict[choice]),
                             None, # Don't need count for removing
                             self.build_params(params, remove=choice),
                             FILTER_REMOVE)
                for choice in choices]

def non_breaking_spaces(val):
    return u' '.join(escape(part) for part in val.split(u' '))

class FilterSet(object):

    remove_filter_template = u'%(label)s [«] '

    add_filter_template =  u'%(label)s (%(count)d)   '

    filter_line_template = u'
%(label)s: %(remove)s%(separator)s%(add)s
'

    add_remove_separator = u' | '

    def __init__(self, queryset, params, request=None):
        self.params = dict(params.items())
        self.initial_queryset = queryset
        self.model = queryset.model
        self.filters = self.setup_filters()
        self.qs = self.apply_filters(queryset, params)

    def apply_filters(self, queryset, params):
        for f in self.filters:
            queryset = f.apply_filter(queryset, params)
        return queryset

    def render_filter(self, filter_, qs, params):
        field_obj = self.model._meta.get_field(filter_.field)
        label = capfirst(field_obj.verbose_name)

        choices = filter_.get_choices(qs, params)
        remove = [self.remove_filter_template %
               dict(label=escape(c.label),
                    url=escape(u'?' + urlencode(c.params)))
               for c in choices if c.link_type == FILTER_REMOVE]
        add = [self.add_filter_template %
               dict(label= non_breaking_spaces(c.label),
                    url=escape(u'?' + urlencode(c.params)),
                    count=c.count)
               for c in choices if c.link_type == FILTER_ADD]

        return (self.filter_line_template %
                dict(label=escape(label),
                     remove=u''.join(remove),
                     add=u''.join(add),
                     separator=self.add_remove_separator if len(add) and len(remove) else u''))

    def render(self):
        return mark_safe(u'\n'.join(self.render_filter(f, self.qs, self.params) for f in self.filters))

    def get_fields(self):
        return self.fields

    def get_filter_for_field(self, field, **kwargs):
        f, model, direct, m2m = self.model._meta.get_field_by_name(field)
        if f.rel is not None:
            if m2m:
                klass = ManyToManyFilter
            else:
                klass = ForeignKeyFilter
        elif f.choices:
            klass = ChoicesFilter
        else:
            klass = ValuesFilter
        return klass(field, self.model, **kwargs)

    def setup_filters(self):
        filters = []
        for i, f in enumerate(self.get_fields()):
            if isinstance(f, basestring):
                f = self.get_filter_for_field(f)
            else:
                # (field name, FilterOptions)
                field = f[0]
                opts = f[1].__dict__.copy()
                f = self.get_filter_for_field(field, **opts)
            filters.append(f)
        return filters

    def __unicode__(self):
        return self.render()
<|endoftext|>


<commit_before>'''blox/compile.py

Creates an optimized programattically generated template from an html file

Copyright (C) 2015  Timothy Edmund Crosley

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

'''
import json
from blox.base import Blox, Text, UnsafeText
from blox.all import factory
from xml.dom import minidom
from lxml.etree import HTMLParser, parse, fromstring

parser = HTMLParser()
SCRIPT_TEMPLATE = """# WARNING: DON'T EDIT AUTO-GENERATED

from blox.base import Blox, Text, UnsafeText

class Template(Blox):
{indent}__slots__ = tuple({accessors})

def build(factory):
{indent}template = Template()
{indent}{build_steps}
{indent}return template
"""

def from_file(file_object):
    return parse(file_object, parser=parser).getroot()

def from_filename(file_name):
    return from_file(open(file_name))

def from_string(html):
    return fromstring(html)

def to_python(dom, factory=factory, indent='    '):
    current = [0]
    def increment(element_name=''):
        current[0] += 1
        return ('{0}{1}'.format(element_name, current[0]), factory.get(element_name))

    lines = []
    accessors = []
    def compile_node(node, parent='template'):
        blok_name, blok = increment(node.tag)
        lines.append("{0} = {1}(factory('{2}'))".format(blok_name, parent, node.tag))

        text = (node.text or "").strip().replace('"', '\\"')
        if text:
            if hasattr(blok, 'text'):
                lines.append('{0}.text = "{1}"'.format(blok_name, text))
            else:
                lines.append('{0}(Text("{1}"))'.format(blok_name, text))

        for attribute_name, attribute_value in node.items():
            lines.append('{0}["{1}"] = "{2}"'.format(blok_name, attribute_name.replace('"', '\\"'),
                                                     attribute_value.replace('"', '\\"')))
            if attribute_name == 'accessor':
                accessors.append(attribute_value)
                lines.apppend('{0}.{1} = {2}'.format(parent, attribute_value, blok_name))

        for child_node in node:
            if child_node.tag in getattr(blok, 'blok_attributes', {}):
                attached_child = "{0}.{1}".format(blok_name, blok.blok_attributes[child_node.tag].name)
                for nested_child_node in child_node:
                    compile_node(nested_child_node, parent=attached_child)
            else:
                compile_node(child_node, parent=blok_name)

            tail = (child_node.tail or "").strip().replace('"', '\\"')
            if tail:
                lines.append('{0}(Text("{1}"))'.format(blok_name, tail))
    compile_node(dom)
    return SCRIPT_TEMPLATE.format(accessors=json.dumps(accessors),
                                  build_steps="\n{indent}".join(lines).format(indent=indent),
                                  indent=indent)
<commit_msg>Fix setting text of child elements<commit_after>'''blox/compile.py

Creates an optimized programattically generated template from an html file

Copyright (C) 2015  Timothy Edmund Crosley

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

'''
import json
from blox.base import Blox, Text, UnsafeText
from blox.all import factory
from xml.dom import minidom
from lxml.etree import HTMLParser, parse, fromstring

parser = HTMLParser()
SCRIPT_TEMPLATE = """# WARNING: DON'T EDIT AUTO-GENERATED

from blox.base import Blox, Text, UnsafeText

class Template(Blox):
{indent}__slots__ = tuple({accessors})

def build(factory):
{indent}template = Template()
{indent}{build_steps}
{indent}return template
"""

def from_file(file_object):
    return parse(file_object, parser=parser).getroot()

def from_filename(file_name):
    return from_file(open(file_name))

def from_string(html):
    return fromstring(html)

def to_python(dom, factory=factory, indent='    '):
    current = [0]
    def increment(element_name=''):
        current[0] += 1
        return ('{0}{1}'.format(element_name, current[0]), factory.get(element_name))

    lines = []
    accessors = []
    def compile_node(node, parent='template'):
        print(node.tag)
        if node.tag == 'title':
            import ipdb;ipdb.set_trace()
        blok_name, blok = increment(node.tag)
        lines.append("{0} = {1}(factory('{2}'))".format(blok_name, parent, node.tag))

        text = (node.text or "").strip().replace('"', '\\"')
        if text:
            if hasattr(blok, 'text'):
                lines.append('{0}.text = "{1}"'.format(blok_name, text))
            else:
                lines.append('{0}(Text("{1}"))'.format(blok_name, text))

        for attribute_name, attribute_value in node.items():
            lines.append('{0}["{1}"] = "{2}"'.format(blok_name, attribute_name.replace('"', '\\"'),
                                                     attribute_value.replace('"', '\\"')))
            if attribute_name == 'accessor':
                accessors.append(attribute_value)
                lines.apppend('{0}.{1} = {2}'.format(parent, attribute_value, blok_name))

        for child_node in node:
            print(node.tag + " > " + child_node.tag)
            if child_node.tag in getattr(blok, 'blok_attributes', {}):
                attached_child = "{0}.{1}".format(blok_name, blok.blok_attributes[child_node.tag].name)
                for nested_child_node in child_node:
                    compile_node(nested_child_node, parent=attached_child)
                attached_text = (child_node.text or "").strip().replace('"', '\\"')
                if attached_text:
                    if hasattr(blok.blok_attributes[child_node.tag].type, 'text'):
                        lines.append('{0}.text = "{1}"'.format(attached_child, attached_text))
                    else:
                        lines.append('{0}(Text("{1}"))'.format(attached_child, attached_text))
            else:
                compile_node(child_node, parent=blok_name)

            tail = (child_node.tail or "").strip().replace('"', '\\"')
            if tail:
                lines.append('{0}(Text("{1}"))'.format(blok_name, tail))
    compile_node(dom)
    return SCRIPT_TEMPLATE.format(accessors=json.dumps(accessors),
                                  build_steps="\n{indent}".join(lines).format(indent=indent),
                                  indent=indent)
<|endoftext|>


<commit_before>#!/usr/bin/env python3
#
# Copyright (c) 2017 Weitian LI <liweitianux@live.com>
# MIT license

"""
Make image by binning the event file, and update the manifest.

TODO: use logging module instead of print()
"""

import sys
import argparse
import subprocess

from _context import acispy
from acispy.manifest import get_manifest
from acispy.pfiles import setup_pfiles
from acispy.acis import ACIS

def make_image(infile, outfile, chips, erange, fov, clobber=False):
    """
    Make image by binning the event file.

    Parameters
    ----------
    infile : str
        Path to the input event file
    outfile : str
        Filename and path of the output image file
    chips : str
        Chips of interest, e.g., ``7`` or ``0-3``
    erange : str
        Energy range of interest, e.g., ``700-7000``
    fov : str
        Path to the FoV file
    """
    chips = chips.replace("-", ":")
    erange = erange.replace("-", ":")
    clobber = "yes" if clobber else "no"
    fregion = "sky=region(%s[ccd_id=%s])" % (fov, chips)
    fenergy = "energy=%s" % erange
    fbin = "bin sky=::1"
    subprocess.check_call(["punlearn", "dmcopy"])
    subprocess.check_call([
        "dmcopy", "infile=%s[%s][%s][%s]" % (infile, fregion, fenergy, fbin),
        "outfile=%s" % outfile, "clobber=%s" % clobber
    ])

def main():
    parser = argparse.ArgumentParser(
        description="Make image by binning the event file")
    parser.add_argument("-L", "--elow", dest="elow", type=int, default=700,
                        help="lower energy limit [eV] of the output image " +
                        "(default: 700 [eV])")
    parser.add_argument("-H", "--ehigh", dest="ehigh", type=int,
                        help="upper energy limit [eV] of the output image " +
                        "(default: 7000 [eV])")
    parser.add_argument("-i", "--infile", dest="infile",
                        help="event file from which to create the image " +
                        "(default: evt2_clean from manifest)")
    parser.add_argument("-o", "--outfile", dest="outfile",
                        help="output image filename (default: " +
                        "build in format 'img_c<chip>_e<elow>-<ehigh>.fits')")
    parser.add_argument("-v", "--verbose", dest="verbose", action="store_true",
                        help="show verbose information")
    parser.add_argument("-C", "--clobber", dest="clobber", action="store_true",
                        help="overwrite existing file")
    args = parser.parse_args()

    setup_pfiles(["dmkeypar", "dmcopy"])

    manifest = get_manifest()
    fov = manifest.getpath("fov", relative=True)
    if args.infile:
        infile = args.infile
    else:
        infile = manifest.getpath("evt2_clean", relative=True)
    chips = ACIS.get_chips_str(infile, sep="-")
    erange = "{elow}-{ehigh}".format(elow=args.elow, ehigh=args.ehigh)
    if args.elow >= args.ehigh:
        raise ValueError("invalid energy range: %s" % erange)
    if args.outfile:
        outfile = args.outfile
    else:
        outfile = "img_c{chips}_e{erange}.fits".format(
            chips=chips, erange=erange)
    if args.verbose:
        print("infile:", infile, file=sys.stderr)
        print("outfile:", outfile, file=sys.stderr)
        print("fov:", fov, file=sys.stderr)
        print("chips:", chips, file=sys.stderr)
        print("erange:", erange, file=sys.stderr)

    make_image(infile, outfile, chips, erange, fov, args.clobber)

    # Add created image to manifest
    key = "img_e{erange}".format(erange=erange)
    manifest.setpath(key, outfile)

if __name__ == "__main__":
    main()
<commit_msg>event2image.py: Update keyword "DETNAM" of the created image<commit_after>#!/usr/bin/env python3
#
# Copyright (c) 2017 Weitian LI <liweitianux@live.com>
# MIT license

"""
Make image by binning the event file, and update the manifest.

TODO: use logging module instead of print()
"""

import sys
import argparse
import subprocess

from _context import acispy
from acispy.manifest import get_manifest
from acispy.pfiles import setup_pfiles
from acispy.acis import ACIS
from acispy.header import write_keyword

def make_image(infile, outfile, chips, erange, fov, clobber=False):
    """
    Make image by binning the event file.

    Parameters
    ----------
    infile : str
        Path to the input event file
    outfile : str
        Filename and path of the output image file
    chips : str
        Chips of interest, e.g., ``7`` or ``0-3``
    erange : str
        Energy range of interest, e.g., ``700-7000``
    fov : str
        Path to the FoV file
    """
    chips = chips.replace("-", ":")
    erange = erange.replace("-", ":")
    clobber = "yes" if clobber else "no"
    fregion = "sky=region(%s[ccd_id=%s])" % (fov, chips)
    fenergy = "energy=%s" % erange
    fbin = "bin sky=::1"
    subprocess.check_call(["punlearn", "dmcopy"])
    subprocess.check_call([
        "dmcopy", "infile=%s[%s][%s][%s]" % (infile, fregion, fenergy, fbin),
        "outfile=%s" % outfile, "clobber=%s" % clobber
    ])

def main():
    parser = argparse.ArgumentParser(
        description="Make image by binning the event file")
    parser.add_argument("-L", "--elow", dest="elow", type=int, default=700,
                        help="lower energy limit [eV] of the output image " +
                        "(default: 700 [eV])")
    parser.add_argument("-H", "--ehigh", dest="ehigh", type=int,
                        help="upper energy limit [eV] of the output image " +
                        "(default: 7000 [eV])")
    parser.add_argument("-i", "--infile", dest="infile",
                        help="event file from which to create the image " +
                        "(default: evt2_clean from manifest)")
    parser.add_argument("-o", "--outfile", dest="outfile",
                        help="output image filename (default: " +
                        "build in format 'img_c<chip>_e<elow>-<ehigh>.fits')")
    parser.add_argument("-v", "--verbose", dest="verbose", action="store_true",
                        help="show verbose information")
    parser.add_argument("-C", "--clobber", dest="clobber", action="store_true",
                        help="overwrite existing file")
    args = parser.parse_args()

    setup_pfiles(["dmkeypar", "dmcopy"])

    manifest = get_manifest()
    fov = manifest.getpath("fov", relative=True)
    if args.infile:
        infile = args.infile
    else:
        infile = manifest.getpath("evt2_clean", relative=True)
    chips = ACIS.get_chips_str(infile, sep="-")
    erange = "{elow}-{ehigh}".format(elow=args.elow, ehigh=args.ehigh)
    if args.elow >= args.ehigh:
        raise ValueError("invalid energy range: %s" % erange)
    if args.outfile:
        outfile = args.outfile
    else:
        outfile = "img_c{chips}_e{erange}.fits".format(
            chips=chips, erange=erange)
    if args.verbose:
        print("infile:", infile, file=sys.stderr)
        print("outfile:", outfile, file=sys.stderr)
        print("fov:", fov, file=sys.stderr)
        print("chips:", chips, file=sys.stderr)
        print("erange:", erange, file=sys.stderr)

    make_image(infile, outfile, chips, erange, fov, args.clobber)
    chips_all = ACIS.get_chips_str(infile)
    write_keyword(outfile, keyword="DETNAM",
                  value="ACIS-{0}".format(chips_all))

    # Add created image to manifest
    key = "img_e{erange}".format(erange=erange)
    manifest.setpath(key, outfile)

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