Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce the concept of interpreters #131

Merged
merged 10 commits into from
Nov 7, 2020
11 changes: 6 additions & 5 deletions pyp5js/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import click

from pyp5js import commands
from pyp5js.config import SKETCHBOOK_DIR
from pyp5js.config import SKETCHBOOK_DIR, AVAILABLE_INTERPRETERS, PYODIDE_INTERPRETER, TRANSCRYPT_INTERPRETER


@click.group()
Expand All @@ -19,7 +19,8 @@ def command_line_entrypoint():
@command_line_entrypoint.command('new')
@click.argument('sketch_name')
@click.option('--monitor', '-m', is_flag=True)
def configure_new_sketch(sketch_name, monitor):
@click.option('--interpreter', '-i', type=click.Choice(AVAILABLE_INTERPRETERS), default=TRANSCRYPT_INTERPRETER)
def configure_new_sketch(sketch_name, monitor, interpreter):
"""
Create dir and configure boilerplate

Expand All @@ -32,13 +33,14 @@ def configure_new_sketch(sketch_name, monitor):
Example:
$ pyp5js new my_sketch
"""
files = commands.new_sketch(sketch_name)
files = commands.new_sketch(sketch_name, interpreter)

cprint.ok(f"Your sketch was created!")

compiler = "transcrypt" if interpreter == TRANSCRYPT_INTERPRETER else "pyodide"
if not monitor:
cprint.ok(f"Please, open and edit the file {files.sketch_py} to draw. When you're ready to see your results, just run:")
cmd = f"\t pyp5js transcrypt {sketch_name}"
cmd = f"\t pyp5js {compiler} {sketch_name}"
cprint.ok(cmd)
cprint.ok(f"And open file://{files.index_html.absolute()} on your browser to see yor results!")
else:
Expand All @@ -47,7 +49,6 @@ def configure_new_sketch(sketch_name, monitor):
commands.monitor_sketch(sketch_name)



@command_line_entrypoint.command("transcrypt")
@click.argument("sketch_name")
def transcrypt_sketch(sketch_name):
Expand Down
6 changes: 4 additions & 2 deletions pyp5js/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
from pyp5js.http import pyp5js_web_app
from pyp5js.monitor import monitor_sketch as monitor_sketch_service
from pyp5js.templates_renderers import get_sketch_index_content
from pyp5js.config import TRANSCRYPT_INTERPRETER


def new_sketch(sketch_name):
# TODO precisa aceitar um parâmetro de escolha de compilador
def new_sketch(sketch_name, interpreter=TRANSCRYPT_INTERPRETER):
"""
Creates a new sketch with the required assets and a index.html file, based on pyp5js's templates

Expand All @@ -21,7 +23,7 @@ def new_sketch(sketch_name):
:return: file names
:rtype: list of strings
"""
sketch_files = SketchFiles(sketch_name)
sketch_files = SketchFiles(sketch_name, interpreter=interpreter)
sketch_files.create_sketch_dir()

