<commit_before>#!/usr/bin/python
# -*- coding: utf-8 -*-

# thumbor imaging service
# https://github.com/globocom/thumbor/wiki

# Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license
# Copyright (c) 2011 globo.com timehome@corp.globo.com

from collections import defaultdict
import os
from os.path import join, exists, expanduser, dirname, abspath
import imp
import tempfile
from textwrap import fill

class ConfigurationError(RuntimeError):
    pass

class Config(object):
    class_defaults = {}
    class_group_items = defaultdict(list)
    class_groups = []
    class_descriptions = {}

    class_aliases = defaultdict(list)
    class_aliased_items = {}

    @classmethod
    def define(cls, key, value, description, group='General'):
        cls.class_defaults[key] = value
        cls.class_descriptions[key] = description
        cls.class_group_items[group].append(key)
        if not group in cls.class_groups:
            cls.class_groups.append(group)

    @classmethod
    def alias(cls, new_key, aliased_key):
        if aliased_key in cls.class_aliased_items:
            aliased_key = cls.class_aliased_items[aliased_key]
        cls.class_aliases[aliased_key].append(new_key)
        cls.class_aliased_items[new_key] = aliased_key

    @classmethod
    def get_conf_file(cls):
        lookup_conf_file_paths = [
            os.curdir,
            expanduser('~'),
            '/etc/',
            dirname(__file__)
        ]
        for conf_path in lookup_conf_file_paths:
            conf_path_file = abspath(join(conf_path, 'thumbor.conf'))
            if exists(conf_path_file):
                return conf_path_file

        raise ConfigurationError('thumbor.conf file not passed and not found on the lookup paths %s' % lookup_conf_file_paths)

    @classmethod
    def load(cls, path):
        if path is None:
            path = cls.get_conf_file()

        with open(path) as config_file:
            name='configuration'
            code = config_file.read()
            module = imp.new_module(name)
            exec code in module.__dict__

            conf = cls()
            conf.config_file = path
            for name, value in module.__dict__.iteritems():
                setattr(conf, name, value)

            return conf

    def __init__(self, **kw):
        if 'defaults' in kw:
            self.defaults = kw['defaults']

        for key, value in kw.iteritems():
            setattr(self, key, value)

    def validates_presence_of(self, *args):
        for arg in args:
            if not hasattr(self, arg):
                raise ConfigurationError('Configuration %s was not found and does not have a default value. Please verify your thumbor.conf file' % arg)

    def get(self, name, default=None):
        if hasattr(self, name):
            return getattr(self, name)
        return default

    def __setattr__(self, name, value):
        if name in Config.class_aliased_items:
            self.__setattr__(Config.class_aliased_items[name], value)
        else:
            super(Config, self).__setattr__(name, value)

    def __getattr__(self, name):
        if name in self.__dict__:
            return self.__dict__[name]

        if name in Config.class_aliased_items:
            return self.__getattr__(Config.class_aliased_items[name])

        if 'defaults' in self.__dict__ and name in self.__dict__['defaults']:
            return self.__dict__['defaults'][name]

        if name in Config.class_defaults:
            return Config.class_defaults[name]

        raise AttributeError(name)

Config.define('MAX_WIDTH', 0, "Max width in pixels for images read or generated by thumbor", 'Imaging')
Config.define('MAX_HEIGHT', 0, "Max height in pixels for images read or generated by thumbor", 'Imaging')
Config.define('MIN_WIDTH', 1, "Min width in pixels for images read or generated by thumbor", 'Imaging')
Config.define('MIN_HEIGHT', 1, "Min width in pixels for images read or generated by thumbor", 'Imaging')
Config.define('ALLOWED_SOURCES', [], "Allowed domains for the http loader to download. These are regular expressions.", 'Imaging')
Config.define('QUALITY', 80, 'Quality index used for generated JPEG images', 'Imaging')
Config.define('MAX_AGE', 24 * 60 * 60, 'Max AGE sent as a header for the image served by thumbor in seconds', 'Imaging')
Config.define('MAX_AGE_TEMP_IMAGE', 0, "Indicates the Max AGE header in seconds for temporary images (images that haven't been detected yet)", 'Imaging')
Config.define('RESPECT_ORIENTATION', False, 'Indicates whether thumbor should rotate images that have an Orientation EXIF header', 'Imaging')

Config.define('LOADER',  'thumbor.loaders.http_loader', 'The loader thumbor should use to load the original image. This must be the full name of a python module (python must be able to import it)', 'Extensibility')
Config.define('STORAGE', 'thumbor.storages.file_storage', 'The file storage thumbor should use to store original images. This must be the full name of a python module (python must be able to import it)', 'Extensibility')
Config.define('RESULT_STORAGE', None, 'The result storage thumbor should use to store generated images. This must be the full name of a python module (python must be able to import it)', 'Extensibility')
Config.define('ENGINE', 'thumbor.engines.pil', 'The imaging engine thumbor should use to perform image operations. This must be the full name of a python module (python must be able to import it)', 'Extensibility')

Config.define('SECURITY_KEY', 'MY_SECURE_KEY', 'The security key thumbor uses to sign image URLs', 'Security')

Config.define('ALLOW_UNSAFE_URL', True, 'Indicates if the /unsafe URL should be available', 'Security')
Config.define('ALLOW_OLD_URLS', True, 'Indicates if encrypted (old style) URLs should be allowed', 'Security')

# FILE LOADER OPTIONS
Config.define('FILE_LOADER_ROOT_PATH', '/tmp', 'The root path where the File Loader will try to find images', 'File Loader')

# HTTP LOADER OPTIONS
Config.define('MAX_SOURCE_SIZE', 0, "Max size in Kb for images downloaded by thumbor's HTTP Loader", 'HTTP Loader')
Config.define('REQUEST_TIMEOUT_SECONDS', 120, "Maximum number of seconds to wait for an original image to download", 'HTTP Loader')

# FILE STORAGE GENERIC OPTIONS
Config.define('STORAGE_EXPIRATION_SECONDS', 60 * 60 * 24 * 30, "Expiration in seconds for the images in the File Storage. Defaults to one month", 'File Storage') # default one month
Config.define('STORES_CRYPTO_KEY_FOR_EACH_IMAGE', False, 'Indicates whether thumbor should store the signing key for each image in the file storage. This allows the key to be changed and old images to still be properly found', 'File Storage')

# FILE STORAGE OPTIONS
Config.define('FILE_STORAGE_ROOT_PATH', join(tempfile.gettempdir(), 'thumbor', 'storage'), 'The root path where the File Storage will try to find images', 'File Storage')

# PHOTO UPLOAD OPTIONS
Config.define('UPLOAD_MAX_SIZE', 0, "Max size in Kb for images uploaded to thumbor", 'Upload')
Config.define('UPLOAD_ENABLED', False, 'Indicates whether thumbor should enable File uploads', 'Upload')
Config.define('UPLOAD_PHOTO_STORAGE', 'thumbor.storages.file_storage', 'The type of storage to store uploaded images with', 'Upload')
Config.define('UPLOAD_DELETE_ALLOWED', False, 'Indicates whether image deletion should be allowed', 'Upload')
Config.define('UPLOAD_PUT_ALLOWED', False, 'Indicates whether image overwrite should be allowed', 'Upload')

# MONGO STORAGE OPTIONS
Config.define('MONGO_STORAGE_SERVER_HOST', 'localhost', 'MongoDB storage server host', 'MongoDB Storage')
Config.define('MONGO_STORAGE_SERVER_PORT', 27017, 'MongoDB storage server port', 'MongoDB Storage')
Config.define('MONGO_STORAGE_SERVER_DB', 'thumbor', 'MongoDB storage server database name', 'MongoDB Storage')
Config.define('MONGO_STORAGE_SERVER_COLLECTION', 'images', 'MongoDB storage image collection', 'MongoDB Storage')

# REDIS STORAGE OPTIONS
Config.define('REDIS_STORAGE_SERVER_HOST', 'localhost', 'Redis storage server host', 'Redis Storage')
Config.define('REDIS_STORAGE_SERVER_PORT', 6379, 'Redis storage server port', 'Redis Storage')
Config.define('REDIS_STORAGE_SERVER_DB', 0, 'Redis storage database index', 'Redis Storage')
Config.define('REDIS_STORAGE_SERVER_PASSWORD', None, 'Redis storage server password', 'Redis Storage')

# MIXED STORAGE OPTIONS
Config.define('MIXED_STORAGE_FILE_STORAGE', 'thumbor.storages.no_storage', 'Mixed Storage file storage. This must be the full name of a python module (python must be able to import it)', 'Mixed Storage')
Config.define('MIXED_STORAGE_CRYPTO_STORAGE', 'thumbor.storages.no_storage', 'Mixed Storage signing key storage. This must be the full name of a python module (python must be able to import it)', 'Mixed Storage')
Config.define('MIXED_STORAGE_DETECTOR_STORAGE', 'thumbor.storages.no_storage', 'Mixed Storage detector information storage. This must be the full name of a python module (python must be able to import it)', 'Mixed Storage')

# JSON META ENGINE OPTIONS
Config.define('META_CALLBACK_NAME', None, 'The callback function name that should be used by the META route for JSONP access', 'Meta')

# DETECTORS OPTIONS
Config.define('DETECTORS', [], 'List of detectors that thumbor should use to find faces and/or features. All of them must be full names of python modules (python must be able to import it)', 'Detection')

# FACE DETECTOR CASCADE FILE
Config.define('FACE_DETECTOR_CASCADE_FILE', 'haarcascade_frontalface_alt.xml', 'The cascade file that opencv will use to detect faces', 'Detection')

# AVAILABLE FILTERS
Config.define('FILTERS', [], 'List of filters that thumbor will allow to be used in generated images. All of them must be full names of python modules (python must be able to import it)', 'Filters')

# RESULT STORAGE
Config.define('RESULT_STORAGE_EXPIRATION_SECONDS', 0, 'Expiration in seconds of generated images in the result storage', 'Result Storage') # Never expires
Config.define('RESULT_STORAGE_FILE_STORAGE_ROOT_PATH', join(tempfile.gettempdir(), 'thumbor', 'result_storage'), 'Path where the Result storage will store generated images', 'Result Storage')
Config.define('RESULT_STORAGE_STORES_UNSAFE', False, 'Indicates whether unsafe requests should also be stored in the Result Storage', 'Result Storage')

# QUEUED DETECTOR REDIS OPTIONS
Config.define('REDIS_QUEUE_SERVER_HOST', 'localhost', 'Server host for the queued redis detector', 'Queued Redis Detector')
Config.define('REDIS_QUEUE_SERVER_PORT', 6379, 'Server port for the queued redis detector', 'Queued Redis Detector')
Config.define('REDIS_QUEUE_SERVER_DB', 0, 'Server database index for the queued redis detector', 'Queued Redis Detector')
Config.define('REDIS_QUEUE_SERVER_PASSWORD', None, 'Server password for the queued redis detector', 'Queued Redis Detector')

