<commit_before># Copyright (C) 2014-2017 New York University
# This file is part of ReproZip which is released under the Revised BSD License
# See file LICENSE for full license details.

from __future__ import division, print_function, unicode_literals

import os

from PyQt4 import QtCore, QtGui

import reprounzip_qt.reprounzip_interface as reprounzip
from reprounzip_qt.gui.common import ROOT, ResizableStack, \
    handle_error, error_msg, parse_ports

class UnpackerOptions(QtGui.QWidget):
    def __init__(self):
        super(UnpackerOptions, self).__init__()
        self.setLayout(QtGui.QGridLayout())

    def add_row(self, label, widget):
        layout = self.layout()
        row = layout.rowCount()
        layout.addWidget(QtGui.QLabel(label), row, 0)
        layout.addWidget(widget, row, 1)

    def options(self):
        return {'args': []}

class DirectoryOptions(UnpackerOptions):
    def __init__(self):
        super(DirectoryOptions, self).__init__()
        self.layout().addWidget(
            QtGui.QLabel("(directory unpacker has no option)"),
            0, 0, 1, 2)

class ChrootOptions(UnpackerOptions):
    def __init__(self):
        super(ChrootOptions, self).__init__()

        self.root = QtGui.QComboBox(editable=False)
        self.root.addItems(ROOT.TEXT)
        self.add_row("Elevate privileges:", self.root)

        self.preserve_owner = QtGui.QCheckBox("enabled", tristate=True)
        self.preserve_owner.setCheckState(QtCore.Qt.PartiallyChecked)
        self.add_row("Preserve file ownership:", self.preserve_owner)

        self.magic_dirs = QtGui.QCheckBox(
            "mount /dev and /proc inside the chroot", tristate=True)
        self.magic_dirs.setCheckState(QtCore.Qt.PartiallyChecked)
        self.add_row("Mount magic dirs:", self.magic_dirs)

    def options(self):
        options = super(ChrootOptions, self).options()

        options['root'] = ROOT.INDEX_TO_OPTION[self.root.currentIndex()]

        if self.preserve_owner.checkState() == QtCore.Qt.Unchecked:
            options['args'].append('--dont-preserve-owner')
        elif self.preserve_owner.checkState() == QtCore.Qt.Checked:
            options['args'].append('--preserve-owner')

        if self.magic_dirs.checkState() == QtCore.Qt.Unchecked:
            options['args'].append('--dont-bind-magic-dirs')
        elif self.magic_dirs.checkState() == QtCore.Qt.Checked:
            options['args'].append('--bind-magic-dirs')

        return options

class DockerOptions(UnpackerOptions):
    def __init__(self):
        super(DockerOptions, self).__init__()

        self.root = QtGui.QComboBox(editable=False)
        self.root.addItems(ROOT.TEXT)
        self.add_row("Elevate privileges:", self.root)

        self.image = QtGui.QLineEdit(placeholderText='detect')
        self.add_row("Base image:", self.image)

        self.distribution = QtGui.QLineEdit(placeholderText='detect')
        self.add_row("Distribution:", self.distribution)

        self.install_pkgs = QtGui.QCheckBox("install packages rather than "
                                            "extracting them from RPZ")
        self.add_row("Install packages:", self.install_pkgs)

    def options(self):
        options = super(DockerOptions, self).options()

        options['root'] = ROOT.INDEX_TO_OPTION[self.root.currentIndex()]

        if self.image.text():
            options['args'].extend(['--base-image', self.image.text()])

        if self.distribution.text():
            options['args'].extend(['--distribution',
                                    self.distribution.text()])

        if self.install_pkgs.isChecked():
            options['args'].append('--install-pkgs')

        return options

class VagrantOptions(UnpackerOptions):
    def __init__(self):
        super(VagrantOptions, self).__init__()

        self.image = QtGui.QLineEdit(placeholderText='detect')
        self.add_row("Base box:", self.image)

        self.distribution = QtGui.QLineEdit(placeholderText='detect')
        self.add_row("Distribution:", self.distribution)

        self.memory = QtGui.QSpinBox(suffix="MB", minimum=99, maximum=64000,
                                     specialValueText="(default)", value=99)
        self.add_row("Memory:", self.memory)

        self.gui = QtGui.QCheckBox("Enable local GUI")
        self.add_row("GUI:", self.gui)

        self.ports = QtGui.QLineEdit(
            '',
            toolTip="Space-separated host:guest port mappings")
        self.add_row("Expose ports:", self.ports)

        self.use_chroot = QtGui.QCheckBox("use chroot and prefer packed files "
                                          "over the virtual machines' files",
                                          checked=True)
        self.add_row("Chroot:", self.use_chroot)

        self.magic_dirs = QtGui.QCheckBox("mount /dev and /proc inside the "
                                          "chroot", checked=True)
        self.add_row("Mount magic dirs:", self.magic_dirs)

    def options(self):
        options = super(VagrantOptions, self).options()

        if self.image.text():
            options['args'].extend(['--base-image', self.image.text()])

        if self.distribution.text():
            options['args'].extend(['--distribution',
                                    self.distribution.text()])

        if self.memory.value() != 99:
            options['args'].extend(['--memory', '%d' % self.memory.value()])

        if self.gui.isChecked():
            options['args'].append('--use-gui')

        ports = parse_ports(self.ports.text(), self)
        if ports is None:
            return None
        for host, container, proto in ports:
            options['args'].append('--expose-port=%s:%s/%s' % (
                                   host, container, proto))

        if not self.use_chroot.isChecked():
            options['args'].append('--dont-use-chroot')

        if not self.magic_dirs.isChecked():
            options['args'].append('--dont-bind-magic-dirs')

        return options

class UnpackTab(QtGui.QWidget):
    """The unpack window, that sets up a .RPZ file in a directory.
    """
    UNPACKERS = [
        ('directory', DirectoryOptions),
        ('chroot', ChrootOptions),
        ('docker', DockerOptions),
        ('vagrant', VagrantOptions),
    ]

    unpacked = QtCore.pyqtSignal(str, object)

    def __init__(self, package='', **kwargs):
        super(UnpackTab, self).__init__(**kwargs)

        layout = QtGui.QGridLayout()
        layout.addWidget(QtGui.QLabel("RPZ package:"), 0, 0)
        self.package_widget = QtGui.QLineEdit(package, enabled=False)
        layout.addWidget(self.package_widget, 0, 1)
        browse_pkg = QtGui.QPushButton("Browse")
        browse_pkg.clicked.connect(self._browse_pkg)
        layout.addWidget(browse_pkg, 0, 2)

        layout.addWidget(QtGui.QLabel("Unpacker:"), 1, 0,
                         QtCore.Qt.AlignTop)
        ulayout = QtGui.QVBoxLayout()
        self.unpackers = QtGui.QButtonGroup()
        for i, name in enumerate(n for n, c in self.UNPACKERS):
            radio = QtGui.QRadioButton(name)
            self.unpackers.addButton(radio, i)
            ulayout.addWidget(radio)
        layout.addLayout(ulayout, 1, 1, 1, 2)

        group = QtGui.QGroupBox(title="Unpacker options")
        group_layout = QtGui.QVBoxLayout()
        self.unpacker_options = ResizableStack()
        self.unpackers.buttonClicked[int].connect(
            self.unpacker_options.setCurrentIndex)
        scroll = QtGui.QScrollArea(widgetResizable=True)
        scroll.setWidget(self.unpacker_options)
        group_layout.addWidget(scroll)
        group.setLayout(group_layout)
        layout.addWidget(group, 2, 0, 1, 3)
        layout.setRowStretch(2, 1)

        for i, (name, WidgetClass) in enumerate(self.UNPACKERS):
            widget = WidgetClass()
            self.unpacker_options.addWidget(widget)

        self.unpacker_options.addWidget(QtGui.QLabel("Select an unpacker to "
                                                     "display options..."))
        self.unpacker_options.setCurrentIndex(len(self.UNPACKERS))

        layout.addWidget(QtGui.QLabel("Destination directory:"), 3, 0)
        self.directory_widget = QtGui.QLineEdit()
        self.directory_widget.editingFinished.connect(self._directory_changed)
        layout.addWidget(self.directory_widget, 3, 1)
        browse_dir = QtGui.QPushButton("Browse")
        browse_dir.clicked.connect(self._browse_dir)
        layout.addWidget(browse_dir, 3, 2)

        buttons = QtGui.QHBoxLayout()
        buttons.addStretch(1)
        self.unpack_widget = QtGui.QPushButton("Unpack experiment",
                                               enabled=False)
        self.unpack_widget.clicked.connect(self._unpack)
        buttons.addWidget(self.unpack_widget)
        layout.addLayout(buttons, 4, 0, 1, 3)

        self.setLayout(layout)

        self._package_changed()

    def _browse_pkg(self):
        picked = QtGui.QFileDialog.getOpenFileName(
            self, "Pick package file",
            QtCore.QDir.currentPath(), "ReproZip Packages (*.rpz)")
        if picked:
            self.package_widget.setText(picked)
            self._package_changed()

    def _package_changed(self, new_pkg=None):
        package = self.package_widget.text()
        if package.lower().endswith('.rpz'):
            self.directory_widget.setText(package[:-4])
            self._directory_changed()

    def _browse_dir(self):
        picked = QtGui.QFileDialog.getSaveFileName(
            self, "Pick directory",
            QtCore.QDir.currentPath())
        if picked:
            if os.path.exists(picked):
                error_msg(self, "This directory already exists", 'warning')
            else:
                self.directory_widget.setText(picked)
                self._directory_changed()

    def _directory_changed(self, new_dir=None):
        self.unpack_widget.setEnabled(bool(self.directory_widget.text()))

    def _unpack(self):
        directory = self.directory_widget.text()
        if not directory:
            return
        unpacker = self.unpackers.checkedButton()
        if unpacker:
            options = self.unpacker_options.currentWidget().options()
            if options is None:
                return
            if handle_error(self, reprounzip.unpack(
                    self.package_widget.text(),
                    unpacker.text(),
                    directory,
                    options)):
                self.unpacked.emit(os.path.abspath(directory),
                                   options.get('root'))
        else:
            error_msg(self, "No unpacker selected", 'warning')
