Skip to content

Commit

Permalink
component generation from blueprint
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminhon committed Jan 30, 2021
1 parent 7ff6577 commit 6eba502
Show file tree
Hide file tree
Showing 12 changed files with 364 additions and 3 deletions.
5 changes: 4 additions & 1 deletion antigen/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
__version__ = 0.0.1
__version__ = "0.0.1"

from .blueprint import *
from .utils import *
72 changes: 72 additions & 0 deletions antigen/blueprint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import glob
import os

from jinja2 import Environment, FileSystemLoader, PackageLoader, Template

from antigen import components_dir

from .utils import files_in_folder, stdout_logger

log = stdout_logger(__name__)

jinja_env = Environment(loader=FileSystemLoader(components_dir))

###############################################################################
## Blueprint
###############################################################################


def component_is_valid(component):

if "component" not in component:
log.warning("component field missing in definition")
return False

fields = ["component", "id", "props", "children"]
for f in fields:
if f not in component:
log.warning(f"{f} missing in component: {component['component']}")
return False
return True


def create_component(component: dict, path: str, templates: str) -> str:
"""
Args:
component: component definition ('component', 'id', 'props', 'children' keys are used in generation)
path: path to create component js file
templates: path to template folder
Returns:
str: path to component js file
"""

if os.path.isfile(path):
raise Exception(f"{path} already exists")

if not component_is_valid(component):
raise Exception(f"invalid component")

templates = jinja_env.list_templates()
log.debug(templates)

if f"{component['component']}.js" in templates:
template = f"{component['component']}.js"
elif "Default.js" in templates:
log.debug(f"no template found for f{component['component']} using Default.js")
template = "Default.js"
else:
raise Exception(f"no component templates found for {component['component']}")

with open(path, "w") as f:
log.debug(f"Generating template {template} -> {path} ...")
f.write(
jinja_env.get_template(template).render(
id=component["id"],
component=component["component"],
children=component["children"],
props=component["props"],
)
)

return path
6 changes: 6 additions & 0 deletions antigen/resources/components/Default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function {{component}}(props) {

return (
<div id="{{id}}">{{children}}</div>
)
}
7 changes: 7 additions & 0 deletions antigen/resources/examples/blueprints/simple_blueprint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
components:

- component: TextBox
props:
id: standard-basic
label: Standard
children: Hello World!
Empty file.
78 changes: 78 additions & 0 deletions antigen/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import logging
import os
import shutil
import sys

import yaml

###############################################################################
## Important Paths
###############################################################################

module_dir = os.path.dirname(sys.modules["antigen"].__file__)
resources_dir = os.path.join(module_dir, "resources")
components_dir = os.path.join(resources_dir, "components")
examples_dir = os.path.join(resources_dir, "examples")

###############################################################################
## General
###############################################################################


def read_yaml(file):
with open(file, "r") as f:
return yaml.load(f, Loader=yaml.FullLoader)


def reverse_dictionary(d):
return {v: k for k, v in d.items()}


def delete_directory(directory):
if os.path.isdir(directory):
shutil.rmtree(directory)


def make_directory(directory, delete=False, exist_ok=True):
if delete:
delete_directory(directory)
if not os.path.isdir(directory):
os.makedirs(directory, exist_ok=exist_ok)
return os.path.abspath(directory)


def files_in_folder(folder):
return [
os.path.join(folder, n)
for n in os.listdir(folder)
if os.path.isfile(os.path.join(folder, n))
]


def folders_in_folder(folder):
return [
os.path.join(folder, n)
for n in os.listdir(folder)
if os.path.isdir(os.path.join(folder, n))
]


def stdout_logger(name):
"""
Use this logger to standardize log output
"""
log = logging.getLogger(name)
log.propagate = False
stream_handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter("%(levelname)-8s %(message)s")
stream_handler.setFormatter(formatter)
stream_handler.setLevel(logging.DEBUG)
log.handlers = [stream_handler]
log.setLevel(logging.DEBUG)
return log


class class_or_instance_method(classmethod):
def __get__(self, instance, type_):
descr_get = super().__get__ if instance is None else self.__func__.__get__
return descr_get(instance, type_)
50 changes: 50 additions & 0 deletions bin/antigen
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python3

import argparse
import os
import re
import shutil
import subprocess
import sys
import tempfile
from glob import glob
from importlib import import_module

from jinja2 import Environment, FileSystemLoader, PackageLoader, Template

from antigen import folders_in_folder, read_yaml, stdout_logger

log = stdout_logger(__name__)
module_folder = os.path.dirname(sys.modules["antigen"].__file__)
resources_folder = os.path.join(module_folder, "resources")
current_folder = os.path.abspath(os.curdir)