def generate_config():
    MAX_LEN = 80
    SEPARATOR = '#'
    for group in Config.class_groups:
        keys = Config.class_group_items[group]
        sep_size = int(round((MAX_LEN - len(group)) / 2, 0)) - 1
        group_name = SEPARATOR * sep_size + ' ' + group + ' ' + SEPARATOR * sep_size
        if len(group_name) < MAX_LEN:
            group_name += SEPARATOR
        print group_name
        for key in keys:
            print
            value = Config.class_defaults[key]
            description = Config.class_descriptions[key]

            wrapped = fill(description, width=78, subsequent_indent='## ')

            print '## %s' % wrapped
            if key in Config.class_aliases:
                print '## Aliases: %s' % ', '.join(Config.class_aliases[key])
            print '## Defaults to: %s' % value
            print '#%s = %s' % (key, format_value(value))
        print
        print SEPARATOR * MAX_LEN
        print
        print

def format_value(value):
    if isinstance(value, basestring):
        return "'%s'" % value
    if isinstance(value, (tuple, list, set)):
        representation = '[\n'
        for item in value:
            representation += '#    %s' % item
        representation += '#]'
        return representation
    return value

if __name__ == '__main__':
    generate_config()
<commit_msg>Adding aliases for old photo upload options<commit_after>#!/usr/bin/python
# -*- coding: utf-8 -*-

# thumbor imaging service
# https://github.com/globocom/thumbor/wiki

# Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license
# Copyright (c) 2011 globo.com timehome@corp.globo.com

from collections import defaultdict
import os
from os.path import join, exists, expanduser, dirname, abspath
import imp
import tempfile
from textwrap import fill

class ConfigurationError(RuntimeError):
    pass

class Config(object):
    class_defaults = {}
    class_group_items = defaultdict(list)
    class_groups = []
    class_descriptions = {}

    class_aliases = defaultdict(list)
    class_aliased_items = {}

    @classmethod
    def define(cls, key, value, description, group='General'):
        cls.class_defaults[key] = value
        cls.class_descriptions[key] = description
        cls.class_group_items[group].append(key)
        if not group in cls.class_groups:
            cls.class_groups.append(group)

    @classmethod
    def alias(cls, new_key, aliased_key):
        if aliased_key in cls.class_aliased_items:
            aliased_key = cls.class_aliased_items[aliased_key]
        cls.class_aliases[aliased_key].append(new_key)
        cls.class_aliased_items[new_key] = aliased_key

    @classmethod
    def get_conf_file(cls):
        lookup_conf_file_paths = [
            os.curdir,
            expanduser('~'),
            '/etc/',
            dirname(__file__)
        ]
        for conf_path in lookup_conf_file_paths:
            conf_path_file = abspath(join(conf_path, 'thumbor.conf'))
            if exists(conf_path_file):
                return conf_path_file

        raise ConfigurationError('thumbor.conf file not passed and not found on the lookup paths %s' % lookup_conf_file_paths)

    @classmethod
    def load(cls, path):
        if path is None:
            path = cls.get_conf_file()

        with open(path) as config_file:
            name='configuration'
            code = config_file.read()
            module = imp.new_module(name)
            exec code in module.__dict__

            conf = cls()
            conf.config_file = path
            for name, value in module.__dict__.iteritems():
                setattr(conf, name, value)

            return conf

    def __init__(self, **kw):
        if 'defaults' in kw:
            self.defaults = kw['defaults']

        for key, value in kw.iteritems():
            setattr(self, key, value)

    def validates_presence_of(self, *args):
        for arg in args:
            if not hasattr(self, arg):
                raise ConfigurationError('Configuration %s was not found and does not have a default value. Please verify your thumbor.conf file' % arg)

    def get(self, name, default=None):
        if hasattr(self, name):
            return getattr(self, name)
        return default

    def __setattr__(self, name, value):
        if name in Config.class_aliased_items:
            self.__setattr__(Config.class_aliased_items[name], value)
        else:
            super(Config, self).__setattr__(name, value)

    def __getattr__(self, name):
        if name in self.__dict__:
            return self.__dict__[name]

        if name in Config.class_aliased_items:
            return self.__getattr__(Config.class_aliased_items[name])

        if 'defaults' in self.__dict__ and name in self.__dict__['defaults']:
            return self.__dict__['defaults'][name]

        if name in Config.class_defaults:
            return Config.class_defaults[name]

        raise AttributeError(name)

Config.define('MAX_WIDTH', 0, "Max width in pixels for images read or generated by thumbor", 'Imaging')
Config.define('MAX_HEIGHT', 0, "Max height in pixels for images read or generated by thumbor", 'Imaging')
Config.define('MIN_WIDTH', 1, "Min width in pixels for images read or generated by thumbor", 'Imaging')
Config.define('MIN_HEIGHT', 1, "Min width in pixels for images read or generated by thumbor", 'Imaging')
Config.define('ALLOWED_SOURCES', [], "Allowed domains for the http loader to download. These are regular expressions.", 'Imaging')
Config.define('QUALITY', 80, 'Quality index used for generated JPEG images', 'Imaging')
Config.define('MAX_AGE', 24 * 60 * 60, 'Max AGE sent as a header for the image served by thumbor in seconds', 'Imaging')
Config.define('MAX_AGE_TEMP_IMAGE', 0, "Indicates the Max AGE header in seconds for temporary images (images that haven't been detected yet)", 'Imaging')
Config.define('RESPECT_ORIENTATION', False, 'Indicates whether thumbor should rotate images that have an Orientation EXIF header', 'Imaging')

Config.define('LOADER',  'thumbor.loaders.http_loader', 'The loader thumbor should use to load the original image. This must be the full name of a python module (python must be able to import it)', 'Extensibility')
Config.define('STORAGE', 'thumbor.storages.file_storage', 'The file storage thumbor should use to store original images. This must be the full name of a python module (python must be able to import it)', 'Extensibility')
Config.define('RESULT_STORAGE', None, 'The result storage thumbor should use to store generated images. This must be the full name of a python module (python must be able to import it)', 'Extensibility')
Config.define('ENGINE', 'thumbor.engines.pil', 'The imaging engine thumbor should use to perform image operations. This must be the full name of a python module (python must be able to import it)', 'Extensibility')

Config.define('SECURITY_KEY', 'MY_SECURE_KEY', 'The security key thumbor uses to sign image URLs', 'Security')

Config.define('ALLOW_UNSAFE_URL', True, 'Indicates if the /unsafe URL should be available', 'Security')
Config.define('ALLOW_OLD_URLS', True, 'Indicates if encrypted (old style) URLs should be allowed', 'Security')

# FILE LOADER OPTIONS
Config.define('FILE_LOADER_ROOT_PATH', '/tmp', 'The root path where the File Loader will try to find images', 'File Loader')

# HTTP LOADER OPTIONS
Config.define('MAX_SOURCE_SIZE', 0, "Max size in Kb for images downloaded by thumbor's HTTP Loader", 'HTTP Loader')
Config.define('REQUEST_TIMEOUT_SECONDS', 120, "Maximum number of seconds to wait for an original image to download", 'HTTP Loader')

# FILE STORAGE GENERIC OPTIONS
Config.define('STORAGE_EXPIRATION_SECONDS', 60 * 60 * 24 * 30, "Expiration in seconds for the images in the File Storage. Defaults to one month", 'File Storage') # default one month
Config.define('STORES_CRYPTO_KEY_FOR_EACH_IMAGE', False, 'Indicates whether thumbor should store the signing key for each image in the file storage. This allows the key to be changed and old images to still be properly found', 'File Storage')

# FILE STORAGE OPTIONS
Config.define('FILE_STORAGE_ROOT_PATH', join(tempfile.gettempdir(), 'thumbor', 'storage'), 'The root path where the File Storage will try to find images', 'File Storage')

# PHOTO UPLOAD OPTIONS
Config.define('UPLOAD_MAX_SIZE', 0, "Max size in Kb for images uploaded to thumbor", 'Upload')
Config.define('UPLOAD_ENABLED', False, 'Indicates whether thumbor should enable File uploads', 'Upload')
Config.define('UPLOAD_PHOTO_STORAGE', 'thumbor.storages.file_storage', 'The type of storage to store uploaded images with', 'Upload')
Config.define('UPLOAD_DELETE_ALLOWED', False, 'Indicates whether image deletion should be allowed', 'Upload')
Config.define('UPLOAD_PUT_ALLOWED', False, 'Indicates whether image overwrite should be allowed', 'Upload')

# ALIASES FOR OLD PHOTO UPLOAD OPTIONS
Config.alias('MAX_SIZE', 'UPLOAD_MAX_SIZE')
Config.alias('ENABLE_ORIGINAL_PHOTO_UPLOAD', 'UPLOAD_ENABLED')
Config.alias('ORIGINAL_PHOTO_STORAGE', 'UPLOAD_PHOTO_STORAGE')
Config.alias('ALLOW_ORIGINAL_PHOTO_DELETION', 'UPLOAD_DELETE_ALLOWED')
Config.alias('ALLOW_ORIGINAL_PHOTO_PUTTING', 'UPLOAD_PUT_ALLOWED')

# MONGO STORAGE OPTIONS
Config.define('MONGO_STORAGE_SERVER_HOST', 'localhost', 'MongoDB storage server host', 'MongoDB Storage')
Config.define('MONGO_STORAGE_SERVER_PORT', 27017, 'MongoDB storage server port', 'MongoDB Storage')
Config.define('MONGO_STORAGE_SERVER_DB', 'thumbor', 'MongoDB storage server database name', 'MongoDB Storage')
Config.define('MONGO_STORAGE_SERVER_COLLECTION', 'images', 'MongoDB storage image collection', 'MongoDB Storage')

# REDIS STORAGE OPTIONS
Config.define('REDIS_STORAGE_SERVER_HOST', 'localhost', 'Redis storage server host', 'Redis Storage')
Config.define('REDIS_STORAGE_SERVER_PORT', 6379, 'Redis storage server port', 'Redis Storage')
Config.define('REDIS_STORAGE_SERVER_DB', 0, 'Redis storage database index', 'Redis Storage')
Config.define('REDIS_STORAGE_SERVER_PASSWORD', None, 'Redis storage server password', 'Redis Storage')

# MIXED STORAGE OPTIONS
Config.define('MIXED_STORAGE_FILE_STORAGE', 'thumbor.storages.no_storage', 'Mixed Storage file storage. This must be the full name of a python module (python must be able to import it)', 'Mixed Storage')
Config.define('MIXED_STORAGE_CRYPTO_STORAGE', 'thumbor.storages.no_storage', 'Mixed Storage signing key storage. This must be the full name of a python module (python must be able to import it)', 'Mixed Storage')
Config.define('MIXED_STORAGE_DETECTOR_STORAGE', 'thumbor.storages.no_storage', 'Mixed Storage detector information storage. This must be the full name of a python module (python must be able to import it)', 'Mixed Storage')

# JSON META ENGINE OPTIONS
Config.define('META_CALLBACK_NAME', None, 'The callback function name that should be used by the META route for JSONP access', 'Meta')