<commit_msg>Add docker machine selection in GUI<commit_after># Copyright (C) 2014-2017 New York University
# This file is part of ReproZip which is released under the Revised BSD License
# See file LICENSE for full license details.

from __future__ import division, print_function, unicode_literals

import os
import subprocess

from PyQt4 import QtCore, QtGui

import reprounzip_qt.reprounzip_interface as reprounzip
from reprounzip_qt.gui.common import ROOT, ResizableStack, \
    handle_error, error_msg, parse_ports

class UnpackerOptions(QtGui.QWidget):
    def __init__(self):
        super(UnpackerOptions, self).__init__()
        self.setLayout(QtGui.QGridLayout())

    def add_row(self, label, widget):
        layout = self.layout()
        row = layout.rowCount()
        layout.addWidget(QtGui.QLabel(label), row, 0)
        layout.addWidget(widget, row, 1)

    def options(self):
        return {'args': []}

class DirectoryOptions(UnpackerOptions):
    def __init__(self):
        super(DirectoryOptions, self).__init__()
        self.layout().addWidget(
            QtGui.QLabel("(directory unpacker has no option)"),
            0, 0, 1, 2)

class ChrootOptions(UnpackerOptions):
    def __init__(self):
        super(ChrootOptions, self).__init__()

        self.root = QtGui.QComboBox(editable=False)
        self.root.addItems(ROOT.TEXT)
        self.add_row("Elevate privileges:", self.root)

        self.preserve_owner = QtGui.QCheckBox("enabled", tristate=True)
        self.preserve_owner.setCheckState(QtCore.Qt.PartiallyChecked)
        self.add_row("Preserve file ownership:", self.preserve_owner)

        self.magic_dirs = QtGui.QCheckBox(
            "mount /dev and /proc inside the chroot", tristate=True)
        self.magic_dirs.setCheckState(QtCore.Qt.PartiallyChecked)
        self.add_row("Mount magic dirs:", self.magic_dirs)

    def options(self):
        options = super(ChrootOptions, self).options()

        options['root'] = ROOT.INDEX_TO_OPTION[self.root.currentIndex()]

        if self.preserve_owner.checkState() == QtCore.Qt.Unchecked:
            options['args'].append('--dont-preserve-owner')
        elif self.preserve_owner.checkState() == QtCore.Qt.Checked:
            options['args'].append('--preserve-owner')

        if self.magic_dirs.checkState() == QtCore.Qt.Unchecked:
            options['args'].append('--dont-bind-magic-dirs')
        elif self.magic_dirs.checkState() == QtCore.Qt.Checked:
            options['args'].append('--bind-magic-dirs')

        return options

class DockerOptions(UnpackerOptions):
    def __init__(self):
        super(DockerOptions, self).__init__()

        self.root = QtGui.QComboBox(editable=False)
        self.root.addItems(ROOT.TEXT)
        self.add_row("Elevate privileges:", self.root)

        try:
            cmd = ['docker-machine', 'ls', '-q']
            query = subprocess.Popen(cmd, stdout=subprocess.PIPE)
            out, _ = query.communicate()
            if query.returncode != 0:
                raise subprocess.CalledProcessError(query.returncode, cmd)
            self.machine = QtGui.QComboBox(editable=False)
            if 'DOCKER_HOST' in os.environ:
                self.machine.addItem("Custom config from environment", None)
            else:
                self.machine.addItem("Default (no machine)", None)
            for machine in out.splitlines():
                machine = machine.strip()
                if machine:
                    self.machine.addItem(machine.decode('utf-8', 'replace'),
                                         machine)
        except (OSError, subprocess.CalledProcessError):
            self.machine = QtGui.QComboBox(editable=False, enabled=False)
            self.machine.addItem("docker-machine unavailable", None)
        self.add_row("docker-machine:", self.machine)

        self.image = QtGui.QLineEdit(placeholderText='detect')
        self.add_row("Base image:", self.image)

        self.distribution = QtGui.QLineEdit(placeholderText='detect')
        self.add_row("Distribution:", self.distribution)

        self.install_pkgs = QtGui.QCheckBox("install packages rather than "
                                            "extracting them from RPZ")
        self.add_row("Install packages:", self.install_pkgs)

    def options(self):
        options = super(DockerOptions, self).options()

        if self.machine.currentIndex() != -1:
            options['docker-machine'] = self.machine.itemData(
                self.machine.currentIndex())

        options['root'] = ROOT.INDEX_TO_OPTION[self.root.currentIndex()]

        if self.image.text():
            options['args'].extend(['--base-image', self.image.text()])

        if self.distribution.text():
            options['args'].extend(['--distribution',
                                    self.distribution.text()])

        if self.install_pkgs.isChecked():
            options['args'].append('--install-pkgs')

        return options

class VagrantOptions(UnpackerOptions):
    def __init__(self):
        super(VagrantOptions, self).__init__()

        self.image = QtGui.QLineEdit(placeholderText='detect')
        self.add_row("Base box:", self.image)

        self.distribution = QtGui.QLineEdit(placeholderText='detect')
        self.add_row("Distribution:", self.distribution)

        self.memory = QtGui.QSpinBox(suffix="MB", minimum=99, maximum=64000,
                                     specialValueText="(default)", value=99)
        self.add_row("Memory:", self.memory)

        self.gui = QtGui.QCheckBox("Enable local GUI")
        self.add_row("GUI:", self.gui)

        self.ports = QtGui.QLineEdit(
            '',
            toolTip="Space-separated host:guest port mappings")
        self.add_row("Expose ports:", self.ports)

        self.use_chroot = QtGui.QCheckBox("use chroot and prefer packed files "
                                          "over the virtual machines' files",
                                          checked=True)
        self.add_row("Chroot:", self.use_chroot)

        self.magic_dirs = QtGui.QCheckBox("mount /dev and /proc inside the "
                                          "chroot", checked=True)
        self.add_row("Mount magic dirs:", self.magic_dirs)

    def options(self):
        options = super(VagrantOptions, self).options()

        if self.image.text():
            options['args'].extend(['--base-image', self.image.text()])

        if self.distribution.text():
            options['args'].extend(['--distribution',
                                    self.distribution.text()])

        if self.memory.value() != 99:
            options['args'].extend(['--memory', '%d' % self.memory.value()])

        if self.gui.isChecked():
            options['args'].append('--use-gui')

        ports = parse_ports(self.ports.text(), self)
        if ports is None:
            return None
        for host, container, proto in ports:
            options['args'].append('--expose-port=%s:%s/%s' % (
                                   host, container, proto))

        if not self.use_chroot.isChecked():
            options['args'].append('--dont-use-chroot')

        if not self.magic_dirs.isChecked():
            options['args'].append('--dont-bind-magic-dirs')

        return options

class UnpackTab(QtGui.QWidget):
    """The unpack window, that sets up a .RPZ file in a directory.
    """
    UNPACKERS = [
        ('directory', DirectoryOptions),
        ('chroot', ChrootOptions),
        ('docker', DockerOptions),
        ('vagrant', VagrantOptions),
    ]

    unpacked = QtCore.pyqtSignal(str, object)

    def __init__(self, package='', **kwargs):
        super(UnpackTab, self).__init__(**kwargs)

        layout = QtGui.QGridLayout()
        layout.addWidget(QtGui.QLabel("RPZ package:"), 0, 0)
        self.package_widget = QtGui.QLineEdit(package, enabled=False)
        layout.addWidget(self.package_widget, 0, 1)
        browse_pkg = QtGui.QPushButton("Browse")
        browse_pkg.clicked.connect(self._browse_pkg)
        layout.addWidget(browse_pkg, 0, 2)

        layout.addWidget(QtGui.QLabel("Unpacker:"), 1, 0,
                         QtCore.Qt.AlignTop)
        ulayout = QtGui.QVBoxLayout()
        self.unpackers = QtGui.QButtonGroup()
        for i, name in enumerate(n for n, c in self.UNPACKERS):
            radio = QtGui.QRadioButton(name)
            self.unpackers.addButton(radio, i)
            ulayout.addWidget(radio)
        layout.addLayout(ulayout, 1, 1, 1, 2)

        group = QtGui.QGroupBox(title="Unpacker options")
        group_layout = QtGui.QVBoxLayout()
        self.unpacker_options = ResizableStack()
        self.unpackers.buttonClicked[int].connect(
            self.unpacker_options.setCurrentIndex)
        scroll = QtGui.QScrollArea(widgetResizable=True)
        scroll.setWidget(self.unpacker_options)
        group_layout.addWidget(scroll)
        group.setLayout(group_layout)
        layout.addWidget(group, 2, 0, 1, 3)
        layout.setRowStretch(2, 1)

        for i, (name, WidgetClass) in enumerate(self.UNPACKERS):
            widget = WidgetClass()
            self.unpacker_options.addWidget(widget)

        self.unpacker_options.addWidget(QtGui.QLabel("Select an unpacker to "
                                                     "display options..."))
        self.unpacker_options.setCurrentIndex(len(self.UNPACKERS))

        layout.addWidget(QtGui.QLabel("Destination directory:"), 3, 0)
        self.directory_widget = QtGui.QLineEdit()
        self.directory_widget.editingFinished.connect(self._directory_changed)
        layout.addWidget(self.directory_widget, 3, 1)
        browse_dir = QtGui.QPushButton("Browse")
        browse_dir.clicked.connect(self._browse_dir)
        layout.addWidget(browse_dir, 3, 2)

        buttons = QtGui.QHBoxLayout()
        buttons.addStretch(1)
        self.unpack_widget = QtGui.QPushButton("Unpack experiment",
                                               enabled=False)
        self.unpack_widget.clicked.connect(self._unpack)
        buttons.addWidget(self.unpack_widget)
        layout.addLayout(buttons, 4, 0, 1, 3)

        self.setLayout(layout)

        self._package_changed()

    def _browse_pkg(self):
        picked = QtGui.QFileDialog.getOpenFileName(
            self, "Pick package file",
            QtCore.QDir.currentPath(), "ReproZip Packages (*.rpz)")
        if picked:
            self.package_widget.setText(picked)
            self._package_changed()

    def _package_changed(self, new_pkg=None):
        package = self.package_widget.text()
        if package.lower().endswith('.rpz'):
            self.directory_widget.setText(package[:-4])
            self._directory_changed()

    def _browse_dir(self):
        picked = QtGui.QFileDialog.getSaveFileName(
            self, "Pick directory",
            QtCore.QDir.currentPath())
        if picked:
            if os.path.exists(picked):
                error_msg(self, "This directory already exists", 'warning')
            else:
                self.directory_widget.setText(picked)
                self._directory_changed()

    def _directory_changed(self, new_dir=None):
        self.unpack_widget.setEnabled(bool(self.directory_widget.text()))

    def _unpack(self):
        directory = self.directory_widget.text()
        if not directory:
            return
        unpacker = self.unpackers.checkedButton()
        if unpacker:
            options = self.unpacker_options.currentWidget().options()
            if options is None:
                return
            if handle_error(self, reprounzip.unpack(
                    self.package_widget.text(),
                    unpacker.text(),
                    directory,
                    options)):
                self.unpacked.emit(os.path.abspath(directory),
                                   options.get('root'))
        else:
            error_msg(self, "No unpacker selected", 'warning')