templates = {
os.path.basename(t): t
for t in folders_in_folder(os.path.join(module_folder, "resources", "templates"))
}

# parsers
parser = argparse.ArgumentParser(description="declarative ui generation")
subparsers = parser.add_subparsers(dest="subcommand")

# parsers - blueprint
generate_parser = subparsers.add_parser(
"blueprint", description="generate components from blueprint"
)
generate_parser.add_argument("path", help="path to blueprint YAML")

args = parser.parse_args()

try:

if args.subcommand == "blueprint":
log.debug(read_yaml(args.path))

else:
parser.print_help()

except Exception as e:
log.error(e)
parser.print_help()

11 changes: 9 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
from distutils.core import setup
from antigen import __version__

from setuptools import find_packages

from antigen import __version__

setup(
name="antigen",
version=__version__,
author="shirecoding",
author_email="[email protected]",
scripts=["bin/antigen"],
install_requires=["Jinja2"],
install_requires=[
"Jinja2",
"pytest",
"pytest-html",
"pytest-cov",
],
url="https://github.com/shirecoding/Antigen",
download_url=f"https://github.com/shirecoding/Antigen/archive/{__version__}.tar.gz",
long_description=open("README.md").read(),
Expand Down
2 changes: 2 additions & 0 deletions tests/.coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[run]
data_file = reports/.coverage
52 changes: 52 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from datetime import datetime

import pytest
from py.xml import html

###########################################################################################
## REPORT
###########################################################################################


def stringToCellData(string, class_):
return html.td([html.p(s) for s in string.split("\n")], class_=class_)


@pytest.mark.optionalhook
def pytest_html_results_table_header(cells):
cells.insert(1, html.th("Specification", class_="spec", col="spec"))
cells.insert(2, html.th("Description"))
cells.insert(3, html.th("Procedure", class_="proc", col="proc"))
cells.insert(4, html.th("Expected", class_="expec", col="expec"))
cells.pop() # remove links column


@pytest.mark.optionalhook
def pytest_html_results_table_row(report, cells):
cells.insert(1, stringToCellData(report.specification, class_="col-spec"))
cells.insert(2, stringToCellData(report.description, class_="col-desc"))
cells.insert(3, stringToCellData(report.procedure, class_="col-proc"))
cells.insert(4, stringToCellData(report.expected, class_="col-expec"))
cells.pop() # remove links column


@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
report.description = str(item.function.__doc__ if item.function.__doc__ else "")
marker = item.get_closest_marker("report")

report.specification = (
marker.kwargs["specification"]
if marker and "specification" in marker.kwargs
else ""
)

report.procedure = (
marker.kwargs["procedure"] if marker and "procedure" in marker.kwargs else ""
)

report.expected = (
marker.kwargs["expected"] if marker and "expected" in marker.kwargs else ""
)
14 changes: 14 additions & 0 deletions tests/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[pytest]
addopts =
--assert='plain'
--html=reports/pytest_report.html
--junitxml=reports/pytest_report.xml
--self-contained-html
--cov=antigen
--cov-report=xml:reports/coverage.xml
--cov-report=html:reports/coverage
--cov-config=tests/.coveragerc
log_cli = true
log_level = DEBUG
markers =
report
70 changes: 70 additions & 0 deletions tests/test_blueprint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import logging
import os
import re
import tempfile

import pytest

log = logging.getLogger(__name__)


@pytest.mark.report(
specification="""
""",
procedure="""
""",
expected="""
""",
)
def test_valid_component():
"""
Test component validation function
"""
from antigen import component_is_valid, components_dir, create_component

c1 = {
"component": "TextField",
"id": "TextField1",
"props": {},
"children": "Hello World",
}

assert component_is_valid(c1) == True

c2 = {"id": "TextField1", "props": {}, "children": "Hello World"}

assert component_is_valid(c2) == False


@pytest.mark.report(
specification="""
""",
procedure="""
""",
expected="""
""",
)
def test_create_component():
"""
Test component creation function
"""

from antigen import components_dir, create_component

c1 = {
"component": "TextField",
"id": "TextField1",
"props": {},
"children": "Hello World",
}

with tempfile.TemporaryDirectory() as folder:
component_path = create_component(
c1, os.path.join(folder, "c1"), components_dir
)

with open(component_path, "r") as file:
data = file.read()
assert re.match(r"^.*id=\"TextField1\".*$", data, re.DOTALL) != None
assert re.match(r"^.*>Hello World<.*$", data, re.DOTALL) != None
assert re.match(r"^.*TextField\(props\).*$", data, re.DOTALL) != None

0 comments on commit 6eba502

Please sign in to comment.