# DETECTORS OPTIONS
Config.define('DETECTORS', [], 'List of detectors that thumbor should use to find faces and/or features. All of them must be full names of python modules (python must be able to import it)', 'Detection')

# FACE DETECTOR CASCADE FILE
Config.define('FACE_DETECTOR_CASCADE_FILE', 'haarcascade_frontalface_alt.xml', 'The cascade file that opencv will use to detect faces', 'Detection')

# AVAILABLE FILTERS
Config.define('FILTERS', [], 'List of filters that thumbor will allow to be used in generated images. All of them must be full names of python modules (python must be able to import it)', 'Filters')

# RESULT STORAGE
Config.define('RESULT_STORAGE_EXPIRATION_SECONDS', 0, 'Expiration in seconds of generated images in the result storage', 'Result Storage') # Never expires
Config.define('RESULT_STORAGE_FILE_STORAGE_ROOT_PATH', join(tempfile.gettempdir(), 'thumbor', 'result_storage'), 'Path where the Result storage will store generated images', 'Result Storage')
Config.define('RESULT_STORAGE_STORES_UNSAFE', False, 'Indicates whether unsafe requests should also be stored in the Result Storage', 'Result Storage')

# QUEUED DETECTOR REDIS OPTIONS
Config.define('REDIS_QUEUE_SERVER_HOST', 'localhost', 'Server host for the queued redis detector', 'Queued Redis Detector')
Config.define('REDIS_QUEUE_SERVER_PORT', 6379, 'Server port for the queued redis detector', 'Queued Redis Detector')
Config.define('REDIS_QUEUE_SERVER_DB', 0, 'Server database index for the queued redis detector', 'Queued Redis Detector')
Config.define('REDIS_QUEUE_SERVER_PASSWORD', None, 'Server password for the queued redis detector', 'Queued Redis Detector')

def generate_config():
    MAX_LEN = 80
    SEPARATOR = '#'
    for group in Config.class_groups:
        keys = Config.class_group_items[group]
        sep_size = int(round((MAX_LEN - len(group)) / 2, 0)) - 1
        group_name = SEPARATOR * sep_size + ' ' + group + ' ' + SEPARATOR * sep_size
        if len(group_name) < MAX_LEN:
            group_name += SEPARATOR
        print group_name
        for key in keys:
            print
            value = Config.class_defaults[key]
            description = Config.class_descriptions[key]

            wrapped = fill(description, width=78, subsequent_indent='## ')

            print '## %s' % wrapped
            if key in Config.class_aliases:
                print '## Aliases: %s' % ', '.join(Config.class_aliases[key])
            print '## Defaults to: %s' % value
            print '#%s = %s' % (key, format_value(value))
        print
        print SEPARATOR * MAX_LEN
        print
        print

def format_value(value):
    if isinstance(value, basestring):
        return "'%s'" % value
    if isinstance(value, (tuple, list, set)):
        representation = '[\n'
        for item in value:
            representation += '#    %s' % item
        representation += '#]'
        return representation
    return value

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


<commit_before>import sys, re, os
from toc import TOCElement

try:
    import elementtree.ElementTree as et
except:
    raise "This module requires ElementTree to run (http://effbot.org/zone/element-index.htm)"

import markdown

def dump_tree(elem, stream):
    if elem.tag.startswith('MYGHTY:'):
        dump_myghty_tag(elem, stream)
    else:
        if len(elem.attrib):
            stream.write("<%s %s>" % (elem.tag, " ".join(["%s=%s" % (key, repr(val)) for key, val in elem.attrib.iteritems()])))
        else:
            stream.write("<%s>" % elem.tag)
        if elem.text:
            stream.write(elem.text)
        for child in elem:
            dump_tree(child, stream)
            if child.tail:
                stream.write(child.tail)
        stream.write("</%s>" % elem.tag)

def dump_myghty_tag(elem, stream):
    tag = elem.tag[7:]
    params = ', '.join(['%s=%s' % i for i in elem.items()])
    pipe = ''
    if elem.text or len(elem):
        pipe = '|'
    comma = ''
    if params:
        comma = ', '
    stream.write('<&%s%s%s%s&>' % (pipe, tag, comma, params))
    if pipe:
        if elem.text:
            stream.write(elem.text)
        for n in elem:
            dump_tree(n, stream)
            if n.tail:
                stream.write(n.tail)
        stream.write("</&>")

def create_toc(filename, tree, tocroot):
    title = [None]
    current = [tocroot]
    level = [0]
    def process(tree):
        while True:
            i = find_header_index(tree)
            if i is None:
                return
            node = tree[i]
            taglevel = int(node.tag[1])
            start, end = i, end_of_header(tree, taglevel, i+1)
            content = tree[start+1:end]
            description = node.text.strip()
            if title[0] is None:
                title[0] = description
            name = node.get('name')
            if name is None:
                name = description.split()[0].lower()

            taglevel = node.tag[1]
            if taglevel > level[0]:
                current[0] = TOCElement(filename, name, description, current[0])
            elif taglevel == level[0]:
                current[0] = TOCElement(filename, name, description, current[0].parent)
            else:
                current[0] = TOCElement(filename, name, description, current[0].parent.parent)

            level[0] = taglevel

            tag = et.Element("MYGHTY:formatting.myt:section", path=literal(current[0].path), toc="toc")
            tag.text = (node.tail or "") + '\n'
            tag.tail = '\n'
            tag[:] = content
            tree[start:end] = [tag]

            process(tag)

    process(tree)
    return (title[0], tocroot.get_by_file(filename))

def literal(s):
    return '"%s"' % s

def index(parent, item):
    for n, i in enumerate(parent):
        if i is item:
            return n

def find_header_index(tree):
    for i, node in enumerate(tree):
        if is_header(node):
            return i

def is_header(node):
    t = node.tag
    return (isinstance(t, str) and len(t) == 2 and t[0] == 'h'
            and t[1] in '123456789')

def end_of_header(tree, level, start):
    for i, node in enumerate(tree[start:]):
        if is_header(node) and int(node.tag[1]) <= level:
            return start + i
    return len(tree)

def process_rel_href(tree):
    parent = get_parent_map(tree)
    for a in tree.findall('.//a'):
        m = re.match(r'(bold)?rel\:(.+)', a.get('href'))
        if m:
            (bold, path) = m.group(1,2)
            text = a.text
            if text == path:
                tag = et.Element("MYGHTY:nav.myt:toclink", path=literal(path), toc="toc", extension="extension")
            else:
                tag = et.Element("MYGHTY:nav.myt:toclink", path=literal(path), description=literal(text), toc="toc", extension="extension")
            a_parent = parent[a]
            if bold:
                bold = et.Element('strong')
                bold.tail = a.tail
                bold.append(tag)
                a_parent[index(a_parent, a)] = bold
            else:
                tag.tail = a.tail
                a_parent[index(a_parent, a)] = tag

def replace_pre_with_myt(tree):
    def splice_code_tag(pre, text, type=None, title=None):
        doctest_directives = re.compile(r'#\s*doctest:\s*[+-]\w+(,[+-]\w+)*\s*$', re.M)
        text = re.sub(doctest_directives, '', text)
        # process '>>>' to have quotes around it, to work with the myghty python
        # syntax highlighter which uses the tokenize module
        text = re.sub(r'>>> ', r'">>>" ', text)

        # indent two spaces.  among other things, this helps comment lines "#  " from being
        # consumed as Myghty comments.
        text = re.compile(r'^(?!<&)', re.M).sub('  ', text)

        sqlre = re.compile(r'{sql}(.*?)((?:SELECT|INSERT|DELETE|UPDATE|CREATE|DROP|PRAGMA|DESCRIBE).*?)\n\s*(\n|$)', re.S)
        if sqlre.search(text) is not None:
            use_sliders = False
        else:
            use_sliders = True

        text = sqlre.sub(r"<&formatting.myt:poplink&>\1\n<&|formatting.myt:codepopper, link='sql'&>\2</&>\n\n", text)

        sqlre2 = re.compile(r'{opensql}(.*?)((?:SELECT|INSERT|DELETE|UPDATE|CREATE|DROP).*?)\n\s*(\n|$)', re.S)
        text = sqlre2.sub(r"<&|formatting.myt:poppedcode &>\1\n\2</&>\n\n", text)

        opts = {}
        if type == 'python':
            opts['syntaxtype'] = literal('python')
        else:
            opts['syntaxtype'] = None

        if title is not None:
            opts['title'] = literal(title)

        if use_sliders:
            opts['use_sliders'] = True

        tag = et.Element("MYGHTY:formatting.myt:code", **opts)
        tag.text = text

        pre_parent = parents[pre]
        tag.tail = pre.tail
        pre_parent[reverse_parent(pre_parent, pre)] = tag

    parents = get_parent_map(tree)

    for precode in tree.findall('.//pre/code'):
        m = re.match(r'\{(python|code)(?: title="(.*?)"){0,1}\}', precode.text.lstrip())
        if m:
            code = m.group(1)
            title = m.group(2)
            text = precode.text.lstrip()
            text = re.sub(r'{(python|code).*?}(\n\s*)?', '', text)
            splice_code_tag(parents[precode], text, type=code, title=title)
        elif precode.text.lstrip().startswith('>>> '):
            splice_code_tag(parents[precode], precode.text)

def reverse_parent(parent, item):
    for n, i in enumerate(parent):
        if i is item:
            return n

def get_parent_map(tree):
    return dict([(c, p) for p in tree.getiterator() for c in p])

def header(toc, title, filename):
    return """#encoding: utf-8
<%%flags>
    inherit='content_layout.myt'
</%%flags>
<%%args>
    toc
    extension
</%%args>
<%%attr>
    title='%s - %s'
    filename = '%s'
</%%attr>
<%%doc>This file is generated.  Edit the .txt files instead of this one.</%%doc>
""" % (toc.root.doctitle, title, filename)

class utf8stream(object):
    def __init__(self, stream):
        self.stream = stream
    def write(self, str):
        self.stream.write(str.encode('utf8'))

def parse_markdown_files(toc, files):
    for inname in files:
        infile = 'content/%s.txt' % inname
        if not os.access(infile, os.F_OK):
            continue
        html = markdown.markdown(file(infile).read())
        tree = et.fromstring("" + html + "")
        (title, toc_element) = create_toc(inname, tree, toc)
        replace_pre_with_myt(tree)
        process_rel_href(tree)
        outname = 'output/%s.myt' % inname
        print infile, '->', outname
        outfile = utf8stream(file(outname, 'w'))
        outfile.write(header(toc, title, inname))
        dump_tree(tree, outfile)

<commit_msg>docstring<commit_after>"""loads Markdown files, converts each one to HTML and parses the HTML into an ElementTree structure.
The collection of ElementTrees are further parsed to generate a table of contents structure, and are
 manipulated to replace various markdown-generated HTML with specific Myghty tags before being written
 to Myghty templates, which then re-access the table of contents structure at runtime.

Much thanks to Alexey Shamrin, who came up with the original idea and did all the heavy Markdown/Elementtree
lifting for this module."""
import sys, re, os
from toc import TOCElement