<|endoftext|>


<commit_before>from ..cache import get_cache_key, get_hexdigest, get_hashed_mtime
from ..settings import COFFEESCRIPT_EXECUTABLE, COFFEESCRIPT_USE_CACHE,\
    COFFEESCRIPT_CACHE_TIMEOUT, COFFEESCRIPT_OUTPUT_DIR
from django.conf import settings
from django.core.cache import cache
from django.template.base import Library, Node
import shlex
import subprocess
import os

register = Library()

class InlineCoffeescriptNode(Node):

    def __init__(self, nodelist):
        self.nodelist = nodelist

    def compile(self, source):
        args = shlex.split("%s -c -s -p" % COFFEESCRIPT_EXECUTABLE)

        p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
        out, errors = p.communicate(source.encode("utf-8"))
        if out:
            return out.decode("utf-8")
        elif errors:
            return errors.decode("utf-8")

        return u""

    def render(self, context):
        output = self.nodelist.render(context)

        if COFFEESCRIPT_USE_CACHE:
            cache_key = get_cache_key(get_hexdigest(output))
            cached = cache.get(cache_key, None)
            if cached is not None:
                return cached
            output = self.compile(output)
            cache.set(cache_key, output, COFFEESCRIPT_CACHE_TIMEOUT)
            return output
        else:
            return self.compile(output)

@register.tag(name="inlinecoffeescript")
def do_inlinecoffeescript(parser, token):
    nodelist = parser.parse(("endinlinecoffeescript",))
    parser.delete_first_token()
    return InlineCoffeescriptNode(nodelist)

@register.simple_tag
def coffeescript(path):

    try:
        STATIC_ROOT = settings.STATIC_ROOT
    except AttributeError:
        STATIC_ROOT = settings.MEDIA_ROOT

    full_path = os.path.join(STATIC_ROOT, path)
    filename = os.path.split(path)[-1]

    output_directory = os.path.join(STATIC_ROOT, COFFEESCRIPT_OUTPUT_DIR, os.path.dirname(path))

    hashed_mtime = get_hashed_mtime(full_path)

    if filename.endswith(".coffee"):
        base_filename = filename[:-7]
    else:
        base_filename = filename

    output_path = os.path.join(output_directory, "%s-%s.js" % (base_filename, hashed_mtime))

    if not os.path.exists(output_path):
        source_file = open(full_path)
        source = source_file.read()
        source_file.close()

        args = shlex.split("%s -c -s -p" % COFFEESCRIPT_EXECUTABLE)
        p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
        out, errors = p.communicate(source)
        if out:
            if not os.path.exists(output_directory):
                os.makedirs(output_directory)
            compiled_file = open(output_path, "w+")
            compiled_file.write(out)
            compiled_file.close()

            # Remove old files
            compiled_filename = os.path.split(output_path)[-1]
            for filename in os.listdir(output_directory):
                if filename.startswith(base_filename) and filename != compiled_filename:
                    os.remove(os.path.join(output_directory, filename))

    return output_path[len(STATIC_ROOT):].lstrip("/")
<commit_msg>In windows output_path looks like "\COFFEESCRIPT_CACHE\...\..", so you need to transform backslashes to slashes.<commit_after>from ..cache import get_cache_key, get_hexdigest, get_hashed_mtime
from ..settings import COFFEESCRIPT_EXECUTABLE, COFFEESCRIPT_USE_CACHE,\
    COFFEESCRIPT_CACHE_TIMEOUT, COFFEESCRIPT_OUTPUT_DIR
from django.conf import settings
from django.core.cache import cache
from django.template.base import Library, Node
import shlex
import subprocess
import os

register = Library()

class InlineCoffeescriptNode(Node):

    def __init__(self, nodelist):
        self.nodelist = nodelist

    def compile(self, source):
        args = shlex.split("%s -c -s -p" % COFFEESCRIPT_EXECUTABLE)

        p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
        out, errors = p.communicate(source.encode("utf-8"))
        if out:
            return out.decode("utf-8")
        elif errors:
            return errors.decode("utf-8")

        return u""

    def render(self, context):
        output = self.nodelist.render(context)

        if COFFEESCRIPT_USE_CACHE:
            cache_key = get_cache_key(get_hexdigest(output))
            cached = cache.get(cache_key, None)
            if cached is not None:
                return cached
            output = self.compile(output)
            cache.set(cache_key, output, COFFEESCRIPT_CACHE_TIMEOUT)
            return output
        else:
            return self.compile(output)

@register.tag(name="inlinecoffeescript")
def do_inlinecoffeescript(parser, token):
    nodelist = parser.parse(("endinlinecoffeescript",))
    parser.delete_first_token()
    return InlineCoffeescriptNode(nodelist)

@register.simple_tag
def coffeescript(path):

    try:
        STATIC_ROOT = settings.STATIC_ROOT
    except AttributeError:
        STATIC_ROOT = settings.MEDIA_ROOT

    full_path = os.path.join(STATIC_ROOT, path)
    filename = os.path.split(path)[-1]

    output_directory = os.path.join(STATIC_ROOT, COFFEESCRIPT_OUTPUT_DIR, os.path.dirname(path))

    hashed_mtime = get_hashed_mtime(full_path)

    if filename.endswith(".coffee"):
        base_filename = filename[:-7]
    else:
        base_filename = filename

    output_path = os.path.join(output_directory, "%s-%s.js" % (base_filename, hashed_mtime))

    if not os.path.exists(output_path):
        source_file = open(full_path)
        source = source_file.read()
        source_file.close()

        args = shlex.split("%s -c -s -p" % COFFEESCRIPT_EXECUTABLE)
        p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
        out, errors = p.communicate(source)
        if out:
            if not os.path.exists(output_directory):
                os.makedirs(output_directory)
            compiled_file = open(output_path, "w+")
            compiled_file.write(out)
            compiled_file.close()

            # Remove old files
            compiled_filename = os.path.split(output_path)[-1]
            for filename in os.listdir(output_directory):
                if filename.startswith(base_filename) and filename != compiled_filename:
                    os.remove(os.path.join(output_directory, filename))

    return output_path[len(STATIC_ROOT):].replace(os.sep, '/').lstrip("/")
<|endoftext|>


<commit_before>import web, config, steam, database, re
from copy import deepcopy

qualitydict = {"unique": "The",
               "normal": ""}

# I don't like this either but Valve didn't expose them
# through the API
particledict = {0: "Invalid Particle",
                1: "Particle 1",
                2: "Flying Bits",
                3: "Nemesis Burst",
                4: "Community Sparkle",
                5: "Holy Glow",
                6: "Green Confetti",
                7: "Purple Confetti",
                8: "Haunted Ghosts",
                9: "Green Energy",
                10: "Purple Energy",
                11: "Circling TF Logo",
                12: "Massed Flies",
                13: "Burning Flames",
                14: "Scorching Flames",
                15: "Searing Plasma",
                16: "Vivid Plasma",
                17: "Sunbeams",
                18: "Circling Peace Sign",
                19: "Circling Heart",
                20: "Map Stamps"}

def generate_full_item_name(item, ignore_qdict = False, strip_prefixes = False):
    """ Ignores the values in qualitydict if ignore_qdict is True """
    quality_str = item.get_quality()["str"]
    pretty_quality_str = item.get_quality()["prettystr"]
    custom_name = item.get_custom_name()
    item_name = item.get_name()

    if ignore_qdict:
        prefix = pretty_quality_str + " "
    else:
        prefix = qualitydict.get(quality_str, pretty_quality_str) + " "

    if item_name.find("The ") != -1 and item.is_name_prefixed():
        item_name = item_name[4:]

    if custom_name or (not item.is_name_prefixed() and quality_str == "unique"):
        prefix = ""
    if custom_name:
        item_name = custom_name

    if ((web.ctx.item_schema.get_language() != "en" and quality_str == "unique") or
        ignore_qdict and (quality_str == "unique" or quality_str == "normal") or
        strip_prefixes):
        return item_name
    else:
        return prefix + item_name

