pyplate.py
A class to be the base for pymaker/pybaker
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.
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/pyplate.py
class 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
# --------------------------------------------------------------------------
# find path to pyplate project
P_DIR_PP = Path(__file__).parents[1].resolve()
# pyplate: replace=True
# short description
S_PP_SHORT_DESC = "A program for creating and building CLI/GUI/Packages in Python from a template"
# version string
S_PP_VERSION = "0.0.3"
# pyplate: replace=False
# config option strings
S_ARG_HLP_OPTION = "-h"
S_ARG_HLP_ACTION = "store_true"
S_ARG_HLP_DEST = "HLP_DEST"
# I18N: help option help
S_ARG_HLP_HELP = _("show this help message and exit")
# debug option strings
S_ARG_DBG_OPTION = "-d"
S_ARG_DBG_ACTION = "store_true"
S_ARG_DBG_DEST = "DBG_DEST"
# I18N help string for debug cmd line option
S_ARG_DBG_HELP = _("enable debugging mode")
# about string (to be set by subclass)
S_ABOUT = ""
# I18N if using argparse, add help at end of about
S_ABOUT_HELP = _("Use -h for help") + "\n"
# cmd line instructions string (to be set by subclass)
S_EPILOG = ""
# --------------------------------------------------------------------------
# Instance methods
# --------------------------------------------------------------------------
# --------------------------------------------------------------------------
# Initialize the new object
# --------------------------------------------------------------------------
def __init__(self):
"""
Initialize the new object
Initializes a new instance of the class, setting the default values
of its properties, and any other code that needs to run to create a
new object.
"""
# set the initial values of properties
# command line options
self._debug = False
# internal props
self._dir_prj = Path()
self._dict_rep = {}
self._dict_type_rules = {}
self._dict_sw_block = {}
self._dict_sw_line = {}
# private.json dicts
self._dict_prv = {}
self._dict_prv_all = {}
self._dict_prv_prj = {}
# project.json dicts
self._dict_pub = {}
self._dict_pub_bl = {}
self._dict_pub_dbg = {}
self._dict_pub_dist = {}
self._dict_pub_docs = {}
self._dict_pub_i18n = {}
self._dict_pub_meta = {}
# dictionary to hold current debug settings
self._dict_debug = {}
# cmd line stuff
# NB: placeholder to avoid comparing to None (to be set by subclass)
self._parser = argparse.ArgumentParser()
self._dict_args = {}
# --------------------------------------------------------------------------
# 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()
# ask user for project info
self._get_project_info()
# --------------------------------------------------------------------------
# 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.
"""
# print default about text
print(self.S_ABOUT)
# ----------------------------------------------------------------------
# use cmd line
# create a parser object in case we need it
self._parser = argparse.ArgumentParser(
add_help=False,
epilog=self.S_EPILOG,
formatter_class=CNFormatter,
)
# add help text to about block
print(self.S_ABOUT_HELP)
# add help option
self._parser.add_argument(
self.S_ARG_HLP_OPTION,
dest=self.S_ARG_HLP_DEST,
help=self.S_ARG_HLP_HELP,
action=self.S_ARG_HLP_ACTION,
)
# add debug option
self._parser.add_argument(
self.S_ARG_DBG_OPTION,
dest=self.S_ARG_DBG_DEST,
help=self.S_ARG_DBG_HELP,
action=self.S_ARG_DBG_ACTION,
)
# --------------------------------------------------------------------------
# Parse the arguments from the command line
# --------------------------------------------------------------------------
def _do_cmd_line(self):
"""
Parse the arguments from the command line
Parse the arguments from the command line, after the parser has been
set up.
"""
# get namespace object
args = self._parser.parse_args()
# convert namespace to dict
self._dict_args = vars(args)
# if -h passed, this will print and exit
if self._dict_args.get(self.S_ARG_HLP_DEST, False):
self._parser.print_help()
sys.exit()
# no -h, print epilog
print(self.S_EPILOG)
print()
# ----------------------------------------------------------------------
# get the args
self._debug = self._dict_args.get(self.S_ARG_DBG_DEST, False)
# set global prop in conf
C.B_DEBUG = self._debug
# debug turns off some post processing to speed up processing
# NB: changing values in self._dict_pub_dbg (through the functions in
# pyplate.py) will not affect the current session when running pymaker
# in debug mode. to do that, change the values of D_DBG_PM in
# pyplate.py
self._dict_debug = self._dict_pub_dbg
# ----------------------------------------------------------------------
# maybe yell
if self._debug:
# yup, yell
print(C.S_MSG_DEBUG)
# ----------------------------------------------------------------------
# set self._dir_prj
# assume we are running in the project dir
# this is used in a lot of places, so just shorthand it
self._dir_prj = Path.cwd()
# ----------------------------------------------------------------------
# set switch dicts to defaults
self._dict_sw_block = dict(C.D_SWITCH_DEF)
self._dict_sw_line = dict(self._dict_sw_block)
# --------------------------------------------------------------------------
# Get project info
# --------------------------------------------------------------------------
def _get_project_info(self):
"""
Get project info
The implementation of this function in the superclass is just a dummy
placeholder. The real work should be done in the subclass.
"""
# --------------------------------------------------------------------------
# Do any work before fix
# --------------------------------------------------------------------------
def _do_before_fix(self):
"""
Do any work before fix
Do any work before fix. This method is called just before _do_fix,
after all dunders have been configured, but before any files have been
modified.\n
It is mostly used to make final adjustments to the 'dict_prv' and
'dict_pub' dicts before any replacement occurs.
"""
C.do_before_fix(
self._dir_prj, self._dict_prv, self._dict_pub, self._dict_debug
)
# --------------------------------------------------------------------------
# Scan dirs/files in the project for replacing text
# --------------------------------------------------------------------------
def _do_fix(self):
"""
Scan dirs/files in the project for replacing text
Scans for dirs/files under the project's location. For each dir/file it
encounters, it passes the path to a filter to determine if the file
needs fixing based on its appearance in the blacklist.
"""
# print info
print(C.S_ACTION_FIX, end="", flush=True)
# check version before we start
version = self._dict_pub_meta[C.S_KEY_META_VERSION]
pattern = C.S_SEM_VER_VALID
ver_ok = re.search(pattern, version) is not None
# ask if user wants to keep invalid version or quit
if not ver_ok:
res = F.dialog(
C.S_ERR_SEM_VER,
[C.S_ERR_SEM_VER_Y, C.S_ERR_SEM_VER_N],
C.S_ERR_SEM_VER_N,
)
if res == C.S_ERR_SEM_VER_N:
sys.exit()
# combine dicts for string replacement
self._dict_rep = F.combine_dicts(
[self._dict_prv_all, self._dict_prv_prj]
)
# save private.json and project.json after all fixes have been done
self._save_project_info()
# make sure pyplate in in skip_all
skip_all = self._dict_pub_bl[C.S_KEY_SKIP_ALL]
if not C.S_PRJ_PP_DIR in skip_all:
skip_all.append(C.S_PRJ_PP_DIR)
# fix up blacklist and convert relative or glob paths to absolute Path
# objects
self._fix_blacklist_paths(self._dict_pub_bl)
# just shorten the names
skip_all = self._dict_pub_bl[C.S_KEY_SKIP_ALL]
skip_contents = self._dict_pub_bl[C.S_KEY_SKIP_CONTENTS]
skip_header = self._dict_pub_bl[C.S_KEY_SKIP_HEADER]
skip_code = self._dict_pub_bl[C.S_KEY_SKIP_CODE]
skip_path = self._dict_pub_bl[C.S_KEY_SKIP_PATH]
# ----------------------------------------------------------------------
# do the fixes
# NB: root is a full path, dirs and files are relative to root
for root, root_dirs, root_files in self._dir_prj.walk():
# handle dirs in skip_all
if root in skip_all:
# NB: don't recurse into subfolders
root_dirs.clear()
continue
# convert files into Paths
files = [root / f for f in root_files]
# for each file item
for item in files:
# for each new file, reset block and line switches to def
# NB: line switches always default to current block switches
self._dict_sw_block = dict(C.D_SWITCH_DEF)
self._dict_sw_line = dict(self._dict_sw_block)
# handle files in skip_all
if item in skip_all:
continue
# handle dirs/files in skip_contents
if not root in skip_contents and not item in skip_contents:
# handle dirs/files in skip_header
bl_hdr = root in skip_header or item in skip_header
# handle dirs/files in skip_code
bl_code = root in skip_code or item in skip_code
# fix content with appropriate dict
self._fix_contents(item, bl_hdr, bl_code)
# handle files in skip_path
if not item in skip_path:
self._fix_path(item)
# handle dirs in skip_path
if not root in skip_path:
self._fix_path(root)
# done
print(C.S_ACTION_DONE)
# --------------------------------------------------------------------------
# Do any work after fix
# --------------------------------------------------------------------------
def _do_after_fix(self):
"""
Do any work after fix
Do any work after fix. This method is called just after _do_after_fix,
after all files have been modified.\n
It is mostly used to tweak files once all the normal fixes have been
applied.
"""
C.do_after_fix(
self._dir_prj, self._dict_prv, self._dict_pub, self._dict_debug
)
# --------------------------------------------------------------------------
# These are minor steps called from the main steps
# --------------------------------------------------------------------------
# --------------------------------------------------------------------------
# Convert items in blacklist to absolute Path objects
# --------------------------------------------------------------------------
def _fix_blacklist_paths(self, dict_bl):
"""
Convert items in blacklist to absolute Path objects
Get absolute paths for all entries in the blacklist.
"""
# make a copy and remove path separators in one shot
# NB: this is mostly for glob support, as globs cannot end in path
# separators
for key in dict_bl:
dict_bl[key] = [item.rstrip("/") for item in dict_bl[key]]
# support for absolute/relative/glob
# NB: taken from cntree.py
# for each section of blacklist
for key, val in dict_bl.items():
# convert all items in list to Path objects
paths = [Path(item) for item in val]
# move absolute paths to one list
abs_paths = [item for item in paths if item.is_absolute()]
# move relative/glob paths to another list
other_paths = [item for item in paths if not item.is_absolute()]
# convert relative/glob paths back to strings
other_strings = [str(item) for item in other_paths]
# get glob results as generators
glob_results = [self._dir_prj.glob(item) for item in other_strings]
# start with absolutes
result = abs_paths
# for each generator
for item in glob_results:
# add results as whole shebang
result += list(item)
# set the list as the result list
dict_bl[key] = result
# --------------------------------------------------------------------------
# Fix header or code for each line in a file
# --------------------------------------------------------------------------
def _fix_contents(self, path, bl_hdr=False, bl_code=False):
"""
Fix header or code for each line in a file
Args:
path: Path for replacing text
bl_hdr: Whether the file is blacklisted for header lines (default:
False)
bl_code: Whether the file is blacklisted for code lines (default:
False)
For the given file, loop through each line, checking to see if it is a
header line or a code line. Ignore blank lines and comment-only lines.
"""
# check for unknown file types
self._dict_type_rules = get_type_rules(path)
if not self._dict_type_rules or len(self._dict_type_rules) == 0:
# do the basic replace (file got here after skip_all/skip_contents
# BUT NOT skip_bl/skip/code)
self._fix_text(path)
return
# default lines
lines = []
# open and read file
with open(path, "r", encoding=C.S_ENCODING) as a_file:
lines = a_file.readlines()
# for each line in array
for index, line in enumerate(lines):
# ------------------------------------------------------------------
# skip blank lines
if line.strip() == "":
continue
# ------------------------------------------------------------------
# split the line into code and comm
# we will split the line into two parts
# NB: assume code is whole line (i.e. no trailing comment)
split_pos = 0
code = line
comm = ""
# find split sequence
split_sch = self._dict_type_rules.get(C.S_KEY_SPLIT, None)
split_grp = self._dict_type_rules.get(C.S_KEY_SPLIT_COMM, None)
# only process files with split
if split_sch and split_grp:
# there may be multiple matches per line (ignore quoted markers)
matches = re.finditer(split_sch, line)
# only use matches that have the right group
matches = [
match for match in matches if match.group(split_grp)
]
for match in matches:
# split the line into code and comment (include delimiter)
split_pos = match.start(split_grp)
code = line[:split_pos]
comm = line[split_pos:]
# ------------------------------------------------------------------
# check for switches
# reset line switch values to block switch values
self._dict_sw_line = dict(self._dict_sw_block)
# check switches
check_switches(
code,
comm,
self._dict_type_rules,
self._dict_sw_block,
self._dict_sw_line,
)
# check for block or line replace switch
repl = False
if (
self._dict_sw_block[C.S_SW_REPLACE] is True
and self._dict_sw_line[C.S_SW_REPLACE] is True
) or self._dict_sw_line[C.S_SW_REPLACE] is True:
repl = True
# switch says no, gtfo
if not repl:
continue
# ------------------------------------------------------------------
# check for header
# check if blacklisted for headers
if not bl_hdr:
# check if it matches header pattern
str_pattern = self._dict_type_rules[C.S_KEY_HDR_SCH]
res = re.search(str_pattern, line)
if res:
# fix it
lines[index] = self._fix_header(line)
# no more processing for header line
continue
# ------------------------------------------------------------------
# not a blank, header or switch, must be code
# check if blacklisted for code
if not bl_code:
# fix dunders in real code lines
code = self._fix_code(code)
# --------------------------------------------------------------
# put the line back together
lines[index] = code + comm
# open and write file
with open(path, "w", encoding=C.S_ENCODING) as a_file:
a_file.writelines(lines)
# --------------------------------------------------------------------------
# Replace dunders inside a file header
# --------------------------------------------------------------------------
def _fix_header(self, line):
"""
Replace dunders inside a file header
Args:
line: The header line of the file in which to replace text
Returns:
The new header line
Replaces text inside a header line, using a regex to match specific
lines. Given a line, it replaces the found pattern with the replacement
as it goes.
"""
# break apart header line
# NB: gotta do this again, can't pass res param
str_pattern = self._dict_type_rules[C.S_KEY_HDR_SCH]
res = re.search(str_pattern, line)
if not res:
return line
# pull out lead, val, and pad using group match values from M
lead = res.group(self._dict_type_rules[C.S_KEY_LEAD])
val = res.group(self._dict_type_rules[C.S_KEY_VAL])
pad = res.group(self._dict_type_rules[C.S_KEY_PAD])
# this is a complicated function to get the length of the spaces
# between the key/val pair and the RAT (right-aligned text)
tmp_val = str(val)
old_val_len = len(tmp_val)
for key2, val2 in self._dict_rep.items():
if isinstance(val2, str):
tmp_val = tmp_val.replace(key2, val2)
new_val_len = len(tmp_val)
val_diff = new_val_len - old_val_len
# get new padding value based in diff key/val length
tmp_pad = str(pad)
tmp_rat = tmp_pad.lstrip()
len_pad = len(tmp_pad) - len(tmp_rat) - val_diff
pad = " " * len_pad
# put the header line back together, adjusting for the pad len
line = lead + tmp_val + pad + tmp_rat + "\n"
# return
return line
# --------------------------------------------------------------------------
# Replace dunders inside a file's contents
# --------------------------------------------------------------------------
def _fix_code(self, code):
"""
Replace dunders inside a file's contents
Args:
code: The code portion of the line to replace text in
Returns:
The new line of code
Replaces text inside the code portion of a line. Given a line,
replaces dunders as it goes. When it is done, it returns the new line.
This replaces the __PP dunders inside the file, excluding blank lines,
headers, and flag switches (all of which are previously handled in
_fix_contents).
"""
# replace content using current flag setting
for key, val in self._dict_rep.items():
if isinstance(val, str):
code = code.replace(key, val)
# return the (maybe replaced) line
return code
# --------------------------------------------------------------------------
# Replace dunders inside a file's contents
# --------------------------------------------------------------------------
def _fix_text(self, path):
"""
Replace dunders inside a file's contents
Args:
path: The path to the file to fix text
Returns:
The new line of code
Replaces text inside the a file. This is a qnd function to replace any
dunder in any file, regardless of D_TYPE_RULES. Think of it as an
oubliette for fi;es you just want to 'undunderize'.
"""
# default lines
lines = []
# open and read file
with open(path, "r", encoding=C.S_ENCODING) as a_file:
lines = a_file.readlines()
# for each line in array
for index, line in enumerate(lines):
# ------------------------------------------------------------------
# skip blank lines
if line.strip() == "":
continue
# replace content using current flag setting
for key, val in self._dict_rep.items():
if isinstance(val, str):
line = line.replace(key, val)
# put new line back in file
lines[index] = line
# open and write file
with open(path, "w", encoding=C.S_ENCODING) as a_file:
a_file.writelines(lines)
# --------------------------------------------------------------------------
# Rename dirs/files in the project
# --------------------------------------------------------------------------
def _fix_path(self, path):
"""
Rename dirs/files in the project
Args:
path: Path for dir/file to be renamed
Rename dirs/files. Given a path, it renames the dir/file by replacing
dunders in the path with their appropriate replacements from
self._dict_rep.
"""
# sanity check
path = Path(path)
# first get the path name (we only want to change the last component)
last_part = path.name
# # replace dunders in last path component
for key, val in self._dict_rep.items():
if isinstance(val, str):
last_part = last_part.replace(key, val)
# replace the name
path_new = path.parent / last_part
# if it hasn't changed, skip to avoid overhead
if path_new == path:
return
# do rename
if path.is_dir():
shutil.move(path, path_new)
else:
path.rename(path_new)
# --------------------------------------------------------------------------
# Reload dicts after any outside changes
# --------------------------------------------------------------------------
def _reload_dicts(self):
"""
Reload dicts after any outside changes
This function is called when a dict is passed to another function, in
order to keep it synced with the internal dict.
"""
# update individual dicts in dict_prv
self._dict_prv_all = self._dict_prv[C.S_KEY_PRV_ALL]
self._dict_prv_prj = self._dict_prv[C.S_KEY_PRV_PRJ]
# update individual dicts in dict_pub
self._dict_pub_bl = self._dict_pub[C.S_KEY_PUB_BL]
self._dict_pub_dbg = self._dict_pub[C.S_KEY_PUB_DBG]
self._dict_pub_dist = self._dict_pub[C.S_KEY_PUB_DIST]
self._dict_pub_docs = self._dict_pub[C.S_KEY_PUB_DOCS]
self._dict_pub_i18n = self._dict_pub[C.S_KEY_PUB_I18N]
self._dict_pub_meta = self._dict_pub[C.S_KEY_PUB_META]
# update debug dict
if not self._debug:
self._dict_debug = self._dict_pub_dbg
# --------------------------------------------------------------------------
# Save project info before fix
# --------------------------------------------------------------------------
def _save_project_info(self):
"""
Save project info before fix
Saves the private.json and project.json files after all modifications,
and reloads them to use in _do_fix.
"""
# ----------------------------------------------------------------------
# save project settings
# create private settings
dict_prv = {
C.S_KEY_PRV_ALL: self._dict_prv_all,
C.S_KEY_PRV_PRJ: self._dict_prv_prj,
}
# save private settings
path_prv = self._dir_prj / C.S_PRJ_PRV_CFG
F.save_dict(dict_prv, [path_prv])
# create public settings
dict_pub = {
C.S_KEY_PUB_BL: self._dict_pub_bl,
C.S_KEY_PUB_DBG: self._dict_pub_dbg,
C.S_KEY_PUB_DIST: self._dict_pub_dist,
C.S_KEY_PUB_DOCS: self._dict_pub_docs,
C.S_KEY_PUB_I18N: self._dict_pub_i18n,
C.S_KEY_PUB_META: self._dict_pub_meta,
}
# save public settings
path_pub = self._dir_prj / C.S_PRJ_PUB_CFG
F.save_dict(dict_pub, [path_pub])
# ----------------------------------------------------------------------
# fix dunders in dict_pub w/o _dict_rep (project.json)
self._fix_contents(path_pub)
# reload dict from fixed file
dict_pub = F.load_dicts([path_pub])
# reload dict pointers after dict change
self._reload_dicts()
# --------------------------------------------------------------------------
# These are minor steps called from the main steps
# --------------------------------------------------------------------------
# --------------------------------------------------------------------------
# Check project type for allowed characters
# --------------------------------------------------------------------------
def _check_type(self, prj_type):
"""
Check project type for allowed characters
Args:
prj_type: Type to check for allowed characters
Returns:
Whether the type is valid to use
Checks the passed type to see if it is one of the allowed project
types.
"""
# sanity check
if len(prj_type) == 1:
# get first char and lower case it
first_char = prj_type[0].lower()
# check if it's one of ours
first_char_test = [item[0] for item in C.L_TYPES]
if first_char in first_char_test:
return True
# nope, fail
types = []
s = ""
for item in C.L_TYPES:
types.append(item[0])
s = ", ".join(types)
print(C.S_ERR_TYPE.format(s))
return False
# --------------------------------------------------------------------------
# Check project name for allowed characters
# --------------------------------------------------------------------------
def _check_name(self, name_prj):
"""
Check project name for allowed characters
Args:
name_prj: Name to check for allowed characters
Returns:
Whether the name is valid to use
Checks the passed name for these criteria:
1. longer than 1 char
2. starts with an alpha char
3. ends with an alphanumeric char
4. contains only alphanumeric chars and/or dash(-) or underscore(_)
"""
# NB: there is an easier way to do this with regex:
# ^([a-zA-Z]+[a-zA-Z\d\-_ ]*[a-zA-Z\d]+)$ AND OMG DID IT TAKE A LONG
# TIME TO FIND IT! in case you were looking for it. It will give you a
# quick yes-no answer. I don't use it here because I want to give the
# user as much feedback as possible, so I break down the regex into
# steps where each step explains which part of the name is wrong.
# check for name length
if len(name_prj.strip(" ")) < 2:
print(C.S_ERR_LEN)
return False
# match start or return false
pattern = C.D_NAME[C.S_KEY_NAME_START]
res = re.search(pattern, name_prj)
if not res:
print(C.S_ERR_START)
return False
# match end or return false
pattern = C.D_NAME[C.S_KEY_NAME_END]
res = re.search(pattern, name_prj)
if not res:
print(C.S_ERR_END)
return False
# match middle or return false
pattern = C.D_NAME[C.S_KEY_NAME_MID]
res = re.search(pattern, name_prj)
if not res:
print(C.S_ERR_MID)
return False
# if we made it this far, return true
return True
# --------------------------------------------------------------------------
# Combine reqs from template/all and template/prj_type
# --------------------------------------------------------------------------
def _merge_reqs(self, prj_type_long):
"""
Combine reqs from template/all and template/prj_type
Args:
prj_type_long: the folder in template for the current project type
This method combines reqs from the all dir used by all projects, and
those used by specific project type (gui needs pygobject, etc).
"""
# get sources and filter out sources that don't exist
reqs_prj = C.S_FILE_REQS_TYPE.format(prj_type_long)
src = [
self.P_DIR_PP / C.S_FILE_REQS_ALL,
self.P_DIR_PP / reqs_prj,
]
src = [str(item) for item in src if item.exists()]
# get dst to put file lines
dst = self._dir_prj / C.S_FILE_REQS
# # the new set of lines for requirements.txt
new_file = []
# read reqs files and put in result
for item in src:
with open(item, "r", encoding=C.S_ENCODING) as a_file:
old_file = a_file.readlines()
old_file = [line.rstrip() for line in old_file]
uniq = set(new_file + old_file)
new_file = list(uniq)
# put combined reqs into final file
joint = "\n".join(new_file)
with open(dst, "w", encoding=C.S_ENCODING) as a_file:
a_file.writelines(joint)
__init__()
Initialize the new object
Initializes a new instance of the class, setting the default values of its properties, and any other code that needs to run to create a new object.
Source code in src/pyplate.py
def __init__(self):
"""
Initialize the new object
Initializes a new instance of the class, setting the default values
of its properties, and any other code that needs to run to create a
new object.
"""
# set the initial values of properties
# command line options
self._debug = False
# internal props
self._dir_prj = Path()
self._dict_rep = {}
self._dict_type_rules = {}
self._dict_sw_block = {}
self._dict_sw_line = {}
# private.json dicts
self._dict_prv = {}
self._dict_prv_all = {}
self._dict_prv_prj = {}
# project.json dicts
self._dict_pub = {}
self._dict_pub_bl = {}
self._dict_pub_dbg = {}
self._dict_pub_dist = {}
self._dict_pub_docs = {}
self._dict_pub_i18n = {}
self._dict_pub_meta = {}
# dictionary to hold current debug settings
self._dict_debug = {}
# cmd line stuff
# NB: placeholder to avoid comparing to None (to be set by subclass)
self._parser = argparse.ArgumentParser()
self._dict_args = {}
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/pyplate.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()
# ask user for project info
self._get_project_info()
check_switches(code, comm, dict_type_rules, dict_sw_block, dict_sw_line)
Check if line or trailing comment is a switch
Parameters:
Name | Type | Description | Default |
---|---|---|---|
comm
|
The comment part of a line to check for switches |
required | |
dict_type_rules
|
Dictionary containing the regex to look for |
required | |
dict_sw
|
Dictionary of switch values for either block or line |
required |
This method checks to see if a line or trailing comment contains a valid switch for the values in dict_type_rules. If a valid switch is found, it sets the appropriate flag in either dict_sw_block or dict_sw_line.
Source code in src/pyplate.py
def check_switches(code, comm, dict_type_rules, dict_sw_block, dict_sw_line):
"""
Check if line or trailing comment is a switch
Args:
comm: The comment part of a line to check for switches
dict_type_rules: Dictionary containing the regex to look for
dict_sw: Dictionary of switch values for either block or line
switches
This method checks to see if a line or trailing comment contains a
valid switch for the values in dict_type_rules. If a valid switch is
found, it sets the appropriate flag in either dict_sw_block or
dict_sw_line.
"""
# switch does not appear anywhere in line
res = re.search(dict_type_rules[C.S_KEY_SW_SCH], comm)
if not res:
return
# find all matches (case insensitive)
matches = re.finditer(dict_type_rules[C.S_KEY_SW_SCH], comm, flags=re.I)
# for each match
for match in matches:
# get key/val of switch
key = match.group(dict_type_rules[C.S_KEY_SW_KEY])
val = match.group(dict_type_rules[C.S_KEY_SW_VAL])
# try a bool conversion
# NB: in honor of John Valby (ddg him!)
val_b = val.lower()
if val_b == "true":
val = True
elif val_b == "false":
val = False
# pick a dict based on if there is preceding code
if code.strip() == "":
dict_sw_block[key] = val
else:
dict_sw_line[key] = val
get_type_rules(path)
Get the filetype-specific regexes (headers, comments. switches)
Parameters:
Name | Type | Description | Default |
---|---|---|---|
path
|
Path of the file to get the dict of regexes for |
required |
Returns:
Type | Description |
---|---|
The dict of regexes for this file type |
Source code in src/pyplate.py
def get_type_rules(path):
"""
Get the filetype-specific regexes (headers, comments. switches)
Args:
path: Path of the file to get the dict of regexes for
Returns:
The dict of regexes for this file type
"""
# iterate over reps
for _key, val in C.D_TYPE_RULES.items():
# fix ets if necessary
exts = val[C.S_KEY_RULES_EXT]
# # if we match ext, return only rep stuff
if is_path_in_list(path, exts):
return val[C.S_KEY_RULES_REP]
# default result is py rep
return {}
is_path_in_list(path, lst)
Check if a file is in a list of file extensions
Parameters:
Name | Type | Description | Default |
---|---|---|---|
path
|
The file to find |
required | |
lst
|
The list to look in |
required |
Returns:
Type | Description |
---|---|
Whether the file exists in the list |
Source code in src/pyplate.py
def is_path_in_list(path, lst):
"""
Check if a file is in a list of file extensions
Args:
path: The file to find
lst: The list to look in
Returns:
Whether the file exists in the list
"""
# lowercase the list
l_ext = [item.lower() for item in lst]
# add dots
l_ext = [
f".{item}" if not item.startswith(".") else item for item in l_ext
]
# check if the suffix or the filename (for dot files) matches
# NB: also checks for dot files
return path.suffix.lower() in l_ext or path.name.lower() in l_ext