try:
    import elementtree.ElementTree as et
except:
    raise "This module requires ElementTree to run (http://effbot.org/zone/element-index.htm)"

import markdown

def dump_tree(elem, stream):
    if elem.tag.startswith('MYGHTY:'):
        dump_myghty_tag(elem, stream)
    else:
        if len(elem.attrib):
            stream.write("<%s %s>" % (elem.tag, " ".join(["%s=%s" % (key, repr(val)) for key, val in elem.attrib.iteritems()])))
        else:
            stream.write("<%s>" % elem.tag)
        if elem.text:
            stream.write(elem.text)
        for child in elem:
            dump_tree(child, stream)
            if child.tail:
                stream.write(child.tail)
        stream.write("</%s>" % elem.tag)

def dump_myghty_tag(elem, stream):
    tag = elem.tag[7:]
    params = ', '.join(['%s=%s' % i for i in elem.items()])
    pipe = ''
    if elem.text or len(elem):
        pipe = '|'
    comma = ''
    if params:
        comma = ', '
    stream.write('<&%s%s%s%s&>' % (pipe, tag, comma, params))
    if pipe:
        if elem.text:
            stream.write(elem.text)
        for n in elem:
            dump_tree(n, stream)
            if n.tail:
                stream.write(n.tail)
        stream.write("</&>")

def create_toc(filename, tree, tocroot):
    title = [None]
    current = [tocroot]
    level = [0]
    def process(tree):
        while True:
            i = find_header_index(tree)
            if i is None:
                return
            node = tree[i]
            taglevel = int(node.tag[1])
            start, end = i, end_of_header(tree, taglevel, i+1)
            content = tree[start+1:end]
            description = node.text.strip()
            if title[0] is None:
                title[0] = description
            name = node.get('name')
            if name is None:
                name = description.split()[0].lower()

            taglevel = node.tag[1]
            if taglevel > level[0]:
                current[0] = TOCElement(filename, name, description, current[0])
            elif taglevel == level[0]:
                current[0] = TOCElement(filename, name, description, current[0].parent)
            else:
                current[0] = TOCElement(filename, name, description, current[0].parent.parent)

            level[0] = taglevel

            tag = et.Element("MYGHTY:formatting.myt:section", path=literal(current[0].path), toc="toc")
            tag.text = (node.tail or "") + '\n'
            tag.tail = '\n'
            tag[:] = content
            tree[start:end] = [tag]

            process(tag)

    process(tree)
    return (title[0], tocroot.get_by_file(filename))

def literal(s):
    return '"%s"' % s

def index(parent, item):
    for n, i in enumerate(parent):
        if i is item:
            return n

def find_header_index(tree):
    for i, node in enumerate(tree):
        if is_header(node):
            return i

def is_header(node):
    t = node.tag
    return (isinstance(t, str) and len(t) == 2 and t[0] == 'h'
            and t[1] in '123456789')

def end_of_header(tree, level, start):
    for i, node in enumerate(tree[start:]):
        if is_header(node) and int(node.tag[1]) <= level:
            return start + i
    return len(tree)

def process_rel_href(tree):
    parent = get_parent_map(tree)
    for a in tree.findall('.//a'):
        m = re.match(r'(bold)?rel\:(.+)', a.get('href'))
        if m:
            (bold, path) = m.group(1,2)
            text = a.text
            if text == path:
                tag = et.Element("MYGHTY:nav.myt:toclink", path=literal(path), toc="toc", extension="extension")
            else:
                tag = et.Element("MYGHTY:nav.myt:toclink", path=literal(path), description=literal(text), toc="toc", extension="extension")
            a_parent = parent[a]
            if bold:
                bold = et.Element('strong')
                bold.tail = a.tail
                bold.append(tag)
                a_parent[index(a_parent, a)] = bold
            else:
                tag.tail = a.tail
                a_parent[index(a_parent, a)] = tag

def replace_pre_with_myt(tree):
    def splice_code_tag(pre, text, type=None, title=None):
        doctest_directives = re.compile(r'#\s*doctest:\s*[+-]\w+(,[+-]\w+)*\s*$', re.M)
        text = re.sub(doctest_directives, '', text)
        # process '>>>' to have quotes around it, to work with the myghty python
        # syntax highlighter which uses the tokenize module
        text = re.sub(r'>>> ', r'">>>" ', text)

        # indent two spaces.  among other things, this helps comment lines "#  " from being
        # consumed as Myghty comments.
        text = re.compile(r'^(?!<&)', re.M).sub('  ', text)

        sqlre = re.compile(r'{sql}(.*?)((?:SELECT|INSERT|DELETE|UPDATE|CREATE|DROP|PRAGMA|DESCRIBE).*?)\n\s*(\n|$)', re.S)
        if sqlre.search(text) is not None:
            use_sliders = False
        else:
            use_sliders = True

        text = sqlre.sub(r"<&formatting.myt:poplink&>\1\n<&|formatting.myt:codepopper, link='sql'&>\2</&>\n\n", text)

        sqlre2 = re.compile(r'{opensql}(.*?)((?:SELECT|INSERT|DELETE|UPDATE|CREATE|DROP).*?)\n\s*(\n|$)', re.S)
        text = sqlre2.sub(r"<&|formatting.myt:poppedcode &>\1\n\2</&>\n\n", text)

        opts = {}
        if type == 'python':
            opts['syntaxtype'] = literal('python')
        else:
            opts['syntaxtype'] = None

        if title is not None:
            opts['title'] = literal(title)

        if use_sliders:
            opts['use_sliders'] = True

        tag = et.Element("MYGHTY:formatting.myt:code", **opts)
        tag.text = text

        pre_parent = parents[pre]
        tag.tail = pre.tail
        pre_parent[reverse_parent(pre_parent, pre)] = tag

    parents = get_parent_map(tree)

    for precode in tree.findall('.//pre/code'):
        m = re.match(r'\{(python|code)(?: title="(.*?)"){0,1}\}', precode.text.lstrip())
        if m:
            code = m.group(1)
            title = m.group(2)
            text = precode.text.lstrip()
            text = re.sub(r'{(python|code).*?}(\n\s*)?', '', text)
            splice_code_tag(parents[precode], text, type=code, title=title)
        elif precode.text.lstrip().startswith('>>> '):
            splice_code_tag(parents[precode], precode.text)

def reverse_parent(parent, item):
    for n, i in enumerate(parent):
        if i is item:
            return n

def get_parent_map(tree):
    return dict([(c, p) for p in tree.getiterator() for c in p])

def header(toc, title, filename):
    return """#encoding: utf-8
<%%flags>
    inherit='content_layout.myt'
</%%flags>
<%%args>
    toc
    extension
</%%args>
<%%attr>
    title='%s - %s'
    filename = '%s'
</%%attr>
<%%doc>This file is generated.  Edit the .txt files instead of this one.</%%doc>
""" % (toc.root.doctitle, title, filename)

class utf8stream(object):
    def __init__(self, stream):
        self.stream = stream
    def write(self, str):
        self.stream.write(str.encode('utf8'))

def parse_markdown_files(toc, files):
    for inname in files:
        infile = 'content/%s.txt' % inname
        if not os.access(infile, os.F_OK):
            continue
        html = markdown.markdown(file(infile).read())
        tree = et.fromstring("" + html + "")
        (title, toc_element) = create_toc(inname, tree, toc)
        replace_pre_with_myt(tree)
        process_rel_href(tree)
        outname = 'output/%s.myt' % inname
        print infile, '->', outname
        outfile = utf8stream(file(outname, 'w'))
        outfile.write(header(toc, title, inname))
        dump_tree(tree, outfile)

<|endoftext|>


<commit_before>'''
Created on Dec 22, 2012

@author: vinnie
'''

import networkx as nx
from copy import deepcopy
from pprint import pprint
from itertools import product
from collections import defaultdict, Counter
from enigma import *
from enigma_factory import *

def get_perm_states(graph, cycle):
    '''
    Create a list of the permutation states given the node cycle
    '''
    perms = [graph[n1][n2]['P'] for (n1, n2) in zip(cycle[:-1], cycle[1:])]

    # add the last permutation state needed to complete the cycle
    perms.append(graph[cycle[-1]][cycle[0]]['P'])
    return perms

def find_cycles(plaintext, ciphertext):
    '''
    Find all the permutation cycles that exist between the plaintext and ciphertext
    '''
    n_perm_states = len(plaintext)
    graph = nx.Graph()

    # create a graph with labeled edges between the plaintext and ciphertext
    graph.add_edges_from((n1, n2, {'P': i}) for (n1, n2, i) in
                         zip(plaintext, ciphertext, range(n_perm_states)))

    cycles = [(c[0], get_perm_states(graph, c)) for c in nx.cycle_basis(graph)]
    return cycles

def valid_cycle(enigma, rotor_positions, E, perm_cycle):
    '''
    Check if the permutation cycle is valid for the given configuration
    '''
    c = E
    for P in perm_cycle:
        enigma.set_rotor_positions(rotor_positions)
        enigma.step_to(abs(P))
        c = enigma.encrypt(c)

    # reset the machine
    enigma.set_rotor_positions(rotor_positions)

    # the cycle holds if the input and output are the same
    if c == E:
        return True
    return False

def reduce_keyspace(rotors, reflector,
                    plaintext, ciphertext,
                    min_n_cycles=3, max_n_cycles=3,
                    min_cycle_length=3, max_cycle_length=5,
                    symbol_space=list(string.uppercase)):
    '''
    Find the possible machine states given the rotors, reflector, plaintext and
    ciphertext. This limits the possible key space by exploiting permutation
    cycles in the machine. It attempts to find all the possible configurations
    to generate the ciphertext.

    The number of cycles used for each input are bounded by min and max n_cycles
    The cycles length is bounded by min and max cycles_length

    Returns: a list of initial rotor positions and map of stecker configurations
    '''

    # create a machine to use in the search
    workspace = Enigma(rotors, reflector)
    workspace.stecker = empty_stecker()

    # dict accessed by the start/end value of each system of cycles
    cycles = find_cycles(plaintext, ciphertext)
    cycle_dict = defaultdict(list)
    for c in cycles:
        cycle_dict[c[0][0]].append(c[1])

    # filter the cycle length, # cycles, and finally sort by length for
    # efficiency (shorter cycles tested and disqualified first)
    for k, v in cycle_dict.items():
        cycle_dict[k] = filter(lambda x: len(x) >= min_cycle_length
                                         and len(x) <= max_cycle_length,
                               v)[:max_n_cycles]

        if len(cycle_dict[k]) < min_n_cycles:
            cycle_dict.pop(k)
        else:
            cycle_dict[k].sort(key=len)

    pprint(cycle_dict)
    print "Cycle groups: ", len(cycle_dict.items())
    # the initial rotor position key space
    rotor_positions = product(*[range(len(symbol_space))] * len(workspace.rotors))

    keyspace = defaultdict(dict)
    for cycle_head, L, P in product(cycle_dict.keys(), symbol_space, rotor_positions):
        workspace.stecker[cycle_head] = L
        workspace.stecker[L] = cycle_head

        if all(valid_cycle(workspace, P, cycle_head, c) for c in cycle_dict[cycle_head]):
            keyspace[P].setdefault(cycle_head, set())
            keyspace[P][cycle_head].add(L)
            #            keyspace[(cycle_head,L)].append(P)
            print "S(%s)=%s for" % (cycle_head, L), P

        workspace.stecker[cycle_head] = cycle_head
        workspace.stecker[L] = L

    return keyspace