def absolute_url(relative_url):
    domain = web.ctx.homedomain
    if domain.endswith('/'): domain = domain[:-1]
    return domain + relative_url

def get_invalid_pos(items):
    poslist = []
    invalid_items = []
    for item in items:
        if not item: continue
        pos = item.get_position()
        if pos != -1 and pos not in poslist:
            poslist.append(item.get_position())
        else:
            invalid_items.append(item)

    return invalid_items

def sort(items, sortby):
    if not items or len(items) == 0:
        return [None] * config.backpack_padded_size

    itemcmp = None
    def defcmp(x, y):
        if x < y:
            return -1
        elif x > y:
            return 1
        elif x == y:
            return 0

    if sortby == "time":
        items.reverse()

    if sortby == "serial":
        def itemcmp(x, y):
            return defcmp(x.get_id(),
                          y.get_id())
    elif sortby == "cell":
        def itemcmp(x, y):
            return defcmp(x.get_position(),
                          y.get_position())
    elif sortby == "level":
        def itemcmp(x, y):
            return defcmp(x.get_level(),
                          y.get_level())
    elif sortby == "name":
        def itemcmp(x, y):
            return defcmp(generate_full_item_name(x, strip_prefixes = True),
                          generate_full_item_name(y, strip_prefixes = True))
    elif sortby == "slot":
        def itemcmp(x, y):
            return defcmp(x.get_slot(), y.get_slot())
    elif sortby == "class":
        def itemcmp(x, y):
            cx = x.get_equipable_classes()
            cy = y.get_equipable_classes()
            lenx = len(cx)
            leny = len(cy)

            if lenx == 1 and leny == 1:
                return defcmp(cx[0], cy[0])
            else:
                return defcmp(lenx, leny)

    if itemcmp:
        items.sort(cmp = itemcmp)

    itemcount = len(items)
    highestpos = items[-1].get_position()

    if itemcount < config.backpack_padded_size:
        itemcount = config.backpack_padded_size

    if sortby == "cell" and highestpos > itemcount:
        itemcount = highestpos

    rem = itemcount % 50
    if rem != 0: itemcount += (50 - rem)
    pagecount = itemcount / 50

    if sortby == "cell":
        newitems = [None] * (itemcount + 1)
        for item in items:
            pos = item.get_position()
            try:
                if pos > -1 and newitems[pos] == None:
                    newitems[pos] = item
            except IndexError: pass
        del newitems[0]
        return newitems

    return items + ([None] * (itemcount - len(items)))

def filter_by_class(items, theclass):
    filtered_items = []

    for item in items:
        if not item: continue
        classes = item.get_equipable_classes()
        for c in classes:
            if c == theclass:
                filtered_items.append(item)
                break
    return filtered_items

def filter_by_quality(items, thequality):
    filtered_items = []

    for item in items:
        if not item: continue
        if str(item.get_quality()["id"]) == thequality:
            filtered_items.append(item)
    return filtered_items

def get_stats(items):
    """ Returns a dict of various backpack stats """
    stats = {"weapons": 0,
             "misc": 0,
             "hats": 0,
             "total": 0}

    for item in items:
        if not item: continue

        slot = item.get_slot()
        iclass = item.get_class()

        stats["total"] += 1

        if slot == "primary" or slot == "melee" or slot == "secondary":
            if iclass.find("token") == -1:
                stats["weapons"] += 1
        elif slot == "head" and iclass.find("token") == -1:
            stats["hats"] += 1
        elif slot == "misc":
            stats["misc"] += 1
    return stats

def process_attributes(items):
    """ Filters attributes for the item list,
    optf2-specific data is stored in item.optf2 """

    default_item_image = config.virtual_root + "static/item_icons/Invalid_icon.png";
    newitems = []
    for item in items:
        if not item: continue
        if not getattr(item, "optf2", None):
            item.optf2 = {}
        attrs = item.get_attributes()
        item.optf2["attrs"] = []
        item.optf2["description"] = item.get_description()
        item.optf2["image_url"] = item.get_image(item.ITEM_IMAGE_SMALL) or default_item_image
        item.optf2["image_url_large"] = item.get_image(item.ITEM_IMAGE_LARGE) or default_item_image
        min_level = item.get_min_level()
        max_level = item.get_max_level()
        pb_level = item.get_level()
        custom_desc = item.get_custom_description()
        itype = item.get_type()

        if itype.startswith("TF_"):
            s1 = re.sub("(.)([A-Z][a-z]+)", "\\1 \\2", itype[3:])
            itype = re.sub("([a-z0-9])([A-Z])", "\\1 \\2", s1)
            itype = itype.replace("_", "")
        item.optf2["type"] = itype

        if custom_desc: item.optf2["description"] = custom_desc

        if min_level == max_level:
            item.optf2["level"] = str(min_level)
        else:
            item.optf2["level"] = str(min_level) + "-" + str(max_level)

        if pb_level != None: item.optf2["level"] = pb_level

        for theattr in attrs:
            newattr = deepcopy(theattr)
            desc = newattr.get_description()

            # Contained item is a schema id, this is an incredibly
            # ugly hack but I'm too stubborn to make DB changes for this
            if newattr.get_name() == "referenced item def":
                giftcontents = newattr.get_value()

                if not isinstance(giftcontents, dict):
                    giftcontents = item._schema[(int(giftcontents))]
                else:
                    giftcontents = steam.tf2.item(item._schema, giftcontents)

                giftcontents.optf2 = {}
                giftcontents.optf2["gift_container_id"] = item.get_id()
                item.optf2["gift_content"] = generate_full_item_name(giftcontents)
                item.optf2["gift_quality"] = giftcontents.get_quality()["str"]
                item.optf2["gift_item"] = giftcontents

                newattr._attribute["description_string"] = 'Contains ' + item.optf2["gift_content"]
                newattr._attribute["hidden"] = False

            if newattr.get_name() == "set item tint RGB":
                raw_rgb = int(newattr.get_value())

                if raw_rgb == 1:
                    # Team Spirit
                    item_color = 'url("{0}team_splotch.png")'.format(config.static_prefix)
                else:
                    item_color = "#{0:02X}{1:02X}{2:02X}".format((raw_rgb >> 16) & 0xFF,
                                                                 (raw_rgb >> 8) & 0xFF,
                                                                 (raw_rgb) & 0xFF)

                # Workaround until the icons for colored paint cans are correct
                try:
                    schema_paintcan = item._schema[item.get_schema_id()]
                except KeyError: schema_paintcan = 0
                if (schema_paintcan and
                    schema_paintcan._item.get("name", "").startswith("Paint Can") and
                    raw_rgb != 0 and raw_rgb != 1):
                    paintcan_url = "{0}item_icons/Paint_Can_{1}.png".format(config.static_prefix,
                                                                            item_color[1:])
                    item.optf2["image_url"] = absolute_url(paintcan_url)
                    item.optf2["image_url_large"] = absolute_url(paintcan_url)
                item.optf2["color"] = item_color
                continue

            if newattr.get_name() == "attach particle effect":
                newattr._attribute["description_string"] = ("Effect: " +
                                                            particledict.get(int(newattr.get_value()), particledict[0]))

            if newattr.get_name() == "gifter account id":
                newattr._attribute["description_string"] = "Gift"
                item.optf2["gift_from"] = "7656" + str(int(newattr.get_value()) +
                                                       1197960265728)
                try:
                    user = database.load_profile_cached(item.optf2["gift_from"], stale = True)
                    item.optf2["gift_from_persona"] = user.get_persona()
                    newattr._attribute["description_string"] = "Gift from " + item.optf2["gift_from_persona"]
                except:
                    item.optf2["gift_from_persona"] = "this user"

            if not newattr.is_hidden():
                newattr._attribute["description_string"] = web.websafe(newattr.get_description())
            else:
                continue

            item.optf2["attrs"].append(newattr)

        if "gift_item" in item.optf2:
            item.optf2["gift_item"].optf2["gift_from_persona"] = item.optf2["gift_from_persona"]
            item.optf2["gift_item"].optf2["gift_from"] = item.optf2["gift_from"]

        quality_str = item.get_quality()["str"]
        full_qdict_name = generate_full_item_name(item)
        full_default_name = generate_full_item_name(item, True)
        is_gift_contents = "gift_container_id" in item.optf2

        item.optf2["cell_name"] = '
{1}
'.format(quality_str, full_qdict_name)

        color = item.optf2.get("color")
        paint_job = ""
        prefix = ""
        if color:
            if color.startswith("url"):
                color = "#FF00FF"
                paint_job = 'Painted'
            else:
                paint_job = 'Painted'.format(color)
        if is_gift_contents:
            prefix = 'Giftwrapped'
        item.optf2["painted_text"] = paint_job
        item.optf2["dedicated_name"] = "{0} {1} {2}".format(prefix, paint_job, full_default_name)

        if color:
            paint_job = "Painted"
        if is_gift_contents:
            prefix = "Giftwrapped"
        item.optf2["title_name"] = "{0} {1} {2}".format(prefix, paint_job, full_default_name)

        if color:
            paint_job = "(Painted)"
        else:
            paint_job = ""
        item.optf2["feed_name"] = "{0} {1}".format(full_qdict_name, paint_job)

        newitems.append(item)

    items = newitems
    return newitems

def get_equippable_classes(items):
    """ Returns a set of classes that can equip this
    item """

    valid_classes = set()

    if not items: return []

    for item in items:
        if not item: continue
        classes = item.get_equipable_classes()
        valid_classes |= set(classes)

    return sorted(list(valid_classes))

def _quality_sort(x, y):
    px = x["prettystr"]
    py = y["prettystr"]

    if px < py: return -1
    elif px > py: return 1
    else: return 0