templates_files = [
Expand Down
4 changes: 4 additions & 0 deletions pyp5js/config.py → pyp5js/config/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from . import sketch
from .sketch import TRANSCRYPT_INTERPRETER, PYODIDE_INTERPRETER
from decouple import config
from pathlib import Path

SKETCHBOOK_DIR = config("SKETCHBOOK_DIR", cast=Path, default=Path.home().joinpath('sketchbook-pyp5js'))

if not SKETCHBOOK_DIR.exists():
SKETCHBOOK_DIR.mkdir()

AVAILABLE_INTERPRETERS = [TRANSCRYPT_INTERPRETER, PYODIDE_INTERPRETER]
29 changes: 29 additions & 0 deletions pyp5js/config/sketch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import json

TRANSCRYPT_INTERPRETER = 'transcrypt'
PYODIDE_INTERPRETER = 'pyodide'

class SketchConfig:

@classmethod
def from_json(cls, json_file_path):
with open(json_file_path) as fd:
config_data = json.load(fd)
return cls(**config_data)

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

def get_index_template(self):
from pyp5js.fs import LibFiles
pyp5js_files = LibFiles()
index_map = {
TRANSCRYPT_INTERPRETER: pyp5js_files.transcrypt_index_html,
PYODIDE_INTERPRETER: pyp5js_files.pyodide_index_html,
}
return index_map[self.interpreter]

def write(self, fname):
with open(fname, "w") as fd:
data = {"interpreter": self.interpreter}
json.dump(data, fd)
23 changes: 20 additions & 3 deletions pyp5js/fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,27 @@
from collections import namedtuple

from pyp5js import config
from pyp5js.config.sketch import SketchConfig
from pyp5js.exceptions import SketchDirAlreadyExistException, InvalidName


SketchUrls = namedtuple('SketchUrls', ['p5_js_url', 'sketch_js_url'])


class SketchFiles():
# TODO now that we have the SketchConfig object, this class name is not good enough
# A better name would be Sketch
TARGET_NAME = 'target'
STATIC_NAME = 'static'

def __init__(self, sketch_name):
def __init__(self, sketch_name, interpreter=config.TRANSCRYPT_INTERPRETER, **cfg):
self.sketch_name = sketch_name
self.from_lib = LibFiles()
if self.config_file.exists():
# TODO add warning to let the user know pyp5js is ignoring cfg
self.config = SketchConfig.from_json(self.config_file)
else:
self.config = SketchConfig(interpreter=interpreter, **cfg)

def validate_name(self):
does_not_start_with_letter_or_underscore = r'^[^a-zA-Z_]'
Expand All @@ -36,6 +44,7 @@ def create_sketch_dir(self):
os.makedirs(self.sketch_dir)
self.static_dir.mkdir()
self.target_dir.mkdir()
self.config.write(self.config_file)

@property
def sketch_exists(self):
Expand Down Expand Up @@ -72,6 +81,10 @@ def target_sketch(self):
def sketch_py(self):
return self.sketch_dir.joinpath(f'{self.sketch_name}.py')

@property
def config_file(self):
return self.sketch_dir.joinpath('properties.json')

@property
def target_dir(self):
return self.sketch_dir.joinpath(self.TARGET_NAME)
Expand Down Expand Up @@ -117,8 +130,12 @@ def target_sketch_template(self):
return self.templates_dir.joinpath('target_sketch.py.template')

@property
def index_html(self):
return self.templates_dir.joinpath('index.html')
def transcrypt_index_html(self):
return self.templates_dir.joinpath('transcrypt_index.html')

@property
def pyodide_index_html(self):
return self.templates_dir.joinpath('pyodide_index.html')

@property
def p5js(self):
Expand Down
22 changes: 22 additions & 0 deletions pyp5js/templates/pyodide_index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!DOCTYPE html>

<!-- pyp5js index.html boilerplate -->
<html lang="">
<head>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ sketch_name }} - pyp5js (using pyodide)</title>
<style> body, html, canvas {padding: 0; margin: 0; overflow: hidden;} </style>

<script src="{{ p5_js_url }}"></script>
<script src="{{ sketch_js_url }}" type="module"></script>
<script src="https://pyodide-cdn2.iodide.io/v0.15.0/full/pyodide.js"></script>
</head>

<body>
<div id="sketch-holder">
<!-- You sketch will go here! -->
</div>
</body>
</html>
File renamed without changes.
3 changes: 2 additions & 1 deletion pyp5js/templates_renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ def get_sketch_index_content(sketch_files):
"p5_js_url": sketch_files.urls.p5_js_url,
"sketch_js_url": sketch_files.urls.sketch_js_url,
}
index_template = templates.get_template(pyp5js_files.index_html.name)
template_file = sketch_files.config.get_index_template()
index_template = templates.get_template(template_file.name)
return index_template.render(context)


Expand Down
14 changes: 13 additions & 1 deletion pyp5js/tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from unittest.mock import Mock, patch

