Skip to content

Commit

Permalink
Add cookiecutter GUI generation and handling
Browse files Browse the repository at this point in the history
  • Loading branch information
goanpeca committed Jun 4, 2020
1 parent b70b3fd commit e7f649b
Show file tree
Hide file tree
Showing 12 changed files with 789 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .github/scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ if [ "$USE_CONDA" = "true" ]; then
fi

# Install main dependencies
conda install python=$PYTHON_VERSION --file requirements/conda.txt -q -y
conda install python=$PYTHON_VERSION --file requirements/conda.txt -q -y -c spyder-ide/label/alpha

# Install test ones
conda install python=$PYTHON_VERSION --file requirements/tests.txt -c spyder-ide -q -y
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ $ workon spyder-dev
After you have created your development environment, you need to install Spyder's necessary dependencies. The easiest way to do so (with Anaconda) is

```bash
$ conda install -c spyder-ide --file requirements/conda.txt
$ conda install -c spyder-ide/label/alpha --file requirements/conda.txt
```

This installs all Spyder's dependencies into the environment.
Expand Down
1 change: 1 addition & 0 deletions binder/environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies:
- atomicwrites >=1.2.0
- chardet >=2.0.0
- cloudpickle >=0.5.0
- cookiecutter >=1.6.0
- diff-match-patch >=20181111
- intervaltree
- ipython >=4.0
Expand Down
1 change: 1 addition & 0 deletions requirements/conda.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ applaunchservices >=0.1.7
atomicwrites >=1.2.0
chardet >=2.0.0
cloudpickle >=0.5.0
cookiecutter >=1.6.0
diff-match-patch >=20181111
intervaltree
IPython >=4.0
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ def run(self):
'atomicwrites>=1.2.0',
'chardet>=2.0.0',
'cloudpickle>=0.5.0',
'cookiecutter>=1.6.0',
'diff-match-patch>=20181111',
'intervaltree',
'ipython>=4.0',
Expand Down
7 changes: 6 additions & 1 deletion spyder/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
ATOMICWRITES_REQVER = '>=1.2.0'
CHARDET_REQVER = '>=2.0.0'
CLOUDPICKLE_REQVER = '>=0.5.0'
COOKIECUTTER_REQVER = '>=1.6.0'
DIFF_MATCH_PATCH_REQVER = '>=20181111'
INTERVALTREE_REQVER = None
IPYTHON_REQVER = ">=4.0;<6.0" if PY2 else ">=4.0"
Expand Down Expand Up @@ -94,6 +95,10 @@
'package_name': "cloudpickle",
'features': _("Handle communications between kernel and frontend"),
'required_version': CLOUDPICKLE_REQVER},
{'modname': "cookiecutter",
'package_name': "cookiecutter",
'features': _("Create projects from cookiecutter templates"),
'required_version': COOKIECUTTER_REQVER},
{'modname': "diff_match_patch",
'package_name': "diff-match-patch",
'features': _("Compute text file diff changes during edition"),
Expand Down Expand Up @@ -199,7 +204,7 @@
{'modname': "watchdog",
'package_name': "watchdog",
'features': _("Watch file changes on project directories"),
'required_version': WATCHDOG_REQVER}
'required_version': WATCHDOG_REQVER},
]


Expand Down
56 changes: 56 additions & 0 deletions spyder/plugins/projects/utils/cookie.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)

"""
Cookiecutter utilities.
"""

import json
import os

from cookiecutter.main import cookiecutter


def generate_cookiecutter_project(cookiecutter_path, output_path,
extra_content=None):
"""
Generate a cookicutter project programmatically.
"""
status = True
try:
result = cookiecutter(
cookiecutter_path,
output_dir=output_path,
overwrite_if_exists=True,
extra_context=extra_content,
no_input=True,
)
except Exception as err:
result = err
status = False

return status, result


def load_cookiecutter_project(project_path):
"""
Load a cookicutter options and pre-hook script.
"""
options = None
pre_gen_code = None
cookiepath = os.path.join(project_path, "cookiecutter.json")
pre_gen_path = os.path.join(project_path, "hooks", "pre_gen_project.py")

if os.path.isdir(project_path):
if os.path.isfile(cookiepath):
with open(cookiepath, 'r') as fh:
options = json.loads(fh.read())

if os.path.isfile(pre_gen_path):
with open(pre_gen_path, 'r') as fh:
pre_gen_code = fh.read()