def get_present_qualities(items):
    """ Returns a sorted list of qualities that are in this set
    of items """

    qualities = set()
    qlist = []

    for item in items:
        if not item: continue
        quality = item.get_quality()
        if quality["id"] not in qualities:
            qualities.add(quality["id"])
            qlist.append(quality)

    qlist.sort(cmp = _quality_sort)
    return qlist
<commit_msg>Put quality name in parens at end of item name for non-English names<commit_after>import web, config, steam, database, re
from copy import deepcopy

qualitydict = {"unique": "The",
               "normal": ""}

# I don't like this either but Valve didn't expose them
# through the API
particledict = {0: "Invalid Particle",
                1: "Particle 1",
                2: "Flying Bits",
                3: "Nemesis Burst",
                4: "Community Sparkle",
                5: "Holy Glow",
                6: "Green Confetti",
                7: "Purple Confetti",
                8: "Haunted Ghosts",
                9: "Green Energy",
                10: "Purple Energy",
                11: "Circling TF Logo",
                12: "Massed Flies",
                13: "Burning Flames",
                14: "Scorching Flames",
                15: "Searing Plasma",
                16: "Vivid Plasma",
                17: "Sunbeams",
                18: "Circling Peace Sign",
                19: "Circling Heart",
                20: "Map Stamps"}

def generate_full_item_name(item, ignore_qdict = False, strip_prefixes = False):
    """ Ignores the values in qualitydict if ignore_qdict is True """
    quality_str = item.get_quality()["str"]
    pretty_quality_str = item.get_quality()["prettystr"]
    custom_name = item.get_custom_name()
    item_name = item.get_name()
    language = web.ctx.item_schema.get_language()

    if ignore_qdict:
        prefix = pretty_quality_str
    else:
        prefix = qualitydict.get(quality_str, pretty_quality_str)

    if item_name.find("The ") != -1 and item.is_name_prefixed():
        item_name = item_name[4:]

    if strip_prefixes or custom_name or (not item.is_name_prefixed() and quality_str == "unique"):
        prefix = ""

    if custom_name:
        item_name = custom_name

    if ((ignore_qdict or language != "en") and (quality_str == "unique" or quality_str == "normal")):
        prefix = ""

    if (language != "en" and prefix):
        return item_name + " (" + prefix + ")"

    if prefix: return prefix + " " + item_name
    else: return item_name

def absolute_url(relative_url):
    domain = web.ctx.homedomain
    if domain.endswith('/'): domain = domain[:-1]
    return domain + relative_url

def get_invalid_pos(items):
    poslist = []
    invalid_items = []
    for item in items:
        if not item: continue
        pos = item.get_position()
        if pos != -1 and pos not in poslist:
            poslist.append(item.get_position())
        else:
            invalid_items.append(item)

    return invalid_items

def sort(items, sortby):
    if not items or len(items) == 0:
        return [None] * config.backpack_padded_size

    itemcmp = None
    def defcmp(x, y):
        if x < y:
            return -1
        elif x > y:
            return 1
        elif x == y:
            return 0

    if sortby == "time":
        items.reverse()

    if sortby == "serial":
        def itemcmp(x, y):
            return defcmp(x.get_id(),
                          y.get_id())
    elif sortby == "cell":
        def itemcmp(x, y):
            return defcmp(x.get_position(),
                          y.get_position())
    elif sortby == "level":
        def itemcmp(x, y):
            return defcmp(x.get_level(),
                          y.get_level())
    elif sortby == "name":
        def itemcmp(x, y):
            return defcmp(generate_full_item_name(x, strip_prefixes = True),
                          generate_full_item_name(y, strip_prefixes = True))
    elif sortby == "slot":
        def itemcmp(x, y):
            return defcmp(x.get_slot(), y.get_slot())
    elif sortby == "class":
        def itemcmp(x, y):
            cx = x.get_equipable_classes()
            cy = y.get_equipable_classes()
            lenx = len(cx)
            leny = len(cy)

            if lenx == 1 and leny == 1:
                return defcmp(cx[0], cy[0])
            else:
                return defcmp(lenx, leny)

    if itemcmp:
        items.sort(cmp = itemcmp)

    itemcount = len(items)
    highestpos = items[-1].get_position()

    if itemcount < config.backpack_padded_size:
        itemcount = config.backpack_padded_size

    if sortby == "cell" and highestpos > itemcount:
        itemcount = highestpos

    rem = itemcount % 50
    if rem != 0: itemcount += (50 - rem)
    pagecount = itemcount / 50

    if sortby == "cell":
        newitems = [None] * (itemcount + 1)
        for item in items:
            pos = item.get_position()
            try:
                if pos > -1 and newitems[pos] == None:
                    newitems[pos] = item
            except IndexError: pass
        del newitems[0]
        return newitems

    return items + ([None] * (itemcount - len(items)))

def filter_by_class(items, theclass):
    filtered_items = []

    for item in items:
        if not item: continue
        classes = item.get_equipable_classes()
        for c in classes:
            if c == theclass:
                filtered_items.append(item)
                break
    return filtered_items

def filter_by_quality(items, thequality):
    filtered_items = []

    for item in items:
        if not item: continue
        if str(item.get_quality()["id"]) == thequality:
            filtered_items.append(item)
    return filtered_items

def get_stats(items):
    """ Returns a dict of various backpack stats """
    stats = {"weapons": 0,
             "misc": 0,
             "hats": 0,
             "total": 0}

    for item in items:
        if not item: continue

        slot = item.get_slot()
        iclass = item.get_class()

        stats["total"] += 1

        if slot == "primary" or slot == "melee" or slot == "secondary":
            if iclass.find("token") == -1:
                stats["weapons"] += 1
        elif slot == "head" and iclass.find("token") == -1:
            stats["hats"] += 1
        elif slot == "misc":
            stats["misc"] += 1
    return stats

def process_attributes(items):
    """ Filters attributes for the item list,
    optf2-specific data is stored in item.optf2 """

    default_item_image = config.virtual_root + "static/item_icons/Invalid_icon.png";
    newitems = []
    for item in items:
        if not item: continue
        if not getattr(item, "optf2", None):
            item.optf2 = {}
        attrs = item.get_attributes()
        item.optf2["attrs"] = []
        item.optf2["description"] = item.get_description()
        item.optf2["image_url"] = item.get_image(item.ITEM_IMAGE_SMALL) or default_item_image
        item.optf2["image_url_large"] = item.get_image(item.ITEM_IMAGE_LARGE) or default_item_image
        min_level = item.get_min_level()
        max_level = item.get_max_level()
        pb_level = item.get_level()
        custom_desc = item.get_custom_description()
        itype = item.get_type()

        if itype.startswith("TF_"):
            s1 = re.sub("(.)([A-Z][a-z]+)", "\\1 \\2", itype[3:])
            itype = re.sub("([a-z0-9])([A-Z])", "\\1 \\2", s1)
            itype = itype.replace("_", "")
        item.optf2["type"] = itype

        if custom_desc: item.optf2["description"] = custom_desc

        if min_level == max_level:
            item.optf2["level"] = str(min_level)
        else:
            item.optf2["level"] = str(min_level) + "-" + str(max_level)

        if pb_level != None: item.optf2["level"] = pb_level

        for theattr in attrs:
            newattr = deepcopy(theattr)
            desc = newattr.get_description()

            # Contained item is a schema id, this is an incredibly
            # ugly hack but I'm too stubborn to make DB changes for this
            if newattr.get_name() == "referenced item def":
                giftcontents = newattr.get_value()

                if not isinstance(giftcontents, dict):
                    giftcontents = item._schema[(int(giftcontents))]
                else:
                    giftcontents = steam.tf2.item(item._schema, giftcontents)

                giftcontents.optf2 = {}
                giftcontents.optf2["gift_container_id"] = item.get_id()
                item.optf2["gift_content"] = generate_full_item_name(giftcontents)
                item.optf2["gift_quality"] = giftcontents.get_quality()["str"]
                item.optf2["gift_item"] = giftcontents

                newattr._attribute["description_string"] = 'Contains ' + item.optf2["gift_content"]
                newattr._attribute["hidden"] = False

            if newattr.get_name() == "set item tint RGB":
                raw_rgb = int(newattr.get_value())

                if raw_rgb == 1:
                    # Team Spirit
                    item_color = 'url("{0}team_splotch.png")'.format(config.static_prefix)
                else:
                    item_color = "#{0:02X}{1:02X}{2:02X}".format((raw_rgb >> 16) & 0xFF,
                                                                 (raw_rgb >> 8) & 0xFF,
                                                                 (raw_rgb) & 0xFF)

                # Workaround until the icons for colored paint cans are correct
                try:
                    schema_paintcan = item._schema[item.get_schema_id()]
                except KeyError: schema_paintcan = 0
                if (schema_paintcan and
                    schema_paintcan._item.get("name", "").startswith("Paint Can") and
                    raw_rgb != 0 and raw_rgb != 1):
                    paintcan_url = "{0}item_icons/Paint_Can_{1}.png".format(config.static_prefix,
                                                                            item_color[1:])
                    item.optf2["image_url"] = absolute_url(paintcan_url)
                    item.optf2["image_url_large"] = absolute_url(paintcan_url)
                item.optf2["color"] = item_color
                continue

            if newattr.get_name() == "attach particle effect":
                newattr._attribute["description_string"] = ("Effect: " +
                                                            particledict.get(int(newattr.get_value()), particledict[0]))

            if newattr.get_name() == "gifter account id":
                newattr._attribute["description_string"] = "Gift"
                item.optf2["gift_from"] = "7656" + str(int(newattr.get_value()) +
                                                       1197960265728)
                try:
                    user = database.load_profile_cached(item.optf2["gift_from"], stale = True)
                    item.optf2["gift_from_persona"] = user.get_persona()
                    newattr._attribute["description_string"] = "Gift from " + item.optf2["gift_from_persona"]
                except:
                    item.optf2["gift_from_persona"] = "this user"

            if not newattr.is_hidden():
                newattr._attribute["description_string"] = web.websafe(newattr.get_description())
            else:
                continue

            item.optf2["attrs"].append(newattr)

        if "gift_item" in item.optf2:
            item.optf2["gift_item"].optf2["gift_from_persona"] = item.optf2["gift_from_persona"]
            item.optf2["gift_item"].optf2["gift_from"] = item.optf2["gift_from"]

        quality_str = item.get_quality()["str"]
        full_qdict_name = generate_full_item_name(item)
        full_default_name = generate_full_item_name(item, True)
        is_gift_contents = "gift_container_id" in item.optf2

        item.optf2["cell_name"] = '
{1}
'.format(quality_str, full_qdict_name)

        color = item.optf2.get("color")
        paint_job = ""
        prefix = ""
        if color:
            if color.startswith("url"):
                color = "#FF00FF"
                paint_job = 'Painted'
            else:
                paint_job = 'Painted'.format(color)
        if is_gift_contents:
            prefix = 'Giftwrapped'
        item.optf2["painted_text"] = paint_job
        item.optf2["dedicated_name"] = "{0} {1} {2}".format(prefix, paint_job, full_default_name)

        if color:
            paint_job = "Painted"
        if is_gift_contents:
            prefix = "Giftwrapped"
        item.optf2["title_name"] = "{0} {1} {2}".format(prefix, paint_job, full_default_name)

        if color:
            paint_job = "(Painted)"
        else:
            paint_job = ""
        item.optf2["feed_name"] = "{0} {1}".format(full_qdict_name, paint_job)

        newitems.append(item)

    items = newitems
    return newitems

