File: C:/Users/fred/anaconda3/Lib/site-packages/spyder/plugins/projects/widgets/qcookiecutter.py
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright © Spyder Project Contributors
#
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)
# -----------------------------------------------------------------------------
"""
Cookiecutter widget.
"""
import sys
import tempfile
from collections import OrderedDict
from jinja2 import Template
from qtpy import QtCore
from qtpy import QtWidgets
class Namespace:
"""
Namespace to provide a holder for attributes when rendering a template.
"""
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
class CookiecutterDialog(QtWidgets.QDialog):
"""
QDialog to display cookiecutter.json options.
cookiecutter_settings: dict
A cookiecutter.json settings content.
pre_gen_code: str
The code of the pregeneration script.
"""
sig_validated = QtCore.Signal(int, str)
"""
This signal is emitted after validation has been executed.
It provides the process exit code and the output captured.
"""
def __init__(self, parent, cookiecutter_settings=None, pre_gen_code=None):
super().__init__(parent)
self._widget = CookiecutterWidget(
self, cookiecutter_settings,
pre_gen_code
)
self._info_label = QtWidgets.QLabel()
self._validate_button = QtWidgets.QPushButton("Validate")
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self._widget)
layout.addWidget(self._info_label)
layout.addWidget(self._validate_button)
self.setLayout(layout)
# Signals
self._validate_button.clicked.connect(self.validate)
self._widget.sig_validated.connect(self._set_message)
self._widget.sig_validated.connect(self.sig_validated)
def _set_message(self, exit_code, message):
if exit_code != 0:
self._info_label.setText(message)
def setup(self, cookiecutter_settings):
"""
Setup the widget using options.
"""
self._widget.setup(cookiecutter_settings)
def set_pre_gen_code(self, pre_gen_code):
"""
Set the cookiecutter pregeneration code.
"""
self._widget.set_pre_gen_code(pre_gen_code)
def validate(self):
"""
Run, pre generation script and provide information on finished.
"""
self._widget.validate()
def get_values(self):
"""
Return all entered and generated values.
"""
return self._widget.get_values()
class CookiecutterWidget(QtWidgets.QWidget):
"""
QWidget to display cookiecutter.json options.
cookiecutter_settings: dict
A cookiecutter.json settings content.
pre_gen_code: str
The code of the pregeneration script.
"""
sig_validated = QtCore.Signal(int, str)
"""
This signal is emitted after validation has been executed.
It provides the process exit code and the output captured.
"""
def __init__(self, parent, cookiecutter_settings=None, pre_gen_code=None):
super().__init__(parent)
# Attributes
self._parent = parent
self._cookiecutter_settings = cookiecutter_settings
self._pre_gen_code = pre_gen_code
self._widgets = OrderedDict()
self._defined_settings = OrderedDict()
self._rendered_settings = OrderedDict()
self._process = None
self._tempfile = tempfile.mkstemp(suffix=".py")[-1]
# Cookiecutter special variables
self._extensions = None
self._copy_without_render = None
self._new_lines = None
self._private_vars = None
self._rendered_private_var = None
# Layout
self._form_layout = QtWidgets.QFormLayout()
self._form_layout.setFieldGrowthPolicy(
self._form_layout.AllNonFixedFieldsGrow)
self.setLayout(self._form_layout)
# --- Helpers
# ------------------------------------------------------------------------
def _check_jinja_options(self):
"""
Check which values are Jinja2 expressions.
"""
if self._cookiecutter_settings:
# https://cookiecutter.readthedocs.io/en/latest/advanced/template_extensions.html
self._extensions = self._cookiecutter_settings.pop("_extensions",
[])
# https://cookiecutter.readthedocs.io/en/latest/advanced/copy_without_render.html
self._copy_without_render = self._cookiecutter_settings.pop(
"_copy_without_render", [])
# https://cookiecutter.readthedocs.io/en/latest/advanced/new_line_characters.html
self._new_lines = self._cookiecutter_settings.pop("_new_lines", "")
for setting, value in self._cookiecutter_settings.items():
# Treat everything like a list for convenience
if isinstance(value, dict):
# https://cookiecutter.readthedocs.io/en/latest/advanced/dict_variables.html
list_values = list(value.keys())
elif not isinstance(value, list):
list_values = [value]
else:
list_values = value
are_rendered_values = []
if list_values and value:
for list_value in list_values:
template = Template(list_value)
rendered_value = template.render(
cookiecutter=Namespace(
**self._cookiecutter_settings))
are_rendered_values.append(
list_value != rendered_value)
if any(are_rendered_values):
self._rendered_settings[setting] = value
else:
self._defined_settings[setting] = value
def _is_jinja(self, setting):
"""
Check if option contains jinja2 code.
"""
return setting in self._rendered_settings
def _parse_bool_text(self, text):
"""
Convert a text value into a boolean.
"""
value = None
if text.lower() in ["n", "no", "false"]:
value = False
elif text.lower() in ["y", "yes", "true"]:
value = True
return value
def _create_textbox(self, setting, label, default=None):
"""
Create a textbox field.
"""
if default is not None and len(default) > 30:
box = QtWidgets.QTextEdit(parent=self)
box.setText = box.setPlainText
box.text = box.toPlainText
else:
box = QtWidgets.QLineEdit(parent=self)
box.setting = setting
if default is not None:
box.setText(default)
box.textChanged.connect(lambda x=None: self.render())
box.get_value = box.text
box.set_value = lambda text: box.setText(text)
return box
def _create_checkbox(self, setting, label, default=None):
"""
Create a checkbox field.
"""
box = QtWidgets.QCheckBox(parent=self)
box.setting = setting
if default is not None:
new_default = self._parse_bool_text(default)
box.setChecked(new_default)
def _get_value():
bool_to_values = {
self._parse_bool_text(default): default,
not self._parse_bool_text(default): "other-value-" + default
}
return bool_to_values[box.isChecked()]
box.get_value = _get_value
return box
def _create_combobox(self, setting, label, choices, default=None):
"""
Create a combobox field.
"""
box = QtWidgets.QComboBox(parent=self)
if isinstance(choices, dict):
temp = OrderedDict()
for choice, choice_value in choices.items():
box.addItem(choice, {choice: choice_value})
else:
for choice in choices:
box.addItem(choice, choice)
box.setting = setting
box.get_value = box.currentData
return box
def _create_field(self, setting, value):
"""
Create a form field.
"""
label = " ".join(setting.split("_")).capitalize()
if isinstance(value, (list, dict)):
# https://cookiecutter.readthedocs.io/en/latest/advanced/choice_variables.html
widget = self._create_combobox(setting, label, value)
elif isinstance(value, str):
if value.lower() in ["y", "yes", "true", "n", "no", "false"]:
widget = self._create_checkbox(setting, label, default=value)
else:
default = None if self._is_jinja(setting) else value
widget = self._create_textbox(setting, label, default=default)
else:
raise Exception(
"Cookiecutter option '{}'cannot be processed".format(setting))
self._widgets[setting] = (label, widget)
return label, widget
def _on_process_finished(self):
"""
Process output of valiation script.
"""
if self._process is not None:
out = bytes(self._process.readAllStandardOutput()).decode()
error = bytes(self._process.readAllStandardError()).decode()
message = ""
if out:
message += out
if error:
message += error
message = message.replace("\r\n", " ")
message = message.replace("\n", " ")
self.sig_validated.emit(self._process.exitCode(), message)
# --- API
# ------------------------------------------------------------------------
def setup(self, cookiecutter_settings):
"""
Setup the widget using options.
"""
self._cookiecutter_settings = cookiecutter_settings
self._check_jinja_options()
for setting, value in self._cookiecutter_settings.items():
if not setting.startswith(("__", "_")):
label, widget = self._create_field(setting, value)
self._form_layout.addRow(label, widget)
self.render()
def set_pre_gen_code(self, pre_gen_code):
"""
Set the cookiecutter pregeneration code.
"""
self._pre_gen_code = pre_gen_code
def render(self):
"""
Render text that contains Jinja2 expressions and set their values.
"""
cookiecutter_settings = self.get_values()
for setting, value in self._rendered_settings.items():
if not setting.startswith(("__", "_")):
template = Template(value)
val = template.render(
cookiecutter=Namespace(**cookiecutter_settings))
__, widget = self._widgets[setting]
widget.set_value(val)
def get_values(self):
"""
Return all entered and generated values.
"""
cookiecutter_settings = cs = OrderedDict()
if self._cookiecutter_settings:
for setting, value in self._cookiecutter_settings.items():
if setting.startswith(("__", "_")):
cookiecutter_settings[setting] = value
else:
__, widget = self._widgets[setting]
cookiecutter_settings[setting] = widget.get_value()
# Cookiecutter special variables
cookiecutter_settings["_extensions"] = self._extensions
cookiecutter_settings["_copy_without_render"] = (
self._copy_without_render)
cookiecutter_settings["_new_lines"] = self._new_lines
return cookiecutter_settings
def validate(self):
"""
Run, pre generation script and provide information on finished.
"""
if self._pre_gen_code is not None:
cookiecutter_settings = self.get_values()
template = Template(self._pre_gen_code)
val = template.render(
cookiecutter=Namespace(**cookiecutter_settings))
with open(self._tempfile, "w") as fh:
fh.write(val)
if self._process is not None:
self._process.close()
self._process.waitForFinished(1000)
self._process = QtCore.QProcess(self)
self._process.setProgram(sys.executable)
self._process.setArguments([self._tempfile])
self._process.finished.connect(self._on_process_finished)
self._process.start()
if __name__ == "__main__":
from spyder.utils.qthelpers import qapplication
app = qapplication()
dlg = CookiecutterDialog(parent=None)
dlg.setup(
{
"list_option": ["1", "2", "3"],
"checkbox_option": "y",
"checkbox_option_2": "false",
"fixed_option": "goanpeca",
"rendered_option": "{{ cookiecutter.fixed_option|upper }}",
"dict_option": {
"png": {
"name": "Portable Network Graphic",
"library": "libpng",
"apps": [
"GIMP"
]
},
"bmp": {
"name": "Bitmap",
"library": "libbmp",
"apps": [
"Paint",
"GIMP"
]
}
},
"_private": "{{ cookiecutter.fixed_option }}",
"__private_rendered": "{{ cookiecutter.fixed_option }}",
}
)
dlg.set_pre_gen_code('''
import sys
print("HELP!") # spyder: test-skip
sys.exit(10)''')
dlg.show()
sys.exit(app.exec_())