def guess_key(keyspace):
    '''
    Given a reduced keyspace (possible stecker connections with the corresponding
    rotor positions), attempt to guess the key
    '''

    P_counter = Counter()
    for P in keyspace.items():
        P_counter.update(P)

    return rotor_positions, stecker

def setup():
    # plaintext =  "OBERKOMMANDODERWEHRMACHT"
    # ciphertext = "ZMGERFEWMLKMTAWXTSWVUINZ"
    plaintext = "OBERKOMMANDODERWEHRMACHTOBERKOMMANDODERWEHRMACHTOBERKOMMANDODERWEHRMACHT"

    e = create_enigma()
    unknown_rotor_positions = e.get_rotor_positions()
    rotors = deepcopy(e.rotors)
    reflector = deepcopy(e.reflector)

    ciphertext = e.encrypt(plaintext)
    print "Unknown machine with rotor positions:", unknown_rotor_positions
    print e
    keyspace = reduce_keyspace(rotors, reflector, plaintext, ciphertext)
    pprint(keyspace)
    from IPython import embed
    embed()
    raise Exception()
    return

if __name__ == '__main__':
    setup()
<commit_msg>Formatted code<commit_after>'''
Created on Dec 22, 2012

@author: vinnie
'''

import networkx as nx
from copy import deepcopy
from pprint import pprint
from itertools import product
from collections import defaultdict, Counter
from enigma import *
from enigma_factory import *

def get_perm_states(graph, cycle):
    '''
    Create a list of the permutation states given the node cycle
    '''
    perms = [graph[n1][n2]['P'] for (n1, n2) in zip(cycle[:-1], cycle[1:])]

    # add the last permutation state needed to complete the cycle
    perms.append(graph[cycle[-1]][cycle[0]]['P'])
    return perms

def find_cycles(plaintext, ciphertext):
    '''
    Find all the permutation cycles that exist between the plaintext and ciphertext
    '''
    n_perm_states = len(plaintext)
    graph = nx.Graph()

    # create a graph with labeled edges between the plaintext and ciphertext
    graph.add_edges_from((n1, n2, {'P': i}) for (n1, n2, i) in
                         zip(plaintext, ciphertext, range(n_perm_states)))

    cycles = [(c[0], get_perm_states(graph, c)) for c in nx.cycle_basis(graph)]
    return cycles

def valid_cycle(enigma, rotor_positions, E, perm_cycle):
    '''
    Check if the permutation cycle is valid for the given configuration
    '''
    c = E
    for P in perm_cycle:
        enigma.set_rotor_positions(rotor_positions)
        enigma.step_to(abs(P))
        c = enigma.encrypt(c)

    # reset the machine
    enigma.set_rotor_positions(rotor_positions)

    # the cycle holds if the input and output are the same
    if c == E:
        return True
    return False

def reduce_keyspace(rotors, reflector,
                    plaintext, ciphertext,
                    min_n_cycles=3, max_n_cycles=3,
                    min_cycle_length=3, max_cycle_length=5,
                    symbol_space=list(string.uppercase)):
    '''
    Find the possible machine states given the rotors, reflector, plaintext and
    ciphertext. This limits the possible key space by exploiting permutation
    cycles in the machine. It attempts to find all the possible configurations
    to generate the ciphertext.

    The number of cycles used for each input are bounded by min and max n_cycles
    The cycles length is bounded by min and max cycles_length

    Returns: a list of initial rotor positions and map of stecker configurations
    '''

    # create a machine to use in the search
    workspace = Enigma(rotors, reflector)
    workspace.stecker = empty_stecker()

    # dict accessed by the start/end value of each system of cycles
    cycles = find_cycles(plaintext, ciphertext)
    cycle_dict = defaultdict(list)
    for c in cycles:
        cycle_dict[c[0][0]].append(c[1])

    # filter the cycle length, # cycles, and finally sort by length for
    # efficiency (shorter cycles tested and disqualified first)
    for k, v in cycle_dict.items():
        cycle_dict[k] = filter(lambda x: len(x) >= min_cycle_length
                                         and len(x) <= max_cycle_length,
                               v)[:max_n_cycles]

        if len(cycle_dict[k]) < min_n_cycles:
            cycle_dict.pop(k)
        else:
            cycle_dict[k].sort(key=len)

    pprint(cycle_dict)
    print "Cycle groups: ", len(cycle_dict.items())
    # the initial rotor position key space
    rotor_positions = product(*[range(len(symbol_space))] * len(workspace.rotors))

    keyspace = defaultdict(dict)
    for cycle_head, L, P in product(cycle_dict.keys(), symbol_space, rotor_positions):
        workspace.stecker[cycle_head] = L
        workspace.stecker[L] = cycle_head

        if all(valid_cycle(workspace, P, cycle_head, c) for c in cycle_dict[cycle_head]):
            keyspace[P].setdefault(cycle_head, set())
            keyspace[P][cycle_head].add(L)
            #            keyspace[(cycle_head,L)].append(P)
            print "S(%s)=%s for" % (cycle_head, L), P

        workspace.stecker[cycle_head] = cycle_head
        workspace.stecker[L] = L

    return keyspace

def guess_key(keyspace):
    '''
    Given a reduced keyspace (possible stecker connections with the corresponding
    rotor positions), attempt to guess the key
    '''

    P_counter = Counter()
    for P in keyspace.items():
        P_counter.update(P)

    return rotor_positions, stecker

def break_enigma():
    # plaintext =  "OBERKOMMANDODERWEHRMACHT"
    # ciphertext = "ZMGERFEWMLKMTAWXTSWVUINZ"
    plaintext = "OBERKOMMANDODERWEHRMACHTOBERKOMMANDODERWEHRMACHTOBERKOMMANDODERWEHRMACHT"

    e = create_enigma()
    rotor_positions = e.get_rotor_positions()
    rotors = deepcopy(e.rotors)
    reflector = deepcopy(e.reflector)

    ciphertext = e.encrypt(plaintext)
    print "Rotor positions:", rotor_positions
    print e
    keyspace = reduce_keyspace(rotors, reflector, plaintext, ciphertext)
    pprint(keyspace)
    return

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


<commit_before>"""
HTTPie: command-line HTTP client for the API era.

"""