def get_equippable_classes(items):
    """ Returns a set of classes that can equip this
    item """

    valid_classes = set()

    if not items: return []

    for item in items:
        if not item: continue
        classes = item.get_equipable_classes()
        valid_classes |= set(classes)

    return sorted(list(valid_classes))

def _quality_sort(x, y):
    px = x["prettystr"]
    py = y["prettystr"]

    if px < py: return -1
    elif px > py: return 1
    else: return 0

def get_present_qualities(items):
    """ Returns a sorted list of qualities that are in this set
    of items """

    qualities = set()
    qlist = []

    for item in items:
        if not item: continue
        quality = item.get_quality()
        if quality["id"] not in qualities:
            qualities.add(quality["id"])
            qlist.append(quality)

    qlist.sort(cmp = _quality_sort)
    return qlist
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: sw=4:ts=4:expandtab

"""
tests.test
~~~~~~~~~~

Provides scripttests to test csv2ofx CLI functionality.
"""

from __future__ import (
    absolute_import, division, print_function, unicode_literals)

import sys

from difflib import unified_diff
from os import path as p
from io import StringIO, open
from timeit import default_timer as timer
from builtins import *

import pygogo as gogo

from scripttest import TestFileEnvironment

sys.path.append('../csv2ofx')

PARENT_DIR = p.abspath(p.dirname(p.dirname(__file__)))
EXAMPLE_DIR = p.join(PARENT_DIR, 'data', 'test')
CHECK_DIR = p.join(PARENT_DIR, 'data', 'converted')

def filter_output(outlines, debug_stmts=None):
    """Remove meza debugging statements"""
    def_stmts = ['File was opened in', 'Decoding file with encoding']
    debug_stmts = debug_stmts or def_stmts

    for line in outlines:
        if not any(stmt in line for stmt in debug_stmts):
            yield line

def main(script, tests, verbose=False, stop=True):
    """
    Returns 0 on success, 1 on failure
    """
    failures = 0
    logger = gogo.Gogo(__name__, verbose=verbose).logger
    short_script = p.basename(script)
    env = TestFileEnvironment('.scripttest')

    start = timer()
    for pos, test in enumerate(tests):
        num = pos + 1
        opts, arguments, expected = test
        joined_opts = ' '.join(opts) if opts else ''
        joined_args = '"%s"' % '" "'.join(arguments) if arguments else ''
        command = "%s %s %s" % (script, joined_opts, joined_args)
        short_command = "%s %s %s" % (short_script, joined_opts, joined_args)
        result = env.run(command, cwd=PARENT_DIR, expect_stderr=True)
        output = result.stdout

        if isinstance(expected, bool):
            text = StringIO(output).read()
            outlines = [str(bool(text))]
            checklines = StringIO(str(expected)).readlines()
        elif p.isfile(expected):
            outlines = StringIO(output).readlines()

            with open(expected, encoding='utf-8') as f:
                checklines = f.readlines()
        else:
            outlines = StringIO(output).readlines()
            checklines = StringIO(expected).readlines()

        args = [checklines, list(filter_output(outlines))]
        kwargs = {'fromfile': 'expected', 'tofile': 'got'}
        diffs = ''.join(unified_diff(*args, **kwargs))

        if diffs:
            failures += 1
            msg = "ERROR! Output from test #%i:\n  %s\n" % (num, short_command)
            msg += "doesn't match:\n  %s\n" % expected
            msg += diffs if diffs else ''
        else:
            logger.debug(output)
            msg = 'Scripttest #%i: %s ... ok' % (num, short_command)

        logger.info(msg)

        if stop and failures:
            break

    time = timer() - start
    logger.info('%s' % '-' * 70)
    end = 'FAILED (failures=%i)' % failures if failures else 'OK'
    logger.info('Ran %i scripttests in %0.3fs\n\n%s', num, time, end)
    sys.exit(failures)

if __name__ == '__main__':
    # pylint: disable=invalid-name
    csv2ofx = p.join(PARENT_DIR, 'bin', 'csv2ofx')

    def gen_test(raw):
        """Generate test arguments"""
        for opts, _in, _out in raw:
            if _in and _out:
                args = [p.join(EXAMPLE_DIR, _in)]
                yield (opts, args, p.join(CHECK_DIR, _out))
            else:
                yield (opts, _in, _out)

    MINT_ALT_OPTS = ['-oqs20150613', '-e20150614', '-m mint']
    SERVER_DATE = '-D 20161031112908'
    SPLIT_OPTS = ['-o', '-m split_account', SERVER_DATE]
    PRE_TESTS = [
        (['--help'], [], True),
        (['-oq'], 'default.csv', 'default.qif'),
        (['-oq', '-m split_account'], 'default.csv', 'default_w_splits.qif'),
        (['-oqc Description', '-m xero'], 'xero.csv', 'xero.qif'),
        (['-oq', '-m mint'], 'mint.csv', 'mint.qif'),
        (MINT_ALT_OPTS, 'mint.csv', 'mint_alt.qif'),
        (['-oe 20150908', SERVER_DATE], 'default.csv', 'default.ofx'),
        (SPLIT_OPTS, 'default.csv', 'default_w_splits.ofx'),
        (['-o', '-m mint', SERVER_DATE], 'mint.csv', 'mint.ofx'),
        (['-oq', '-m creditunion'], 'creditunion.csv', 'creditunion.qif'),
        (['-E windows-1252', '-m gls', SERVER_DATE, '-e 20171111', '-o'], 'gls.csv', 'gls.ofx'),
    ]

    main(csv2ofx, gen_test(PRE_TESTS))
<commit_msg>Fix whitespace issues.<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: sw=4:ts=4:expandtab

"""
tests.test
~~~~~~~~~~

Provides scripttests to test csv2ofx CLI functionality.
"""

from __future__ import (
    absolute_import, division, print_function, unicode_literals)

import sys

from difflib import unified_diff
from os import path as p
from io import StringIO, open
from timeit import default_timer as timer
from builtins import *

import pygogo as gogo

from scripttest import TestFileEnvironment

sys.path.append('../csv2ofx')

PARENT_DIR = p.abspath(p.dirname(p.dirname(__file__)))
EXAMPLE_DIR = p.join(PARENT_DIR, 'data', 'test')
CHECK_DIR = p.join(PARENT_DIR, 'data', 'converted')

def filter_output(outlines, debug_stmts=None):
    """Remove meza debugging statements"""
    def_stmts = ['File was opened in', 'Decoding file with encoding']
    debug_stmts = debug_stmts or def_stmts

    for line in outlines:
        if not any(stmt in line for stmt in debug_stmts):
            yield line

def main(script, tests, verbose=False, stop=True):
    """
    Returns 0 on success, 1 on failure
    """
    failures = 0
    logger = gogo.Gogo(__name__, verbose=verbose).logger
    short_script = p.basename(script)
    env = TestFileEnvironment('.scripttest')

    start = timer()
    for pos, test in enumerate(tests):
        num = pos + 1
        opts, arguments, expected = test
        joined_opts = ' '.join(opts) if opts else ''
        joined_args = '"%s"' % '" "'.join(arguments) if arguments else ''
        command = "%s %s %s" % (script, joined_opts, joined_args)
        short_command = "%s %s %s" % (short_script, joined_opts, joined_args)
        result = env.run(command, cwd=PARENT_DIR, expect_stderr=True)
        output = result.stdout

        if isinstance(expected, bool):
            text = StringIO(output).read()
            outlines = [str(bool(text))]
            checklines = StringIO(str(expected)).readlines()
        elif p.isfile(expected):
            outlines = StringIO(output).readlines()

            with open(expected, encoding='utf-8') as f:
                checklines = f.readlines()
        else:
            outlines = StringIO(output).readlines()
            checklines = StringIO(expected).readlines()

        args = [checklines, list(filter_output(outlines))]
        kwargs = {'fromfile': 'expected', 'tofile': 'got'}
        diffs = ''.join(unified_diff(*args, **kwargs))

        if diffs:
            failures += 1
            msg = "ERROR! Output from test #%i:\n  %s\n" % (num, short_command)
            msg += "doesn't match:\n  %s\n" % expected
            msg += diffs if diffs else ''
        else:
            logger.debug(output)
            msg = 'Scripttest #%i: %s ... ok' % (num, short_command)

        logger.info(msg)

        if stop and failures:
            break

    time = timer() - start
    logger.info('%s' % '-' * 70)
    end = 'FAILED (failures=%i)' % failures if failures else 'OK'
    logger.info('Ran %i scripttests in %0.3fs\n\n%s', num, time, end)
    sys.exit(failures)

