pymaker.py
A program to create a PyPlate project from a few variables
This module gets the project type, the project's destination dir, copies the required dirs/files for the project type from the template to the specified destination, and performs some initial fixes/replacements of text and path names in the resulting files.
Run pymaker -h for more options.
PyMaker
Bases: PyPlate
The main class, responsible for the operation of the program
Public methods
main: The main method of the program
This class implements all the needed functionality of PyMaker, to create a PyPlate project from a template.
Source code in src/pymaker.py
class PyMaker(PyPlate):
"""
The main class, responsible for the operation of the program
Public methods:
main: The main method of the program
This class implements all the needed functionality of PyMaker, to create a
PyPlate project from a template.
"""
# --------------------------------------------------------------------------
# Class constants
# --------------------------------------------------------------------------
# about string
S_ABOUT = (
"\n"
f"{'PyPlate/PyMaker'}\n"
f"{PyPlate.S_PP_SHORT_DESC}\n"
f"{PyPlate.S_PP_VERSION}\n"
f"https://github.com/cyclopticnerve/PyPlate"
)
# I18N cmd line instructions string
S_EPILOG = _(
"\n"
"Run this program from the directory where you want to create "
"a project."
)
# messages
# make msg
# NB: param is name of project folder
S_MSG_MAKE = _("Making {}")
# --------------------------------------------------------------------------
# Public methods
# --------------------------------------------------------------------------
# --------------------------------------------------------------------------
# The main method of the program
# --------------------------------------------------------------------------
def main(self):
"""
The main method of the program
This method is the main entry point for the program, initializing the
program, and performing its steps.
"""
# call boilerplate code
self._setup()
# ----------------------------------------------------------------------
# main stuff
# get project info
self._get_project_info()
# do before template
self._do_before_template()
# copy template
self._do_template()
# do before template
self._do_after_template()
# do any fixing up of dicts (like meta keywords, etc)
self._do_before_fix()
# do replacements in final project location
self._do_fix()
# do extra stuff to final dir after fix
self._do_after_fix()
print()
# ----------------------------------------------------------------------
# teardown
# call boilerplate code
self._teardown()
# --------------------------------------------------------------------------
# Private methods
# --------------------------------------------------------------------------
# NB: these are the main steps, called in order from main
# --------------------------------------------------------------------------
# Boilerplate to use at the start of main
# --------------------------------------------------------------------------
def _setup(self):
"""
Boilerplate to use at the start of main
Perform some mundane stuff like setting properties.
"""
self._parser.prog = "pymaker"
# do parent setup
super()._setup()
# do not run pymaker in pyplate dir
if self._dir_prj.is_relative_to(P.P_DIR_PRJ):
print(P.C.S_ERR_PRJ_DIR_IS_PP)
P.sys.exit(-1)
# --------------------------------------------------------------------------
# Get project info
# --------------------------------------------------------------------------
def _get_project_info(self):
"""
Get project info
Asks the user for project info, such as type and name, to be saved to
self._dict_prv_prj.
"""
# check version from conf
pattern = P.C.S_SEM_VER_VALID
version = P.C.D_PUB_META[P.C.S_KEY_META_VERSION]
ver_ok = P.re.search(pattern, version) is not None
# ask if user wants to keep invalid version or quit
if not ver_ok:
res = F.dialog(
P.C.S_ERR_SEM_VER,
[F.S_ASK_YES, F.S_ASK_NO],
default=F.S_ASK_NO,
# loop=True
)
if res != F.S_ASK_YES:
P.sys.exit(-1)
# ----------------------------------------------------------------------
# first question is type
# NB: this makes the string to display in terminal
# sanity check
prj_type = ""
# build the input question
types = []
for item in P.C.L_TYPES:
s = P.C.S_ASK_TYPE_FMT.format(item[0], item[1])
types.append(s)
str_types = P.C.S_ASK_TYPE_JOIN.join(types)
# format the question
in_type = P.C.S_ASK_TYPE.format(str_types)
# loop forever until we get a valid type
while True:
# ask for type of project (single letter)
prj_type = input(in_type)
# check for valid type
if self._check_type(prj_type):
prj_type = prj_type[0].lower()
# at this point, type is valid so exit loop
break
# ----------------------------------------------------------------------
# next question is name
# if in debug mode
if self._cmd_debug:
# get long name
name_prj = "DEBUG"
for item in P.C.L_TYPES:
if item[0] == prj_type:
# get debug name of project
name_prj = f"{item[1]} DEBUG"
break
# dir name, no spaces
name_prj_big = name_prj.replace(" ", "_")
# set up for existence check
tmp_dir = self._dir_prj / name_prj_big
# check if project already exists
if tmp_dir.exists():
# if it does exist, "nuke it from orbit! it's the only way to
# be sure!"
# NB: yes i know ive used this joke more than once... FUCK YOU
# ITS FUNNY
shutil.rmtree(tmp_dir)
# not debug
else:
# loop forever until we get a valid name that does not exist
while True:
# ask for name of project
name_prj = input(P.C.S_ASK_NAME)
name_prj = name_prj.strip(" ")
# check for valid name
if self._check_name(name_prj):
# dir name, no spaces
name_prj_big = name_prj.replace(" ", "_")
# set up for existence check
tmp_dir = self._dir_prj / name_prj_big
# check if project already exists
if tmp_dir.exists():
# tell the user that the old name exists
print(P.C.S_ERR_EXIST.format(name_prj_big))
else:
break
# save global property
self._dir_prj = tmp_dir
# save other names
name_prj_small = name_prj_big.lower()
name_prj_pascal = F.pascal_case(name_prj_small)
# ----------------------------------------------------------------------
# here we figure out the binary/package/window name for a project
# NB: for a cli, the binary name is the project name lowercased
# for a gui we should ask for the main window class name
# for a package we should ask for the module name
name_sec = ""
name_sec_big = ""
name_sec_small = ""
name_sec_pascal = ""
# do we need a second name?
if prj_type in P.C.D_NAME_SEC:
# dup prj names if debug
if self._cmd_debug:
name_sec = name_prj
name_sec_big = name_prj_big
name_sec_small = name_prj_small
name_sec_pascal = name_prj_pascal
# if not debug, if need second name, ask for it
else:
# format question for second name
s_sec_ask = P.C.D_NAME_SEC[prj_type]
s_sec_ask_fmt = s_sec_ask.format(name_prj_small)
# loop forever until we get a valid name or empty string
while True:
# ask for second name
name_sec = input(s_sec_ask_fmt)
name_sec = name_sec.strip(" ")
# empty, keep default
if name_sec == "":
name_sec = name_prj_small
# check for valid name
if self._check_name(name_sec):
name_sec_big = name_sec.replace(" ", "_")
break
# save other names
name_sec_small = name_sec_big.lower()
name_sec_pascal = F.pascal_case(name_sec_small)
# ----------------------------------------------------------------------
# make dicts from conf defaults
# create global settings dicts in private.json
self._dict_prv = {
P.C.S_KEY_PRV_ALL: dict(P.C.D_PRV_ALL),
P.C.S_KEY_PRV_PRJ: dict(P.C.D_PRV_PRJ),
}
# create individual dicts in project.json
self._dict_pub = {
P.C.S_KEY_PUB_META: dict(P.C.D_PUB_META),
P.C.S_KEY_PUB_BL: dict(P.C.D_PUB_BL),
P.C.S_KEY_PUB_DBG: dict(P.C.D_PUB_DBG),
P.C.S_KEY_PUB_DIST: dict(P.C.D_PUB_DIST),
P.C.S_KEY_PUB_DOCS: dict(P.C.D_PUB_DOCS),
P.C.S_KEY_PUB_I18N: dict(P.C.D_PUB_I18N),
P.C.S_KEY_PUB_INST: dict(P.C.D_PUB_INST),
# P.C.S_KEY_PUB_UNINST: dict(P.C.D_PUB_UNINST),
}
# ----------------------------------------------------------------------
# fill dicts
# get prv subs
self._dict_prv_all = self._dict_prv[P.C.S_KEY_PRV_ALL]
self._dict_prv_prj = self._dict_prv[P.C.S_KEY_PRV_PRJ]
# save project stuff
self._dict_prv_prj["__PP_TYPE_PRJ__"] = prj_type
self._dict_prv_prj["__PP_NAME_PRJ__"] = name_prj
self._dict_prv_prj["__PP_NAME_PRJ_BIG__"] = name_prj_big
self._dict_prv_prj["__PP_NAME_PRJ_SMALL__"] = name_prj_small
self._dict_prv_prj["__PP_NAME_PRJ_PASCAL__"] = name_prj_pascal
self._dict_prv_prj["__PP_NAME_SEC_BIG__"] = name_sec_big
self._dict_prv_prj["__PP_NAME_SEC_SMALL__"] = name_sec_small
self._dict_prv_prj["__PP_NAME_SEC_PASCAL__"] = name_sec_pascal
self._dict_prv_prj["__PP_NAME_VENV__"] = P.C.S_VENV_FMT_NAME.format(
name_prj_small
)
self._dict_prv_prj["__PP_FILE_APP__"] = P.C.S_APP_FILE_FMT.format(
name_prj_small
)
self._dict_prv_prj["__PP_CLASS_APP__"] = name_prj_pascal
self._dict_prv_prj["__PP_FILE_WIN__"] = P.C.S_WIN_FILE_FMT.format(
name_sec_small
)
self._dict_prv_prj["__PP_CLASS_WIN__"] = name_sec_pascal
# ----------------------------------------------------------------------
# get reps to fix public
self._fix_dicts()
# ----------------------------------------------------------------------
# handle -d
# NB: do after _fix dicts
if self._cmd_debug:
self._dict_dbg = dict(P.C.D_DBG_PM)
# ----------------------------------------------------------------------
# print some info
print()
print(self.S_MSG_MAKE.format(name_prj_big))
# blank line before printing progress
print()
# --------------------------------------------------------------------------
# Do any work before template copy
# --------------------------------------------------------------------------
def _do_before_template(self):
"""
Do any work before template copy
Do any work before copying the template. This method is called just
before _do_template, before any files have been copied.\n
It is mostly used to make final adjustments to the 'dict_prv' and
'dict_pub' dicts before any copying occurs.
"""
P.C.do_before_template(
self._dir_prj, self._dict_prv, self._dict_pub, self._dict_dbg
)
# --------------------------------------------------------------------------
# Copy template files to final location
# --------------------------------------------------------------------------
def _do_template(self):
"""
Copy template files to final location
Gets dirs/files from template and copies them to the project dir.
"""
# show info
print(P.C.S_ACTION_COPY, end="", flush=True)
# ----------------------------------------------------------------------
# do template/all
# copy template/all
src = P.P_DIR_PRJ / P.C.S_DIR_TEMPLATE / P.C.S_DIR_ALL
dst = self._dir_prj
shutil.copytree(src, dst, dirs_exist_ok=True)
# ----------------------------------------------------------------------
# copy template/type
# get some paths
prj_type_short = self._dict_prv_prj["__PP_TYPE_PRJ__"]
prj_type_long = ""
# get long type of project
for item in P.C.L_TYPES:
if item[0] == prj_type_short:
prj_type_long = item[2]
break
# get the src dir in the template dir
src = P.P_DIR_PRJ / P.C.S_DIR_TEMPLATE / prj_type_long
dst = self._dir_prj
shutil.copytree(src, dst, dirs_exist_ok=True)
# ----------------------------------------------------------------------
# do stuff outside template all/type
# copy linked files
for key, val in P.C.D_COPY.items():
# get src/dst
src = P.P_DIR_PRJ / key
dst = self._dir_prj / val
# copy dir/file
if src.is_dir():
shutil.copytree(src, dst)
elif src.is_file():
shutil.copy2(src, dst)
# ----------------------------------------------------------------------
# merge reqs
# merge reqs files from all and prj
self._merge_reqs(prj_type_long)
# ----------------------------------------------------------------------
# done
F.printc(P.C.S_ACTION_DONE, fg=F.C_FG_GREEN, bold=True)
# --------------------------------------------------------------------------
# Do any work after template copy
# --------------------------------------------------------------------------
def _do_after_template(self):
"""
Do any work after template copy
Do any work after copying the template. This method is called after
_do_template, and before _do_before_fix.
"""
P.C.do_after_template(
self._dir_prj, self._dict_prv, self._dict_pub, self._dict_dbg
)
main()
The main method of the program
This method is the main entry point for the program, initializing the program, and performing its steps.
Source code in src/pymaker.py
def main(self):
"""
The main method of the program
This method is the main entry point for the program, initializing the
program, and performing its steps.
"""
# call boilerplate code
self._setup()
# ----------------------------------------------------------------------
# main stuff
# get project info
self._get_project_info()
# do before template
self._do_before_template()
# copy template
self._do_template()
# do before template
self._do_after_template()
# do any fixing up of dicts (like meta keywords, etc)
self._do_before_fix()
# do replacements in final project location
self._do_fix()
# do extra stuff to final dir after fix
self._do_after_fix()
print()
# ----------------------------------------------------------------------
# teardown
# call boilerplate code
self._teardown()