Ver a proveniência

refactor: split make_boilerplate function

version-14
Ankush Menat há 3 anos
committed by Ankush Menat
ascendente
cometimento
74d7753a5a
2 ficheiros alterados com 130 adições e 58 eliminações
  1. +100
    -41
      frappe/tests/test_boilerplate.py
  2. +30
    -17
      frappe/utils/boilerplate.py

+ 100
- 41
frappe/tests/test_boilerplate.py Ver ficheiro

@@ -1,4 +1,5 @@
import ast
import copy
import glob
import os
import shutil
@@ -6,33 +7,39 @@ import unittest
from unittest.mock import patch

import frappe
from frappe.utils.boilerplate import make_boilerplate
from frappe.utils.boilerplate import _create_app_boilerplate, _get_inputs


class TestBoilerPlate(unittest.TestCase):
@classmethod
def setUpClass(cls):
title = "Test App"
description = "This app's description contains 'single quotes' and \"double quotes\"."
publisher = "Test Publisher"
email = "example@example.org"
icon = "" # empty -> default
color = ""
app_license = "MIT"

cls.user_input = [
title,
description,
publisher,
email,
icon,
color,
app_license,
]
cls.default_hooks = frappe._dict(
{
"app_name": "test_app",
"app_title": "Test App",
"app_description": "This app's description contains 'single quotes' and \"double quotes\".",
"app_publisher": "Test Publisher",
"app_email": "example@example.org",
"app_icon": "octicon octicon-file-directory",
"app_color": "grey",
"app_license": "MIT",
}
)

cls.default_user_input = frappe._dict(
{
"title": "Test App",
"description": "This app's description contains 'single quotes' and \"double quotes\".",
"publisher": "Test Publisher",
"email": "example@example.org",
"icon": "", # empty -> default
"color": "",
"app_license": "MIT",
}
)

cls.bench_path = frappe.utils.get_bench_path()
cls.apps_dir = os.path.join(cls.bench_path, "apps")
cls.app_names = ("test_app", "test_app_no_git")
cls.gitignore_file = ".gitignore"
cls.git_folder = ".git"

@@ -55,39 +62,91 @@ class TestBoilerPlate(unittest.TestCase):
"public",
]

def create_app(self, hooks, no_git=False):
self.addCleanup(self.delete_test_app, hooks.app_name)
_create_app_boilerplate(self.apps_dir, hooks, no_git)

@classmethod
def tearDownClass(cls):
test_app_dirs = (os.path.join(cls.bench_path, "apps", app_name) for app_name in cls.app_names)
for test_app_dir in test_app_dirs:
if os.path.exists(test_app_dir):
shutil.rmtree(test_app_dir)
def delete_test_app(cls, app_name):
test_app_dir = os.path.join(cls.bench_path, "apps", app_name)
if os.path.exists(test_app_dir):
shutil.rmtree(test_app_dir)

@staticmethod
def get_user_input_stream(inputs):
user_inputs = []
for value in inputs.values():
if isinstance(value, list):
user_inputs.extend(value)
else:
user_inputs.append(value)
return user_inputs

def test_simple_input_to_boilerplate(self):
with patch("builtins.input", side_effect=self.get_user_input_stream(self.default_user_input)):
hooks = _get_inputs(self.default_hooks.app_name)
self.assertDictEqual(hooks, self.default_hooks)

def test_invalid_inputs(self):
invalid_inputs = copy.deepcopy(self.default_user_input).update(
{
"title": ["1nvalid Title", "valid title"],
}
)
with patch("builtins.input", side_effect=self.get_user_input_stream(invalid_inputs)):
hooks = _get_inputs(self.default_hooks.app_name)
self.assertEqual(hooks.app_title, "valid title")

def test_create_app(self):
with patch("builtins.input", side_effect=self.user_input):
make_boilerplate(self.apps_dir, self.app_names[0])

new_app_dir = os.path.join(self.bench_path, self.apps_dir, self.app_names[0])

paths = self.get_paths(new_app_dir, self.app_names[0])
app_name = "test_app"

hooks = frappe._dict(
{
"app_name": app_name,
"app_title": "Test App",
"app_description": "This app's description contains 'single quotes' and \"double quotes\".",
"app_publisher": "Test Publisher",
"app_email": "example@example.org",
"app_icon": "octicon octicon-file-directory",
"app_color": "grey",
"app_license": "MIT",
}
)

self.create_app(hooks)
new_app_dir = os.path.join(self.bench_path, self.apps_dir, app_name)

paths = self.get_paths(new_app_dir, app_name)
for path in paths:
self.assertTrue(os.path.exists(path), msg=f"{path} should exist in {self.app_names[0]} app")
self.assertTrue(os.path.exists(path), msg=f"{path} should exist in {app_name} app")