if __name__ == '__main__':
    # pylint: disable=invalid-name
    csv2ofx = p.join(PARENT_DIR, 'bin', 'csv2ofx')

    def gen_test(raw):
        """Generate test arguments"""
        for opts, _in, _out in raw:
            if _in and _out:
                args = [p.join(EXAMPLE_DIR, _in)]
                yield (opts, args, p.join(CHECK_DIR, _out))
            else:
                yield (opts, _in, _out)

    MINT_ALT_OPTS = ['-oqs20150613', '-e20150614', '-m mint']
    SERVER_DATE = '-D 20161031112908'
    SPLIT_OPTS = ['-o', '-m split_account', SERVER_DATE]
    PRE_TESTS = [
        (['--help'], [], True),
        (['-oq'], 'default.csv', 'default.qif'),
        (['-oq', '-m split_account'], 'default.csv', 'default_w_splits.qif'),
        (['-oqc Description', '-m xero'], 'xero.csv', 'xero.qif'),
        (['-oq', '-m mint'], 'mint.csv', 'mint.qif'),
        (MINT_ALT_OPTS, 'mint.csv', 'mint_alt.qif'),
        (['-oe 20150908', SERVER_DATE], 'default.csv', 'default.ofx'),
        (SPLIT_OPTS, 'default.csv', 'default_w_splits.ofx'),
        (['-o', '-m mint', SERVER_DATE], 'mint.csv', 'mint.ofx'),
        (['-oq', '-m creditunion'], 'creditunion.csv', 'creditunion.qif'),
        (
            ['-E windows-1252', '-m gls', SERVER_DATE, '-e 20171111', '-o'],
            'gls.csv', 'gls.ofx'
        ),
    ]

    main(csv2ofx, gen_test(PRE_TESTS))
<|endoftext|>


<commit_before>import time

TIMEOUT = 5  # Seconds

def invincible(func):
    def wrap():
        try:
            return func()
        except:
            pass
    return wrap

class WaitForTimeout(Exception):
    """This should only be raised inside the `wait_for` function."""
    pass

def wait_for(condition_function, expected_value=None, timeout=TIMEOUT,
             get_message=None, *args, **kwargs):
    """
    Waits for condition_function to return truthy or raises WaitForTimeout.
    :param (function) condition_function: Should return truthy or
        expected_value on success.
    :param (function) get_message: Optional failure message function
    :param expected_value: Optional return value to wait for. If omitted,
        success is any truthy value.
    :param (float) timeout: max seconds to wait. Defaults to 5
    :param args: Optional args to pass to condition_function.
    :param kwargs: Optional kwargs to pass to condition_function.
        if `timeout` is in kwargs, it will be used to override TIMEOUT
    :raises: WaitForTimeout If condition_function doesn't return True in time.
    Usage:
        def get_element(selector):
            # some code to get some element or return a `False`-y value.
        selector = '.js-plotly-plot'
        try:
            wait_for(get_element, selector)
        except WaitForTimeout:
            self.fail('element never appeared...')
        plot = get_element(selector)  # we know it exists.
    """
    def wrapped_condition_function():
        """We wrap this to alter the call base on the closure."""
        if args and kwargs:
            return condition_function(*args, **kwargs)
        if args:
            return condition_function(*args)
        if kwargs:
            return condition_function(**kwargs)
        return condition_function()

    start_time = time.time()
    while time.time() < start_time + timeout:
        condition_val = wrapped_condition_function()
        if expected_value is None:
            if condition_val:
                return True
        elif condition_val == expected_value:
            return True
        time.sleep(0.5)

    if get_message:
        message = get_message()
    elif expected_value:
        message = 'Final value: {}'.format(condition_val)
    else:
        message = ''

    raise WaitForTimeout(message)

def assert_clean_console(TestClass):
    def assert_no_console_errors(TestClass):
        TestClass.assertEqual(
            TestClass.driver.execute_script(
                'return window.tests.console.error.length'
            ),
            0
        )

    def assert_no_console_warnings(TestClass):
        TestClass.assertEqual(
            TestClass.driver.execute_script(
                'return window.tests.console.warn.length'
            ),
            0
        )

    assert_no_console_warnings(TestClass)
    assert_no_console_errors(TestClass)
<commit_msg>reorder wait_for args for backward compat for non-kwarg callers<commit_after>import time

TIMEOUT = 5  # Seconds

def invincible(func):
    def wrap():
        try:
            return func()
        except:
            pass
    return wrap

class WaitForTimeout(Exception):
    """This should only be raised inside the `wait_for` function."""
    pass

def wait_for(condition_function, get_message=None, expected_value=None,
             timeout=TIMEOUT, *args, **kwargs):
    """
    Waits for condition_function to return truthy or raises WaitForTimeout.
    :param (function) condition_function: Should return truthy or
        expected_value on success.
    :param (function) get_message: Optional failure message function
    :param expected_value: Optional return value to wait for. If omitted,
        success is any truthy value.
    :param (float) timeout: max seconds to wait. Defaults to 5
    :param args: Optional args to pass to condition_function.
    :param kwargs: Optional kwargs to pass to condition_function.
        if `timeout` is in kwargs, it will be used to override TIMEOUT
    :raises: WaitForTimeout If condition_function doesn't return True in time.
    Usage:
        def get_element(selector):
            # some code to get some element or return a `False`-y value.
        selector = '.js-plotly-plot'
        try:
            wait_for(get_element, selector)
        except WaitForTimeout:
            self.fail('element never appeared...')
        plot = get_element(selector)  # we know it exists.
    """
    def wrapped_condition_function():
        """We wrap this to alter the call base on the closure."""
        if args and kwargs:
            return condition_function(*args, **kwargs)
        if args:
            return condition_function(*args)
        if kwargs:
            return condition_function(**kwargs)
        return condition_function()

    start_time = time.time()
    while time.time() < start_time + timeout:
        condition_val = wrapped_condition_function()
        if expected_value is None:
            if condition_val:
                return True
        elif condition_val == expected_value:
            return True
        time.sleep(0.5)

    if get_message:
        message = get_message()
    elif expected_value:
        message = 'Final value: {}'.format(condition_val)
    else:
        message = ''

    raise WaitForTimeout(message)

def assert_clean_console(TestClass):
    def assert_no_console_errors(TestClass):
        TestClass.assertEqual(
            TestClass.driver.execute_script(
                'return window.tests.console.error.length'
            ),
            0
        )

    def assert_no_console_warnings(TestClass):
        TestClass.assertEqual(
            TestClass.driver.execute_script(
                'return window.tests.console.warn.length'
            ),
            0
        )

    assert_no_console_warnings(TestClass)
    assert_no_console_errors(TestClass)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from bottle import route, run

# 現在はURLから温度・湿度を取得
# TODO - 別の取得方法を後々実装する
@route('/<temperature:int>/<humidity:int>')
def print_hukaiShisu(temperature,humidity):
    hukai_shisu_result = 0.81 * float(temperature) + 0.01 * float(humidity) * (0.99 * float(temperature) - 14.3) + 46.3
    print "run hukai_shisu_gui"
    return "温度 : " + str(temperature) +  " 湿度 : " + str(humidity) + " 不快指数 : " + str(hukai_shisu_result)

# サーバーの設定(開発用) - debugとreloaderを有効にしている
run(host='localhost', port=8081, debug=True, reloader=True)
<commit_msg>GUI版をPython3に対応<commit_after># -*- coding: utf-8 -*-
from bottle import route, run

# 現在はURLから温度・湿度を取得
# TODO - 別の取得方法を後々実装する
@route('/<temperature:int>/<humidity:int>')
def print_hukaiShisu(temperature,humidity):
    hukai_shisu_result = 0.81 * float(temperature) + 0.01 * float(humidity) * (0.99 * float(temperature) - 14.3) + 46.3
    print("run hukai_shisu_gui")
    return "温度 : " + str(temperature) +  " 湿度 : " + str(humidity) + " 不快指数 : " + str(hukai_shisu_result)

# サーバーの設定(開発用) - debugとreloaderを有効にしている
run(host='localhost', port=8081, debug=True, reloader=True)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-04-10 16:19
from __future__ import unicode_literals

import wizard_builder.models

import django.db.models.deletion
import django.db.models.manager
from django.db import migrations, models