return options, pre_gen_code
9 changes: 9 additions & 0 deletions spyder/plugins/projects/utils/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright (c) Spyder Project Contributors
#
# Licensed under the terms of the MIT License
# (see LICENSE.txt for details)
# -----------------------------------------------------------------------------

"""Tests."""
137 changes: 137 additions & 0 deletions spyder/plugins/projects/utils/tests/test_cookie.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
#

"""
Tests for qcookiecutter widget.
"""

# Standard library imports
import json
import os
import shutil
import tempfile

# Third party imports
import pytest

# Local imports
from spyder.plugins.projects.utils.cookie import (
generate_cookiecutter_project, load_cookiecutter_project)


def test_load_cookiecutter_project_config():
settings = {
"opt_1": "value",
"opt_2": "{{ cookiecutter.opt_1 }}",
}
temp_path = tempfile.mkdtemp(suffix='-some-cookiecutter')
temp_cookie_path = os.path.join(temp_path, 'cookiecutter.json')

with open(temp_cookie_path, 'w') as fh:
fh.write(json.dumps(settings, sort_keys=True))

sets, pre_gen_code = load_cookiecutter_project(temp_path)
assert settings == sets
assert pre_gen_code is None

shutil.rmtree(temp_path)


def test_load_cookiecutter_project_hooks():
settings = {
"opt_1": "value",
"opt_2": "{{ cookiecutter.opt_1 }}",
}
pre_gen_code = "import sys\n\nprint('test!')\nsys.exit(1)\n"
temp_path = tempfile.mkdtemp(suffix='-some-cookiecutter')
temp_cookie_path = os.path.join(temp_path, 'cookiecutter.json')
temp_hooks_path = os.path.join(temp_path, 'hooks')
temp_hooks_pre_path = os.path.join(temp_hooks_path, 'pre_gen_project.py')
os.makedirs(temp_hooks_path)

with open(temp_cookie_path, 'w') as fh:
fh.write(json.dumps(settings, sort_keys=True))

with open(temp_hooks_pre_path, 'w') as fh:
fh.write(pre_gen_code)

sets, pre_gen_code = load_cookiecutter_project(temp_path)
assert settings == sets
assert pre_gen_code == pre_gen_code

shutil.rmtree(temp_path)


def test_generate_cookiecutter_project_defaults():
settings = {
"repo_name": "value",
}
temp_path = tempfile.mkdtemp(suffix='-some-cookiecutter')
temp_path_created = tempfile.mkdtemp(suffix='-created-project')
temp_cookie_path = os.path.join(temp_path, 'cookiecutter.json')
temp_project_path = os.path.join(temp_path, '{{cookiecutter.repo_name}}')
os.makedirs(temp_project_path)

with open(temp_cookie_path, 'w') as fh:
fh.write(json.dumps(settings, sort_keys=True))

status, result = generate_cookiecutter_project(
temp_path,
temp_path_created,
)
assert "value" in result
assert status is True
shutil.rmtree(temp_path)


def test_generate_cookiecutter_project_extra_content():
settings = {
"repo_name": "value",
}
temp_path = tempfile.mkdtemp(suffix='-some-cookiecutter')
temp_path_created = tempfile.mkdtemp(suffix='-created-project')
temp_cookie_path = os.path.join(temp_path, 'cookiecutter.json')
temp_project_path = os.path.join(temp_path, '{{cookiecutter.repo_name}}')
os.makedirs(temp_project_path)

with open(temp_cookie_path, 'w') as fh:
fh.write(json.dumps(settings, sort_keys=True))

status, result = generate_cookiecutter_project(
temp_path,
temp_path_created,
{"repo_name": "boom"},
)
assert "boom" in result
assert status is True
shutil.rmtree(temp_path)


def test_generate_cookiecutter_project_exception():
settings = {
"repo_name": "value",
}
temp_path = tempfile.mkdtemp(suffix='-some-invalid-cookiecutter')
temp_path_created = tempfile.mkdtemp(suffix='-created-project')
temp_cookie_path = os.path.join(temp_path, 'cookiecutter.json')
temp_project_path = os.path.join(
temp_path,
'{{cookiecutter.not_foun_variable}}',
)
os.makedirs(temp_project_path)

with open(temp_cookie_path, 'w') as fh:
fh.write(json.dumps(settings, sort_keys=True))

status, __ = generate_cookiecutter_project(
temp_path,
temp_path_created,
)
assert status is False


if __name__ == "__main__":
pytest.main()
Loading

0 comments on commit e7f649b

Please sign in to comment.