from pyp5js import commands
from pyp5js.config import SKETCHBOOK_DIR
from pyp5js.config import SKETCHBOOK_DIR, TRANSCRYPT_INTERPRETER, PYODIDE_INTERPRETER
from pyp5js.exceptions import PythonSketchDoesNotExist, SketchDirAlreadyExistException, InvalidName
from pyp5js.fs import SketchFiles

Expand Down Expand Up @@ -78,6 +78,18 @@ def test_create_new_sketch_with_all_required_files(self):
assert self.sketch_files.index_html.exists()
assert self.sketch_files.sketch_py.exists()
assert self.sketch_files.p5js.exists()
assert self.sketch_files.config_file.exists()
assert self.sketch_files.config.interpreter == TRANSCRYPT_INTERPRETER

def test_create_pyodide_sketch(self):
commands.new_sketch(self.sketch_name, interpreter=PYODIDE_INTERPRETER)
self.sketch_files = SketchFiles(self.sketch_name) # read config after init

assert self.sketch_files.index_html.exists()
assert self.sketch_files.sketch_py.exists()
assert self.sketch_files.p5js.exists()
assert self.sketch_files.config_file.exists()
assert self.sketch_files.config.interpreter == PYODIDE_INTERPRETER

def test_raise_exception_if_dir_already_exist(self):
self.sketch_files.create_sketch_dir()
Expand Down
78 changes: 78 additions & 0 deletions pyp5js/tests/test_config/test_sketch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import json
import os
from pytest import fixture
from pathlib import Path
from tempfile import NamedTemporaryFile

from pyp5js.fs import LibFiles
from pyp5js.config import TRANSCRYPT_INTERPRETER, PYODIDE_INTERPRETER
from pyp5js.config.sketch import SketchConfig


@fixture
def transcrypt_json_file():
try:
fd = NamedTemporaryFile(mode='w', delete=False)
data = {"interpreter": "transcrypt"}
json.dump(data, fd)
filename = fd.name
fd.seek(0)
fd.close()
yield filename
finally:
os.remove(filename)

@fixture
def pyodide_json_file():
try:
fd = NamedTemporaryFile(mode='w', delete=False)
data = {"interpreter": "pyodide"}
json.dump(data, fd)
filename = fd.name
fd.seek(0)
fd.close()
yield filename
finally:
os.remove(filename)

@fixture
def transcrypt_config():
return SketchConfig(interpreter=TRANSCRYPT_INTERPRETER)

@fixture
def pyodide_config():
return SketchConfig(interpreter=PYODIDE_INTERPRETER)


def test_init_transcrypt_sketch_config_from_json(transcrypt_json_file):
config = SketchConfig.from_json(transcrypt_json_file)
assert config.interpreter == TRANSCRYPT_INTERPRETER


def test_init_pyodide_sketch_config_from_json(pyodide_json_file):
config = SketchConfig.from_json(pyodide_json_file)
assert config.interpreter == PYODIDE_INTERPRETER


def test_write_sketch_interpreter_config(transcrypt_config):
config = transcrypt_config
fd = NamedTemporaryFile(mode="w", delete=False)
config.write(fd.name)
fd.close()
with open(fd.name) as fd:
data = json.load(fd)

assert data["interpreter"] == TRANSCRYPT_INTERPRETER
os.remove(fd.name)

def test_get_transcrypt_index_template(transcrypt_config):
template = transcrypt_config.get_index_template()
pyp5js_files = LibFiles()
assert pyp5js_files.transcrypt_index_html == template
assert template.exists()

def test_get_pyodide_index_template(pyodide_config):
template = pyodide_config.get_index_template()
pyp5js_files = LibFiles()
assert pyp5js_files.pyodide_index_html == template
assert template.exists()
38 changes: 28 additions & 10 deletions pyp5js/tests/test_fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pathlib import Path
from unittest import TestCase