class Migration(migrations.Migration):

    dependencies = [
        ('sites', '0002_alter_domain_unique'),
        ('wizard_builder', '0002_remove_formquestion_example'),
    ]

    operations = [
        migrations.AlterModelManagers(
            name='questionpage',
            managers=[
                ('objects', django.db.models.manager.Manager()),
                ('base_objects', django.db.models.manager.Manager()),
            ],
        ),
        migrations.AlterModelManagers(
            name='textpage',
            managers=[
                ('objects', django.db.models.manager.Manager()),
                ('base_objects', django.db.models.manager.Manager()),
            ],
        ),
        migrations.AddField(
            model_name='pagebase',
            name='site',
            field=models.ForeignKey(default=wizard_builder.models.get_current_site_wrapper, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'),
        ),
    ]
<commit_msg>rely on an earlier site migration<commit_after># -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2017-04-10 16:19
from __future__ import unicode_literals

import wizard_builder.models

import django.db.models.deletion
import django.db.models.manager
from django.db import migrations, models

class Migration(migrations.Migration):

    dependencies = [
        ('sites', '0001_initial'),
        ('wizard_builder', '0002_remove_formquestion_example'),
    ]

    operations = [
        migrations.AlterModelManagers(
            name='questionpage',
            managers=[
                ('objects', django.db.models.manager.Manager()),
                ('base_objects', django.db.models.manager.Manager()),
            ],
        ),
        migrations.AlterModelManagers(
            name='textpage',
            managers=[
                ('objects', django.db.models.manager.Manager()),
                ('base_objects', django.db.models.manager.Manager()),
            ],
        ),
        migrations.AddField(
            model_name='pagebase',
            name='site',
            field=models.ForeignKey(default=wizard_builder.models.get_current_site_wrapper, on_delete=django.db.models.deletion.CASCADE, to='sites.Site'),
        ),
    ]
<|endoftext|>


<commit_before>"""Fun section of CLI command."""
import json
import logging
import time
from pprint import pformat, pprint

import click
from fabric.colors import red

@click.group()
def cli():
    """My fun program!"""
    pass

@cli.command()
def progress():
    """Sample progress bar."""
    i = range(0, 200)
    logging.debug('%s -> %s', i[0], i[-1])
    with click.progressbar(i, width=0, fill_char=red('#')) as items:
        for _ in items:
            time.sleep(.01)

@cli.command('open')
def fun_open():
    """Trying out click.launch."""
    sites = {
        'Google': 'https://google.com',
        'The Verge': 'https://theverge.com',
        'Liliputing': 'https://liliputing.com'
    }

    sites_keys = sites.keys()
    for index, site in enumerate(sites_keys):
        click.echo('%i %s' % (index, site))

    choice = click.prompt('Which site to open?', default=0, type=int)
    click.launch(sites[sites_keys[choice]])

@cli.command()
def party():
    """Get this party started!"""
    for i in range(10):
        click.echo('Wub wub wub')
        logging.debug(i)

@cli.command()
@click.option('-d', '--destination', prompt=True)
def to(destination):
    """Connecting fun to stuffs!"""
    click.echo('Apparently you are going to ' + destination)

@cli.command('max')
def fun_max():
    """Maximum levels achieved."""
    click.echo('You found the highest peak!')

@cli.command()
def hop():
    """The hopping function."""
    click.echo('Hop hop hop, \'til you just can stop!')

@cli.command()
def j():
    """Example JSON."""
    test_object = {'this': 'that', 'up': 'down', 'sub': {'can': 'do'}}
    print json.dumps(test_object, indent=2)
    pprint(test_object, indent=2)
    print pformat(test_object, indent=2)
    print pformat(test_object, indent=2, depth=1)
    print test_object.viewitems()
    print test_object.values()
    print test_object.viewvalues()
<commit_msg>to: Better function name 'fun_to'<commit_after>"""Fun section of CLI command."""
import json
import logging
import time
from pprint import pformat, pprint

import click
from fabric.colors import red

@click.group()
def cli():
    """My fun program!"""
    pass

@cli.command()
def progress():
    """Sample progress bar."""
    i = range(0, 200)
    logging.debug('%s -> %s', i[0], i[-1])
    with click.progressbar(i, width=0, fill_char=red('#')) as items:
        for _ in items:
            time.sleep(.01)

@cli.command('open')
def fun_open():
    """Trying out click.launch."""
    sites = {
        'Google': 'https://google.com',
        'The Verge': 'https://theverge.com',
        'Liliputing': 'https://liliputing.com'
    }

    sites_keys = sites.keys()
    for index, site in enumerate(sites_keys):
        click.echo('%i %s' % (index, site))

    choice = click.prompt('Which site to open?', default=0, type=int)
    click.launch(sites[sites_keys[choice]])

@cli.command()
def party():
    """Get this party started!"""
    for i in range(10):
        click.echo('Wub wub wub')
        logging.debug(i)

@cli.command('to')
@click.option('-d', '--destination', prompt=True)
def fun_to(destination):
    """Connecting fun to stuffs!"""
    click.echo('Apparently you are going to ' + destination)

@cli.command('max')
def fun_max():
    """Maximum levels achieved."""
    click.echo('You found the highest peak!')

@cli.command()
def hop():
    """The hopping function."""
    click.echo('Hop hop hop, \'til you just can stop!')

@cli.command()
def j():
    """Example JSON."""
    test_object = {'this': 'that', 'up': 'down', 'sub': {'can': 'do'}}
    print json.dumps(test_object, indent=2)
    pprint(test_object, indent=2)
    print pformat(test_object, indent=2)
    print pformat(test_object, indent=2, depth=1)
    print test_object.viewitems()
    print test_object.values()
    print test_object.viewvalues()
<|endoftext|>


<commit_before>from KerbalStuff.config import _cfg
from github import Github
from flask import url_for
import subprocess
import json
import os
import re

# TODO(Thomas): Make this modular
def send_to_ckan(mod):
    if not _cfg("netkan_repo_path"):
        return
    if not mod.ckan:
        return
    json_blob = {
        'spec_version': 'v1.4',
        'identifier': re.sub(r'\W+', '', mod.name),
        '$kref': '#/ckan/spacedock/' + str(mod.id),
        'license': mod.license,
        'x_via': 'Automated ' + _cfg('site-name') + ' CKAN submission'
    }
    wd = _cfg("netkan_repo_path")
    path = os.path.join(wd, 'NetKAN', json_blob['identifier'] + '.netkan')

    if os.path.exists(path):
        # If the file is already there, then chances are this mod has already been indexed
        return

    with open(path, 'w') as f:
        f.write(json.dumps(json_blob, indent=4))
    subprocess.call(['git', 'fetch', 'origin'], cwd=wd)
    subprocess.call(['git', 'checkout', '-b', 'add-' + json_blob['identifier'], 'origin/master'], cwd=wd)
    subprocess.call(['git', 'add', '-A'], cwd=wd)
    subprocess.call(['git', 'commit', '-m', 'Add {0} from '.format(mod.name) + _cfg('site-name') + '\n\nThis is an automated commit on behalf of {1}'\
            .format(mod.name, mod.user.username), '--author={0} <{1}>'.format(mod.user.username, mod.user.email)], cwd=wd)
    subprocess.call(['git', 'push', '-u', 'origin', 'add-' + json_blob['identifier']], cwd=wd)
    g = Github(_cfg('github_user'), _cfg('github_pass'))
    r = g.get_repo("KSP-CKAN/NetKAN")
    r.create_pull(title="Add {0} from ".format(mod.name) + _cfg('site-name'), base=r.default_branch, head=_cfg('github_user') + ":add-" + json_blob['identifier'], body=\
"""\
This pull request was automatically generated by """ + _cfg('site-name') + """ on behalf of {0}, to add [{1}]({4}{2}) to CKAN.

Please direct questions about this pull request to [{0}]({4}{3}).
""".format(mod.user.username, mod.name,\
    url_for('mods.mod', mod_name=mod.name, id=mod.id),\
    url_for("profile.view_profile", username=mod.user.username),\
    _cfg("protocol") + "://" + _cfg("domain")))
<commit_msg>Give CKAN more info in automated PRs<commit_after>from KerbalStuff.config import _cfg
from github import Github
from flask import url_for
import subprocess
import json
import os
import re

# TODO(Thomas): Make this modular
def send_to_ckan(mod):
    if not _cfg("netkan_repo_path"):
        return
    if not mod.ckan:
        return
    json_blob = {
        'spec_version': 'v1.4',
        'identifier': re.sub(r'\W+', '', mod.name),
        '$kref': '#/ckan/spacedock/' + str(mod.id),
        'license': mod.license,
        'x_via': 'Automated ' + _cfg('site-name') + ' CKAN submission'
    }
    wd = _cfg("netkan_repo_path")
    path = os.path.join(wd, 'NetKAN', json_blob['identifier'] + '.netkan')

    if os.path.exists(path):
        # If the file is already there, then chances are this mod has already been indexed
        return

    with open(path, 'w') as f:
        f.write(json.dumps(json_blob, indent=4))
    subprocess.call(['git', 'fetch', 'origin'], cwd=wd)
    subprocess.call(['git', 'checkout', '-b', 'add-' + json_blob['identifier'], 'origin/master'], cwd=wd)
    subprocess.call(['git', 'add', '-A'], cwd=wd)
    subprocess.call(['git', 'commit', '-m', 'Add {0} from '.format(mod.name) + _cfg('site-name') + '\n\nThis is an automated commit on behalf of {1}'\
            .format(mod.name, mod.user.username), '--author={0} <{1}>'.format(mod.user.username, mod.user.email)], cwd=wd)
    subprocess.call(['git', 'push', '-u', 'origin', 'add-' + json_blob['identifier']], cwd=wd)
    g = Github(_cfg('github_user'), _cfg('github_pass'))
    r = g.get_repo("KSP-CKAN/NetKAN")
    r.create_pull(title="Add {0} from ".format(mod.name) + _cfg('site-name'), base=r.default_branch, head=_cfg('github_user') + ":add-" + json_blob['identifier'], body=\
"""\
This pull request was automatically generated by """ + _cfg('site-name') + """ on behalf of {0}, to add [{1}]({4}{2}) to CKAN.

Mod details:
    name = {2}
    author = {0}
    description = {5}
    abstract = {6}
    license = {7}
    Homepage = {8}

Please direct questions about this pull request to [{0}]({4}{3}).
""".format(mod.user.username, mod.name,\
    url_for('mods.mod', mod_name=mod.name, id=mod.id),\
    url_for("profile.view_profile", username=mod.user.username),\
    _cfg("protocol") + "://" + _cfg("domain"),\
    mod.description, short_description,\
    license, external_link))
<|endoftext|>