__version__ = '2.5.0-dev'
__author__ = 'Jakub Roztocil'
__licence__ = 'BSD'
<commit_msg>Normalize the version (#1101)<commit_after>"""
HTTPie: command-line HTTP client for the API era.

"""

__version__ = '2.5.0.dev0'
__author__ = 'Jakub Roztocil'
__licence__ = 'BSD'
<|endoftext|>


<commit_before>#!/usr/bin/env python
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2012 thomasv@gitorious
#
# 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 copy
import datetime
import json
import traceback

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

from electrum.bitcoin import base_encode
from electrum.i18n import _
from electrum.plugins import run_hook
from electrum import simple_config

from electrum.util import bfh
from electrum.wallet import AddTransactionException
from electrum.transaction import SerializationError

from .util import *

dialogs = []  # Otherwise python randomly garbage collects the dialogs...

def show_transaction(tx, parent, desc=None, prompt_if_unsaved=False):
    try:
        d = TxDialog(tx, parent, desc, prompt_if_unsaved)
    except SerializationError as e:
        traceback.print_exc(file=sys.stderr)
        parent.show_critical(_("Electrum was unable to deserialize the transaction:") + "\n" + str(e))
    else:
        dialogs.append(d)
        d.show()

class TxDialog(QDialog, MessageBoxMixin):

    def __init__(self, tx, parent, desc, prompt_if_unsaved):
        '''Transactions in the wallet will show their description.
        Pass desc to give a description for txs not yet in the wallet.
        '''
        # We want to be a top-level window
        QDialog.__init__(self, parent=None)
        # Take a copy; it might get updated in the main window by
        # e.g. the FX plugin.  If this happens during or after a long
        # sign operation the signatures are lost.
        self.tx = copy.deepcopy(tx)
        try:
            self.tx.deserialize()
        except BaseException as e:
            raise SerializationError(e)
        self.main_window = parent
        self.wallet = parent.wallet
        self.prompt_if_unsaved = prompt_if_unsaved
        self.saved = False
        self.desc = desc

        self.setMinimumWidth(750)
        self.setWindowTitle(_("Transaction"))

        vbox = QVBoxLayout()
        self.setLayout(vbox)

        vbox.addWidget(QLabel(_("Transaction ID:")))
        self.tx_hash_e  = ButtonsLineEdit()
        qr_show = lambda: parent.show_qrcode(str(self.tx_hash_e.text()), 'Transaction ID', parent=self)
        self.tx_hash_e.addButton(":icons/qrcode.png", qr_show, _("Show as QR code"))
        self.tx_hash_e.setReadOnly(True)
        vbox.addWidget(self.tx_hash_e)
        self.tx_desc = QLabel()
        vbox.addWidget(self.tx_desc)
        self.status_label = QLabel()
        vbox.addWidget(self.status_label)
        self.date_label = QLabel()
        vbox.addWidget(self.date_label)
        self.amount_label = QLabel()
        vbox.addWidget(self.amount_label)
        self.size_label = QLabel()
        vbox.addWidget(self.size_label)
        self.fee_label = QLabel()
        vbox.addWidget(self.fee_label)

        self.add_io(vbox)

        vbox.addStretch(1)

        self.sign_button = b = QPushButton(_("Sign"))
        b.clicked.connect(self.sign)

        self.broadcast_button = b = QPushButton(_("Broadcast"))
        b.clicked.connect(self.do_broadcast)

        self.save_button = QPushButton(_("Save"))
        save_button_disabled = not tx.is_complete()
        self.save_button.setDisabled(save_button_disabled)
        if save_button_disabled:
            self.save_button.setToolTip(_("Please sign this transaction in order to save it"))
        else:
            self.save_button.setToolTip("")
        self.save_button.clicked.connect(self.save)

        self.export_button = b = QPushButton(_("Export"))
        b.clicked.connect(self.export)

        self.cancel_button = b = QPushButton(_("Close"))
        b.clicked.connect(self.close)
        b.setDefault(True)

        self.qr_button = b = QPushButton()
        b.setIcon(QIcon(":icons/qrcode.png"))
        b.clicked.connect(self.show_qr)

        self.copy_button = CopyButton(lambda: str(self.tx), parent.app)

        # Action buttons
        self.buttons = [self.sign_button, self.broadcast_button, self.save_button, self.cancel_button]
        # Transaction sharing buttons
        self.sharing_buttons = [self.copy_button, self.qr_button, self.export_button]

        run_hook('transaction_dialog', self)

        hbox = QHBoxLayout()
        hbox.addLayout(Buttons(*self.sharing_buttons))
        hbox.addStretch(1)
        hbox.addLayout(Buttons(*self.buttons))
        vbox.addLayout(hbox)
        self.update()

    def do_broadcast(self):
        self.main_window.push_top_level_window(self)
        try:
            self.main_window.broadcast_transaction(self.tx, self.desc)
        finally:
            self.main_window.pop_top_level_window(self)
        self.saved = True
        self.update()

    def closeEvent(self, event):
        if (self.prompt_if_unsaved and not self.saved
                and not self.question(_('This transaction is not saved. Close anyway?'), title=_("Warning"))):
            event.ignore()
        else:
            event.accept()
            try:
                dialogs.remove(self)
            except ValueError:
                pass  # was not in list already

    def show_qr(self):
        text = bfh(str(self.tx))
        text = base_encode(text, base=43)
        try:
            self.main_window.show_qrcode(text, 'Transaction', parent=self)
        except Exception as e:
            self.show_message(str(e))

    def sign(self):
        def sign_done(success):
            if success:
                self.prompt_if_unsaved = True
                self.saved = False
                self.save_button.setDisabled(False)
                self.save_button.setToolTip("")
            self.update()
            self.main_window.pop_top_level_window(self)

        self.sign_button.setDisabled(True)
        self.main_window.push_top_level_window(self)
        self.main_window.sign_tx(self.tx, sign_done)

    def save(self):
        if self.main_window.save_transaction_into_wallet(self.tx):
            self.save_button.setDisabled(True)
            self.saved = True

    def export(self):
        name = 'signed_%s.txn' % (self.tx.txid()[0:8]) if self.tx.is_complete() else 'unsigned.txn'
        fileName = self.main_window.getSaveFileName(_("Select where to save your signed transaction"), name, "*.txn")
        if fileName:
            with open(fileName, "w+") as f:
                f.write(json.dumps(self.tx.as_dict(), indent=4) + '\n')
            self.show_message(_("Transaction exported successfully"))
            self.saved = True

    def update(self):
        desc = self.desc
        base_unit = self.main_window.base_unit()
        format_amount = self.main_window.format_amount
        tx_hash, status, label, can_broadcast, can_rbf, amount, fee, height, conf, timestamp, exp_n = self.wallet.get_tx_info(self.tx)
        size = self.tx.estimated_size()
        self.broadcast_button.setEnabled(can_broadcast)
        can_sign = not self.tx.is_complete() and \
            (self.wallet.can_sign(self.tx) or bool(self.main_window.tx_external_keypairs))
        self.sign_button.setEnabled(can_sign)
        self.tx_hash_e.setText(tx_hash or _('Unknown'))
        if desc is None:
            self.tx_desc.hide()
        else:
            self.tx_desc.setText(_("Description") + ': ' + desc)
            self.tx_desc.show()
        self.status_label.setText(_('Status:') + ' ' + status)

        if timestamp:
            time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
            self.date_label.setText(_("Date: {}").format(time_str))
            self.date_label.show()
        elif exp_n:
            text = '%.2f MB'%(exp_n/1000000)
            self.date_label.setText(_('Position in mempool') + ': ' + text + ' ' + _('from tip'))
            self.date_label.show()
        else:
            self.date_label.hide()
        if amount is None:
            amount_str = _("Transaction unrelated to your wallet")
        elif amount > 0:
            amount_str = _("Amount received:") + ' %s'% format_amount(amount) + ' ' + base_unit
        else:
            amount_str = _("Amount sent:") + ' %s'% format_amount(-amount) + ' ' + base_unit
        size_str = _("Size:") + ' %d bytes'% size
        fee_str = _("Fee") + ': %s' % (format_amount(fee) + ' ' + base_unit if fee is not None else _('unknown'))
        if fee is not None:
            fee_rate = fee/size*1000
            fee_str += '  ( %s ) ' % self.main_window.format_fee_rate(fee_rate)
            confirm_rate = simple_config.FEERATE_WARNING_HIGH_FEE
            if fee_rate > confirm_rate:
                fee_str += ' - ' + _('Warning') + ': ' + _("high fee") + '!'
        self.amount_label.setText(amount_str)
        self.fee_label.setText(fee_str)
        self.size_label.setText(size_str)
        run_hook('transaction_dialog_update', self)

    def add_io(self, vbox):
        if self.tx.locktime > 0:
            vbox.addWidget(QLabel("LockTime: %d\n" % self.tx.locktime))

        vbox.addWidget(QLabel(_("Inputs") + ' (%d)'%len(self.tx.inputs())))
        ext = QTextCharFormat()
        rec = QTextCharFormat()
        rec.setBackground(QBrush(ColorScheme.GREEN.as_color(background=True)))
        rec.setToolTip(_("Wallet receive address"))
        chg = QTextCharFormat()
        chg.setBackground(QBrush(ColorScheme.YELLOW.as_color(background=True)))
        chg.setToolTip(_("Wallet change address"))

        def text_format(addr):
            if self.wallet.is_mine(addr):
                return chg if self.wallet.is_change(addr) else rec
            return ext

        def format_amount(amt):
            return self.main_window.format_amount(amt, whitespaces = True)

        i_text = QTextEdit()
        i_text.setFont(QFont(MONOSPACE_FONT))
        i_text.setReadOnly(True)
        i_text.setMaximumHeight(100)
        cursor = i_text.textCursor()
        for x in self.tx.inputs():
            if x['type'] == 'coinbase':
                cursor.insertText('coinbase')
            else:
                prevout_hash = x.get('prevout_hash')
                prevout_n = x.get('prevout_n')
                cursor.insertText(prevout_hash[0:8] + '...', ext)
                cursor.insertText(prevout_hash[-8:] + ":%-4d " % prevout_n, ext)
                addr = x.get('address')
                if addr == "(pubkey)":
                    _addr = self.wallet.find_pay_to_pubkey_address(prevout_hash, prevout_n)
                    if _addr:
                        addr = _addr
                if addr is None:
                    addr = _('unknown')
                cursor.insertText(addr, text_format(addr))
                if x.get('value'):
                    cursor.insertText(format_amount(x['value']), ext)
            cursor.insertBlock()

        vbox.addWidget(i_text)
        vbox.addWidget(QLabel(_("Outputs") + ' (%d)'%len(self.tx.outputs())))
        o_text = QTextEdit()
        o_text.setFont(QFont(MONOSPACE_FONT))
        o_text.setReadOnly(True)
        o_text.setMaximumHeight(100)
        cursor = o_text.textCursor()
        for addr, v in self.tx.get_outputs():
            cursor.insertText(addr, text_format(addr))
            if v is not None:
                cursor.insertText('\t', ext)
                cursor.insertText(format_amount(v), ext)
            cursor.insertBlock()
        vbox.addWidget(o_text)
<commit_msg>fix transaction dialog for p2pk input<commit_after>#!/usr/bin/env python
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2012 thomasv@gitorious
#
# 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 copy
import datetime
import json
import traceback

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

from electrum.bitcoin import base_encode
from electrum.i18n import _
from electrum.plugins import run_hook
from electrum import simple_config

from electrum.util import bfh
from electrum.wallet import AddTransactionException
from electrum.transaction import SerializationError

from .util import *

dialogs = []  # Otherwise python randomly garbage collects the dialogs...

def show_transaction(tx, parent, desc=None, prompt_if_unsaved=False):
    try:
        d = TxDialog(tx, parent, desc, prompt_if_unsaved)
    except SerializationError as e:
        traceback.print_exc(file=sys.stderr)
        parent.show_critical(_("Electrum was unable to deserialize the transaction:") + "\n" + str(e))
    else:
        dialogs.append(d)
        d.show()

class TxDialog(QDialog, MessageBoxMixin):

    def __init__(self, tx, parent, desc, prompt_if_unsaved):
        '''Transactions in the wallet will show their description.
        Pass desc to give a description for txs not yet in the wallet.
        '''
        # We want to be a top-level window
        QDialog.__init__(self, parent=None)
        # Take a copy; it might get updated in the main window by
        # e.g. the FX plugin.  If this happens during or after a long
        # sign operation the signatures are lost.
        self.tx = copy.deepcopy(tx)
        try:
            self.tx.deserialize()
        except BaseException as e:
            raise SerializationError(e)
        self.main_window = parent
        self.wallet = parent.wallet
        self.prompt_if_unsaved = prompt_if_unsaved
        self.saved = False
        self.desc = desc

        self.setMinimumWidth(750)
        self.setWindowTitle(_("Transaction"))

        vbox = QVBoxLayout()
        self.setLayout(vbox)

        vbox.addWidget(QLabel(_("Transaction ID:")))
        self.tx_hash_e  = ButtonsLineEdit()
        qr_show = lambda: parent.show_qrcode(str(self.tx_hash_e.text()), 'Transaction ID', parent=self)
        self.tx_hash_e.addButton(":icons/qrcode.png", qr_show, _("Show as QR code"))
        self.tx_hash_e.setReadOnly(True)
        vbox.addWidget(self.tx_hash_e)
        self.tx_desc = QLabel()
        vbox.addWidget(self.tx_desc)
        self.status_label = QLabel()
        vbox.addWidget(self.status_label)
        self.date_label = QLabel()
        vbox.addWidget(self.date_label)
        self.amount_label = QLabel()
        vbox.addWidget(self.amount_label)
        self.size_label = QLabel()
        vbox.addWidget(self.size_label)
        self.fee_label = QLabel()
        vbox.addWidget(self.fee_label)

        self.add_io(vbox)

        vbox.addStretch(1)

        self.sign_button = b = QPushButton(_("Sign"))
        b.clicked.connect(self.sign)

        self.broadcast_button = b = QPushButton(_("Broadcast"))
        b.clicked.connect(self.do_broadcast)

        self.save_button = QPushButton(_("Save"))
        save_button_disabled = not tx.is_complete()
        self.save_button.setDisabled(save_button_disabled)
        if save_button_disabled:
            self.save_button.setToolTip(_("Please sign this transaction in order to save it"))
        else:
            self.save_button.setToolTip("")
        self.save_button.clicked.connect(self.save)

        self.export_button = b = QPushButton(_("Export"))
        b.clicked.connect(self.export)

        self.cancel_button = b = QPushButton(_("Close"))
        b.clicked.connect(self.close)
        b.setDefault(True)

        self.qr_button = b = QPushButton()
        b.setIcon(QIcon(":icons/qrcode.png"))
        b.clicked.connect(self.show_qr)

        self.copy_button = CopyButton(lambda: str(self.tx), parent.app)

        # Action buttons
        self.buttons = [self.sign_button, self.broadcast_button, self.save_button, self.cancel_button]
        # Transaction sharing buttons
        self.sharing_buttons = [self.copy_button, self.qr_button, self.export_button]

        run_hook('transaction_dialog', self)

        hbox = QHBoxLayout()
        hbox.addLayout(Buttons(*self.sharing_buttons))
        hbox.addStretch(1)
        hbox.addLayout(Buttons(*self.buttons))
        vbox.addLayout(hbox)
        self.update()

    def do_broadcast(self):
        self.main_window.push_top_level_window(self)
        try:
            self.main_window.broadcast_transaction(self.tx, self.desc)
        finally:
            self.main_window.pop_top_level_window(self)
        self.saved = True
        self.update()

    def closeEvent(self, event):
        if (self.prompt_if_unsaved and not self.saved
                and not self.question(_('This transaction is not saved. Close anyway?'), title=_("Warning"))):
            event.ignore()
        else:
            event.accept()
            try:
                dialogs.remove(self)
            except ValueError:
                pass  # was not in list already

    def show_qr(self):
        text = bfh(str(self.tx))
        text = base_encode(text, base=43)
        try:
            self.main_window.show_qrcode(text, 'Transaction', parent=self)
        except Exception as e:
            self.show_message(str(e))

    def sign(self):
        def sign_done(success):
            if success:
                self.prompt_if_unsaved = True
                self.saved = False
                self.save_button.setDisabled(False)
                self.save_button.setToolTip("")
            self.update()
            self.main_window.pop_top_level_window(self)

        self.sign_button.setDisabled(True)
        self.main_window.push_top_level_window(self)
        self.main_window.sign_tx(self.tx, sign_done)

    def save(self):
        if self.main_window.save_transaction_into_wallet(self.tx):
            self.save_button.setDisabled(True)
            self.saved = True

    def export(self):
        name = 'signed_%s.txn' % (self.tx.txid()[0:8]) if self.tx.is_complete() else 'unsigned.txn'
        fileName = self.main_window.getSaveFileName(_("Select where to save your signed transaction"), name, "*.txn")
        if fileName:
            with open(fileName, "w+") as f:
                f.write(json.dumps(self.tx.as_dict(), indent=4) + '\n')
            self.show_message(_("Transaction exported successfully"))
            self.saved = True

    def update(self):
        desc = self.desc
        base_unit = self.main_window.base_unit()
        format_amount = self.main_window.format_amount
        tx_hash, status, label, can_broadcast, can_rbf, amount, fee, height, conf, timestamp, exp_n = self.wallet.get_tx_info(self.tx)
        size = self.tx.estimated_size()
        self.broadcast_button.setEnabled(can_broadcast)
        can_sign = not self.tx.is_complete() and \
            (self.wallet.can_sign(self.tx) or bool(self.main_window.tx_external_keypairs))
        self.sign_button.setEnabled(can_sign)
        self.tx_hash_e.setText(tx_hash or _('Unknown'))
        if desc is None:
            self.tx_desc.hide()
        else:
            self.tx_desc.setText(_("Description") + ': ' + desc)
            self.tx_desc.show()
        self.status_label.setText(_('Status:') + ' ' + status)

        if timestamp:
            time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
            self.date_label.setText(_("Date: {}").format(time_str))
            self.date_label.show()
        elif exp_n:
            text = '%.2f MB'%(exp_n/1000000)
            self.date_label.setText(_('Position in mempool') + ': ' + text + ' ' + _('from tip'))
            self.date_label.show()
        else:
            self.date_label.hide()
        if amount is None:
            amount_str = _("Transaction unrelated to your wallet")
        elif amount > 0:
            amount_str = _("Amount received:") + ' %s'% format_amount(amount) + ' ' + base_unit
        else:
            amount_str = _("Amount sent:") + ' %s'% format_amount(-amount) + ' ' + base_unit
        size_str = _("Size:") + ' %d bytes'% size
        fee_str = _("Fee") + ': %s' % (format_amount(fee) + ' ' + base_unit if fee is not None else _('unknown'))
        if fee is not None:
            fee_rate = fee/size*1000
            fee_str += '  ( %s ) ' % self.main_window.format_fee_rate(fee_rate)
            confirm_rate = simple_config.FEERATE_WARNING_HIGH_FEE
            if fee_rate > confirm_rate:
                fee_str += ' - ' + _('Warning') + ': ' + _("high fee") + '!'
        self.amount_label.setText(amount_str)
        self.fee_label.setText(fee_str)
        self.size_label.setText(size_str)
        run_hook('transaction_dialog_update', self)

    def add_io(self, vbox):
        if self.tx.locktime > 0:
            vbox.addWidget(QLabel("LockTime: %d\n" % self.tx.locktime))

        vbox.addWidget(QLabel(_("Inputs") + ' (%d)'%len(self.tx.inputs())))
        ext = QTextCharFormat()
        rec = QTextCharFormat()
        rec.setBackground(QBrush(ColorScheme.GREEN.as_color(background=True)))
        rec.setToolTip(_("Wallet receive address"))
        chg = QTextCharFormat()
        chg.setBackground(QBrush(ColorScheme.YELLOW.as_color(background=True)))
        chg.setToolTip(_("Wallet change address"))

        def text_format(addr):
            if self.wallet.is_mine(addr):
                return chg if self.wallet.is_change(addr) else rec
            return ext

        def format_amount(amt):
            return self.main_window.format_amount(amt, whitespaces = True)

        i_text = QTextEdit()
        i_text.setFont(QFont(MONOSPACE_FONT))
        i_text.setReadOnly(True)
        i_text.setMaximumHeight(100)
        cursor = i_text.textCursor()
        for x in self.tx.inputs():
            if x['type'] == 'coinbase':
                cursor.insertText('coinbase')
            else:
                prevout_hash = x.get('prevout_hash')
                prevout_n = x.get('prevout_n')
                cursor.insertText(prevout_hash[0:8] + '...', ext)
                cursor.insertText(prevout_hash[-8:] + ":%-4d " % prevout_n, ext)
                addr = x.get('address')
                if addr == "(pubkey)":
                    _addr = self.wallet.get_txin_address(x)
                    if _addr:
                        addr = _addr
                if addr is None:
                    addr = _('unknown')
                cursor.insertText(addr, text_format(addr))
                if x.get('value'):
                    cursor.insertText(format_amount(x['value']), ext)
            cursor.insertBlock()

        vbox.addWidget(i_text)
        vbox.addWidget(QLabel(_("Outputs") + ' (%d)'%len(self.tx.outputs())))
        o_text = QTextEdit()
        o_text.setFont(QFont(MONOSPACE_FONT))
        o_text.setReadOnly(True)
        o_text.setMaximumHeight(100)
        cursor = o_text.textCursor()
        for addr, v in self.tx.get_outputs():
            cursor.insertText(addr, text_format(addr))
            if v is not None:
                cursor.insertText('\t', ext)
                cursor.insertText(format_amount(v), ext)
            cursor.insertBlock()
        vbox.addWidget(o_text)
<|endoftext|>


<commit_before>#!/usr/bin/python
#
# md2epub.py
#
# by Ben Crowder
# http://bencrowder.net/
#
# Based off Matt Turner's GetBook.py
# http://staff.washington.edu/mdturner
#

import urllib, re, os, zipfile, glob, shutil, sys, markdown2, string, datetime

class Chapter:
	title = ''
	filename = ''
	htmlfile = ''
	id = ''

class EPub:
	title = ''
	author = ''
	lang = 'en-US'
	basename = ''			# base name of ePub (before the extension)
	path = ''				# directory ePub will be stored in
	bookid = ''				# unique identifier
	url = ''				# URL for this book
	css = ''				# CSS file
	images = []				# list of images to be included
	content = []			# list of Chapters

	def cleanup(self):
		os.rmdir(self.path)

	def save(self):
		# get current working directory
		cwd = os.getcwd()

		# create directory for the ePub
		os.mkdir(self.path)
		os.chdir(self.path)

		try:
			# make mimetype
			f = open('mimetype','w')
			f.write('application/epub+zip')
			f.close()

			# make the META-INF/container.xml
			os.mkdir('META-INF')
			os.chdir('META-INF')
			f = open('container.xml','w')
			f.write('''<?xml version="1.0"?>
	<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
		<rootfiles>
			<rootfile full-path="OEBPS/metadata.opf" media-type="application/oebps-package+xml" />
		</rootfiles>
	</container>''')
			f.close()
			os.chdir('..')

			# make OEBPS/metadata.opf
			os.mkdir('OEBPS')
			os.chdir('OEBPS')
			f = open('metadata.opf','w')
			f.write('''<?xml version="1.0"?>
	<package version="2.0" xmlns="http://www.idpf.org/2007/opf"
			 unique-identifier="bookid">
		<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
			<dc:title>''' + self.title + '''</dc:title>
			<dc:creator opf:role="aut">''' + self.author + '''</dc:creator>
			<dc:language>''' + self.lang + '''</dc:language>
			<dc:identifier id="bookid">''' + self.url + ':' + self.bookid + '''</dc:identifier>
		</metadata>
		<manifest>
			<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml" />
	''')

			if self.css:
				f.write('\t\t<item id="style" href="' + os.path.basename(self.css) + '" media-type="application/css" />')

			for chapter in self.content:
				f.write('''
			<item id="''' + chapter.id + '''" href="''' + chapter.htmlfile + '''" media-type="application/xhtml+xml" />''')

			for image in self.images:
				imagefile = os.path.basename(image)
				ext = os.path.splitext(imagefile)[1][1:]	# get the extension
				if ext == 'jpg':
					ext = 'jpeg'
				f.write('''
			<item id="''' + imagefile + '''" href="''' + imagefile + '''" media-type="image/''' + ext + '''" />''')

			f.write('''
		</manifest>
		<spine toc="ncx">''')

			for chapter in self.content:
				f.write('''
			<itemref idref="''' + chapter.id + '''"/>''')

			f.write('''
		</spine>
	</package>''')

			f.close()

			# make toc.ncx
			f = open('toc.ncx','w')
			f.write('''<?xml version="1.0" encoding="UTF-8"?>
	<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
		<head>
			<meta name="dtb:uid" content="''' + self.url + ':' + self.bookid + '''" />
			<meta name="dtb:depth" content="1" />
			<meta name="dtb:totalPageCount" content="0" />
			<meta name="dtb:maxPageNumber" content="0" />
		</head>
		<docTitle>
			<text>''' + self.title + '''</text>
		</docTitle>
		<navMap>''')

			n = 1
			for chapter in self.content:
				f.write('''
			<navPoint id="''' + chapter.id + '''" playOrder="''' + str(n) + '''">
				<navLabel><text>''' + chapter.title + '''</text></navLabel>
				<content src="''' + chapter.htmlfile + '''" />
			</navPoint>''')
				n += 1

			f.write('''
		</navMap>
	</ncx>''')

			f.close()

			# convert the texts to Markdown and save in the directory
			for chapter in self.content:
				input = open('../../' + chapter.filename, 'r')
				f = open(chapter.htmlfile, 'w')
				sourcetext = input.read()
				input.close()

				# write HTML header
				f.write('''<?xml version="1.0" encoding="UTF-8"?>
	<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

	<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<title>''' + self.title + '''</title>''')
				if self.css:
					f.write('<link rel="stylesheet" type="text/css" href="' + os.path.basename(self.css) + '" />')
				f.write('''
	</head>
	<body>''')

				# write the Markdowned text
				f.write(markdown2.markdown(sourcetext).encode('utf-8'))

				# write HTML footer
				f.write('''
	</body>
	</html>''')

				f.close()

			# if there's a CSS file, copy it in
			if self.css:
				css = open('../../' + self.css, 'r')
				csstext = css.read()
				css.close()
				f = open(os.path.basename(self.css), 'w')
				f.write(csstext)
				f.close()

			# copy images into the directory
			for image in self.images:
				dest = os.path.basename(image)
				shutil.copyfile('../../' + image, dest)

			# now zip the ePub up
			os.chdir('../..')
			file = zipfile.ZipFile(self.basename + '.epub', "w")
			os.chdir(self.path)

			file.write('mimetype','mimetype', zipfile.ZIP_STORED)
			file.write('META-INF/container.xml', 'META-INF/container.xml', zipfile.ZIP_DEFLATED)
			for filename in glob.glob("OEBPS/*"):
				file.write(filename, filename, zipfile.ZIP_DEFLATED)

			# and remove the directory
			os.chdir('..')
			shutil.rmtree(self.path)
		except:
			# if something went wrong, remove the temp directory
			os.chdir(cwd)
			shutil.rmtree(self.path)

def process_book(filename):
	epub = EPub()

	now = datetime.datetime.now()
	epub.basename = filename.split('.')[0]
	epub.path = '%s_%s-%s-%s_%s-%s-%s' % (epub.basename, now.year, now.month, now.day, now.hour, now.minute, now.second)
	epub.url = 'http://localhost/' + epub.path		# dummy URL, replaced in control file

	fh = open(filename, 'r')
	for line in fh.readlines():
		if ':' in line:					# keywords
			values = line.split(':')
			keyword = values[0].strip()
			value = values[1].strip()
			if keyword == 'Title':
				epub.title = value
			elif keyword == 'Author':
				epub.author = value
			elif keyword == 'Language':
				epub.lang = value
			elif keyword == 'URL':
				epub.url = value
			elif keyword == 'Image' or keyword == 'Images':
				images = value.split(',')
				# split list
				for image in images:
					epub.images.append(image.strip())
			elif keyword == 'CSS':
				epub.css = value
		elif '|' in line:				# contents
			values = line.split('|')
			title = values[0].strip()
			filename = values[1].strip()

			chapter = Chapter()
			chapter.title = values[0].strip()
			chapter.filename = values[1].strip()

			# replace extension with .html
			basename = os.path.basename(chapter.filename)
			chapter.htmlfile = os.path.splitext(basename)[0] + '.html'

			# for the ID, lowercase it all, strip punctuation, and replace spaces with underscores
			chapter.id = re.sub(r'[^a-zA-Z0-9]', r'', chapter.title.lower()).replace(' ', '_')
		 	# chapter.id = chapter.id.translate(string.maketrans('',''), string.punctuation).replace(' ', '_')

			epub.content.append(chapter)
		else:
			if line[0] == '#' or not line.strip():
				# ignore comments and blank lines
				pass
			else:
				print 'Error!'

	fh.close()

	# create a (hopefully unique) book ID
	epub.bookid = '[' + epub.title + '|' + epub.author + ']'

	return epub

##### Main

def main():
	if (len(sys.argv) > 1):
		epub = process_book(sys.argv[1])
		epub.save()

if __name__ == "__main__":
	main()
<commit_msg>Slight indentation adjustment<commit_after>#!/usr/bin/python
#
# md2epub.py
#
# by Ben Crowder
# http://bencrowder.net/
#
# Based off Matt Turner's GetBook.py
# http://staff.washington.edu/mdturner
#

import urllib, re, os, zipfile, glob, shutil, sys, markdown2, string, datetime

class Chapter:
	title = ''
	filename = ''
	htmlfile = ''
	id = ''

class EPub:
	title = ''
	author = ''
	lang = 'en-US'
	basename = ''			# base name of ePub (before the extension)
	path = ''				# directory ePub will be stored in
	bookid = ''				# unique identifier
	url = ''				# URL for this book
	css = ''				# CSS file
	images = []				# list of images to be included
	content = []			# list of Chapters

	def cleanup(self):
		os.rmdir(self.path)

	def save(self):
		# get current working directory
		cwd = os.getcwd()

		# create directory for the ePub
		os.mkdir(self.path)
		os.chdir(self.path)

		try:
			# make mimetype
			f = open('mimetype','w')
			f.write('application/epub+zip')
			f.close()

			# make the META-INF/container.xml
			os.mkdir('META-INF')
			os.chdir('META-INF')
			f = open('container.xml','w')
			f.write('''<?xml version="1.0"?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
	<rootfiles>
		<rootfile full-path="OEBPS/metadata.opf" media-type="application/oebps-package+xml" />
	</rootfiles>
</container>''')
			f.close()
			os.chdir('..')

			# make OEBPS/metadata.opf
			os.mkdir('OEBPS')
			os.chdir('OEBPS')
			f = open('metadata.opf','w')
			f.write('''<?xml version="1.0"?>
<package version="2.0" xmlns="http://www.idpf.org/2007/opf"
		 unique-identifier="bookid">
	<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
		<dc:title>''' + self.title + '''</dc:title>
		<dc:creator opf:role="aut">''' + self.author + '''</dc:creator>
		<dc:language>''' + self.lang + '''</dc:language>
		<dc:identifier id="bookid">''' + self.url + '''</dc:identifier>
	</metadata>
	<manifest>
		<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml" />
''')

			if self.css:
				f.write('\t\t<item id="style" href="' + os.path.basename(self.css) + '" media-type="application/css" />')

			for chapter in self.content:
				f.write('''
		<item id="''' + chapter.id + '''" href="''' + chapter.htmlfile + '''" media-type="application/xhtml+xml" />''')

			for image in self.images:
				imagefile = os.path.basename(image)
				ext = os.path.splitext(imagefile)[1][1:]	# get the extension
				if ext == 'jpg':
					ext = 'jpeg'
				f.write('''
		<item id="''' + imagefile + '''" href="''' + imagefile + '''" media-type="image/''' + ext + '''" />''')

			f.write('''
	</manifest>
	<spine toc="ncx">''')

			for chapter in self.content:
				f.write('''
		<itemref idref="''' + chapter.id + '''"/>''')

			f.write('''
	</spine>
</package>''')

			f.close()

			# make toc.ncx
			f = open('toc.ncx','w')
			f.write('''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
	<head>
		<meta name="dtb:uid" content="''' + self.url + '''" />
		<meta name="dtb:depth" content="1" />
		<meta name="dtb:totalPageCount" content="0" />
		<meta name="dtb:maxPageNumber" content="0" />
	</head>
	<docTitle>
		<text>''' + self.title + '''</text>
	</docTitle>
	<navMap>''')

			n = 1
			for chapter in self.content:
				f.write('''
		<navPoint id="''' + chapter.id + '''" playOrder="''' + str(n) + '''">
			<navLabel><text>''' + chapter.title + '''</text></navLabel>
			<content src="''' + chapter.htmlfile + '''" />
		</navPoint>''')
				n += 1

			f.write('''
	</navMap>
</ncx>''')

			f.close()

			# convert the texts to Markdown and save in the directory
			for chapter in self.content:
				input = open('../../' + chapter.filename, 'r')
				f = open(chapter.htmlfile, 'w')
				sourcetext = input.read()
				input.close()

				# write HTML header
				f.write('''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<title>''' + self.title + '''</title>''')
				if self.css:
					f.write('\n\t<link rel="stylesheet" type="text/css" href="' + os.path.basename(self.css) + '" />')
				f.write('''
	<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
</head>
<body>''')

				# write the Markdowned text
				f.write(markdown2.markdown(sourcetext).encode('utf-8'))

				# write HTML footer
				f.write('''
</body>
</html>''')

				f.close()

			# if there's a CSS file, copy it in
			if self.css:
				css = open('../../' + self.css, 'r')
				csstext = css.read()
				css.close()
				f = open(os.path.basename(self.css), 'w')
				f.write(csstext)
				f.close()

			# copy images into the directory
			for image in self.images:
				dest = os.path.basename(image)
				shutil.copyfile('../../' + image, dest)

			# now zip the ePub up
			os.chdir('../..')
			file = zipfile.ZipFile(self.basename + '.epub', "w")
			os.chdir(self.path)

			file.write('mimetype','mimetype', zipfile.ZIP_STORED)
			file.write('META-INF/container.xml', 'META-INF/container.xml', zipfile.ZIP_DEFLATED)
			for filename in glob.glob("OEBPS/*"):
				file.write(filename, filename, zipfile.ZIP_DEFLATED)

			# and remove the directory
			os.chdir('..')
			shutil.rmtree(self.path)
		except:
			# if something went wrong, remove the temp directory
			os.chdir(cwd)
			shutil.rmtree(self.path)

def process_book(filename):
	epub = EPub()

	now = datetime.datetime.now()
	epub.basename = filename.split('.')[0]
	epub.path = '%s_%s-%s-%s_%s-%s-%s' % (epub.basename, now.year, now.month, now.day, now.hour, now.minute, now.second)
	epub.url = 'http://localhost/' + epub.path		# dummy URL, replaced in control file

	fh = open(filename, 'r')
	for line in fh.readlines():
		if ':' in line:					# keywords
			values = line.split(':')
			keyword = values[0].strip()
			value = values[1].strip()
			if keyword == 'Title':
				epub.title = value
			elif keyword == 'Author':
				epub.author = value
			elif keyword == 'Language':
				epub.lang = value
			elif keyword == 'URL':
				epub.url = ''.join([values[1].strip(), ':', values[2].strip()])
			elif keyword == 'Image' or keyword == 'Images':
				images = value.split(',')
				# split list
				for image in images:
					epub.images.append(image.strip())
			elif keyword == 'CSS':
				epub.css = value
		elif '|' in line:				# contents
			values = line.split('|')
			title = values[0].strip()
			filename = values[1].strip()

			chapter = Chapter()
			chapter.title = values[0].strip()
			chapter.filename = values[1].strip()

			# replace extension with .html
			basename = os.path.basename(chapter.filename)
			chapter.htmlfile = os.path.splitext(basename)[0] + '.html'

			# for the ID, lowercase it all, strip punctuation, and replace spaces with underscores
			chapter.id = re.sub(r'[^a-zA-Z0-9]', r'', chapter.title.lower()).replace(' ', '_')
		 	# chapter.id = chapter.id.translate(string.maketrans('',''), string.punctuation).replace(' ', '_')

			epub.content.append(chapter)
		else:
			if line[0] == '#' or not line.strip():
				# ignore comments and blank lines
				pass
			else:
				print 'Error!'

	fh.close()

	# create a (hopefully unique) book ID
	epub.bookid = '[' + epub.title + '|' + epub.author + ']'

	return epub

##### Main

def main():
	if (len(sys.argv) > 1):
		epub = process_book(sys.argv[1])
		epub.save()

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