self.check_parsable_python_files(new_app_dir)

def test_create_app_without_git_init(self):
with patch("builtins.input", side_effect=self.user_input):
make_boilerplate(self.apps_dir, self.app_names[1], no_git=True)

new_app_dir = os.path.join(self.apps_dir, self.app_names[1])

paths = self.get_paths(new_app_dir, self.app_names[1])
app_name = "test_app_no_git"

hooks = frappe._dict(
{
"app_name": app_name,
"app_title": "Test App",
"app_description": "This app's description contains 'single quotes' and \"double quotes\".",
"app_publisher": "Test Publisher",
"app_email": "example@example.org",
"app_icon": "octicon octicon-file-directory",
"app_color": "grey",
"app_license": "MIT",
}
)
self.create_app(hooks, no_git=True)

new_app_dir = os.path.join(self.apps_dir, app_name)

paths = self.get_paths(new_app_dir, app_name)
for path in paths:
if os.path.basename(path) in (self.git_folder, self.gitignore_file):
self.assertFalse(
os.path.exists(path), msg=f"{path} shouldn't exist in {self.app_names[1]} app"
)
self.assertFalse(os.path.exists(path), msg=f"{path} shouldn't exist in {app_name} app")
else:
self.assertTrue(os.path.exists(path), msg=f"{path} should exist in {self.app_names[1]} app")
self.assertTrue(os.path.exists(path), msg=f"{path} should exist in {app_name} app")

self.check_parsable_python_files(new_app_dir)



+ 30
- 17
frappe/utils/boilerplate.py Ver ficheiro

@@ -17,23 +17,32 @@ def make_boilerplate(dest, app_name, no_git=False):

# app_name should be in snake_case
app_name = frappe.scrub(app_name)
hooks = _get_inputs(app_name)
_create_app_boilerplate(dest, hooks, no_git=no_git)


def _get_inputs(app_name):
"""Prompt user for various inputs related to new app and return config."""
app_name = frappe.scrub(app_name)

hooks = frappe._dict()
hooks.app_name = app_name
app_title = hooks.app_name.replace("_", " ").title()
for key in (
"App Title (default: {0})".format(app_title),
"App Description",
"App Publisher",
"App Email",
"App Icon (default 'octicon octicon-file-directory')",
"App Color (default 'grey')",
"App License (default 'MIT')",
):
hook_key = key.split(" (")[0].lower().replace(" ", "_")

NEW_APP_CONFIG = {
"app_title": {"prompt": "App Title (default: {0})".format(app_title)},
"app_description": {"prompt": "App Description"},
"app_publisher": {"prompt": "App Publisher"},
"app_email": {"prompt": "App Email"},
"app_icon": {"prompt": "App Icon (default 'octicon octicon-file-directory')"},
"app_color": {"prompt": "App Color (default 'grey')"},
"app_license": {"prompt": "App License (default 'MIT')"},
}

for property, config in NEW_APP_CONFIG.items():
hook_val = None
while not hook_val:
hook_val = cstr(input(key + ": "))
hook_val = cstr(input(config["prompt"] + ": "))

if not hook_val:
defaults = {
@@ -42,13 +51,13 @@ def make_boilerplate(dest, app_name, no_git=False):
"app_color": "grey",
"app_license": "MIT",
}
if hook_key in defaults:
hook_val = defaults[hook_key]
if property in defaults:
hook_val = defaults[property]

if hook_key == "app_name" and hook_val.lower().replace(" ", "_") != hook_val:
if property == "app_name" and hook_val.lower().replace(" ", "_") != hook_val:
print("App Name must be all lowercase and without spaces")
hook_val = ""
elif hook_key == "app_title" and not re.match(
elif property == "app_title" and not re.match(
r"^(?![\W])[^\d_\s][\w -]+$", hook_val, re.UNICODE
):
print(
@@ -56,8 +65,12 @@ def make_boilerplate(dest, app_name, no_git=False):
)
hook_val = ""

hooks[hook_key] = hook_val
hooks[property] = hook_val

return hooks


def _create_app_boilerplate(dest, hooks, no_git=False):
frappe.create_folder(
os.path.join(dest, hooks.app_name, hooks.app_name, frappe.scrub(hooks.app_title)), with_init=True
)
@@ -132,7 +145,7 @@ def make_boilerplate(dest, app_name, no_git=False):
app_repo.git.add(A=True)
app_repo.index.commit("feat: Initialize App")

print("'{app}' created at {path}".format(app=app_name, path=app_directory))
print(f"'{hooks.app_name}' created at {app_directory}")


manifest_template = """include MANIFEST.in


Carregando…
Cancelar
Guardar