from pyp5js.config import SKETCHBOOK_DIR
from pyp5js.config import SKETCHBOOK_DIR, PYODIDE_INTERPRETER
from pyp5js.exceptions import SketchDirAlreadyExistException
from pyp5js.fs import LibFiles, SketchFiles
from pyp5js.exceptions import InvalidName
Expand Down Expand Up @@ -37,8 +37,11 @@ def test_files_properties(lib_files):
assert lib_files.target_sketch_template == pyp5_dir.joinpath('templates', 'target_sketch.py.template')
assert lib_files.target_sketch_template.exists()

assert lib_files.index_html == pyp5_dir.joinpath('templates', 'index.html')
assert lib_files.index_html.exists()
assert lib_files.transcrypt_index_html == pyp5_dir.joinpath('templates', 'transcrypt_index.html')
assert lib_files.transcrypt_index_html.exists()

assert lib_files.pyodide_index_html == pyp5_dir.joinpath('templates', 'pyodide_index.html')
assert lib_files.pyodide_index_html.exists()

assert lib_files.p5js == pyp5_dir.joinpath('static', 'p5', 'p5.min.js')
assert lib_files.p5js.exists()
Expand All @@ -58,18 +61,22 @@ def tearDown(self):
if self.base_dir.exists():
shutil.rmtree(self.base_dir)

def get_expected_path(self, *args):
return self.base_dir.joinpath(self.sketch_name, *args)

def test_sketch_dirs(self):
assert self.base_dir.joinpath(self.sketch_name) == self.files.sketch_dir
assert self.base_dir.joinpath(self.sketch_name, 'static') == self.files.static_dir
assert self.base_dir.joinpath(self.sketch_name, 'target') == self.files.target_dir
assert self.get_expected_path() == self.files.sketch_dir
assert self.get_expected_path('static') == self.files.static_dir
assert self.get_expected_path('target') == self.files.target_dir
assert self.files.TARGET_NAME == 'target'

def test_sketch_files(self):
self.files.check_sketch_dir = False
assert self.base_dir.joinpath(self.sketch_name, 'index.html') == self.files.index_html
assert self.base_dir.joinpath(self.sketch_name, 'static', 'p5.js') == self.files.p5js
assert self.base_dir.joinpath(self.sketch_name, 'foo.py') == self.files.sketch_py
assert self.base_dir.joinpath(self.sketch_name, 'target_sketch.py') == self.files.target_sketch
assert self.get_expected_path('index.html') == self.files.index_html
assert self.get_expected_path('static', 'p5.js') == self.files.p5js
assert self.get_expected_path('foo.py') == self.files.sketch_py
assert self.get_expected_path('target_sketch.py') == self.files.target_sketch
assert self.get_expected_path('properties.json') == self.files.config_file

def test_sketch_files_holds_reference_to_lib_files(self):
lib_files = LibFiles()
Expand All @@ -80,12 +87,14 @@ def test_create_dirs(self):
assert self.files.sketch_dir.exists() is False
assert self.files.static_dir.exists() is False
assert self.files.target_dir.exists() is False
assert self.files.config_file.exists() is False

self.files.create_sketch_dir()

assert self.files.sketch_dir.exists() is True
assert self.files.static_dir.exists() is True
assert self.files.target_dir.exists() is True
assert self.files.config_file.exists() is True

with pytest.raises(SketchDirAlreadyExistException):
self.files.create_sketch_dir()
Expand All @@ -112,3 +121,12 @@ def test_name_should_accept_underscore_in_the_beginning(self):
def test_name_should_accept_underscore_in_the_middle(self):
file = SketchFiles('na_me')
assert file.sketch_name == 'na_me'

def test_loads_config_from_config_file(self):
files = SketchFiles('bar', interpreter=PYODIDE_INTERPRETER)
files.create_sketch_dir() # writes config file json

same_files = SketchFiles('bar')

assert same_files.config_file == files.config_file
assert same_files.config.interpreter == PYODIDE_INTERPRETER
Loading