Skip to content

Commit

Permalink
v1.3.0 -> added more flexibility and customization to the keyword mec…
Browse files Browse the repository at this point in the history
…hanic + changed the content of the php project files + added cpp file content + upgraded the HELP action to be more informative.
  • Loading branch information
PedroHenriques committed Jan 23, 2017
1 parent 1510919 commit 0b48ba2
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 45 deletions.
26 changes: 20 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,36 @@ In this example, if the program is told to create a new python project (exact sy

**Special Keywords:**

The content of files, defined in the JSON files, can have special keywords that will be replaced by dynamic or static data.
All keywords are in the format `|!keyword!|`. The supported keywords are:
The content of files, created by both the file and the project actions, can have special keywords that will be replaced by dynamic or static data.

All keywords are in the format `|!keyword{multiplier}[case]!|`.
- `keyword`: One of the supported keywords. See below for a list of global keywords, as well as, how to add your own.
- `multiplier` (Optional): An integer (whole number) indicating how many times that keyword replacement should be inserted.
- `case` (Optional): A tag indicating a specific case status for the keyword replacement. The supported tags are:
- `uc`: insert the keyword replacement in UPPERCASE.
- `lc`: insert the keyword replacement in lowercase.
- `t`: insert the keyword replacement Capitalized.

The **global keywords** are:

Keyword | Replace Value | Supported Actions
--- | --- | ---
copyright | copyright text<br>see below for details on how to customize the text | project<br>file
project_name | the new project's name | project<br>file (1)
file_name | the new file's name | project<br>file
file_type | the new file's type | project<br>file
project_type | the new project's type | project
no_www_domain | the new project's name, striped of any starting "www." | project
file_name | the new file's name | file
file_type | the new file's type | file

(1) When creating a new file, the code will search the file's path for the first directory with a `.git` folder inside it. That directory will be treated as the file's project_name.

All static keywords and their replacement strings are defined in the `keywords.json` file.
Any keywords added to this file will become usable in file's content.
Adding **custom keywords**:

All custom keywords and their replacement strings are defined in the `keywords.json` file.
Any keywords added to this file will become usable in all the actions.

It is valid to use other keywords in the replacement string.

This is also where the copyright text is defined and can be customized.

NOTE: the code expects the keyword to have a string as a value in `keywords.json` and will replace it with an empty string if that is not the case.
Expand Down
118 changes: 98 additions & 20 deletions classes/Application.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# Python Project Manager v1.2.1 #
# Python Project Manager v1.3.0 #
# #
# Copyright 2016, PedroHenriques #
# http://www.pedrojhenriques.com #
Expand Down Expand Up @@ -198,6 +198,21 @@ def executeHelp(self) :
help_string += "\n\t- " + key
else:
# a topic was provided
# split the topic into its parts
topic_parts = topic.split(":")

# check if a sub topic was provided
sub_topic = True
if (len(topic_parts) == 1) :
# it wasn't
sub_topic = False

# ask for the base information about this topic
topic += ":base"

# update the topic parts
topic_parts = topic.split(":")

# get the information relevant for the desired topic
if (not self.updateJsonData(topic)) :
# the file_type isn't defined, so bail out
Expand All @@ -213,12 +228,53 @@ def executeHelp(self) :
# store the help string
help_string = self.json_data

# if a subtopic was provided, add the extra information
if (sub_topic) :
if (topic_parts[1] == "type") :
# get the relevant JSON file's content
sub_topic_json_data = self.parseJSON(self.json_path + topic_parts[0] + ".json")

# check if the topic is "project"
if (topic_parts[0] == "project") :
# it is, so only loop through the 1st tier of the JSON file
# loop through the JSON's content
for key in sub_topic_json_data :
help_string += "\n\t- " + key
else :
# it isn't, so loop through all the tiers of the JSON file
# loop through the JSON's content
for key in sub_topic_json_data :
help_string += "\n\t- " + self.buildTypesString(key, sub_topic_json_data[key])

# print the help text
print(help_string)

# at this point everything went OK
return(True)

# builds and returns a string with all the type supported by the various actions
# NOTE: called recursively
def buildTypesString(self, parent_str, data) :
res = ""

# loop through the data
for key in data :
# check if key's value is a string
if (isinstance(data[key], str)) :
# it is, so this is an end tier which is to be ignored
continue

# this isn't an end tier
# update the type string with this tier's sub-tiers
res += self.buildTypesString(parent_str + ":" + key, data[key]) + " "

# check if this tier had any valid sub-tiers
if (len(res) == 0) :
# it didn't, so the type is the tier itself
res = parent_str

return(res.strip())

# searches the selected JSON file for the needed information
# the changes will be made to self.json_data
# return True if successful or False otherwise
Expand Down Expand Up @@ -283,16 +339,20 @@ def createStructure(self, structure, path) :
# build the dictionary with replacement keywords
replacements = self.keywords.copy()

# determine this file's extension
aux_pos = key.rfind(".")
if (aux_pos == -1) :
file_extension = ""
else :
file_extension = key[aux_pos + 1:]

# add this file's name and type to the replacements
replacements["file_name"] = key[:aux_pos]
replacements["file_type"] = file_extension

# check if this file requires the copyright text to be inserted
if ("|!copyright!|" in file_content) :
# it does
# determine this file's extension
aux_pos = key.rfind(".")
if (aux_pos == -1) :
file_extension = ""
else :
file_extension = key[aux_pos + 1:]

# add the copyright replacement information
replacements["copyright"] = self.buildCopyrightString(file_extension)

Expand Down Expand Up @@ -371,35 +431,53 @@ def deleteDir(self, path) :
# NOTE: any keywords found in string not present in replacements will be replaced by an empty string
def replaceKeyWords(self, replacements, string) :
# the pattern to identify the placeholders
re_pattern = "\|!([^{!]+)\{?(\d+)*\}?!\|"
re_pattern = "\|!([^{!\[\]]+)(\{\d+\})?(\[[^{!\[\]]+\])?!\|"

# the map between case tag in the regex and the String class function to use
str_func = {"lc" : "lower", "uc" : "upper", "t" : "title"}

# loop while there are keywords in the string
re_matches = re.search(re_pattern, string)
while (re_matches != None) :
# grab the re_matches groups
match_groups = re_matches.groups()
match_groups = re_matches.groups("")
match_count = len(match_groups)

# check if the keyword found is present in replacements and is a string
if (match_groups[0] in replacements and isinstance(replacements[match_groups[0]], str)) :
# it is
# build the new_string
# build the replacement string
new_string = replacements[match_groups[0]]
else :
# it isn't
# replace the keyword with an empty string
new_string = ""

# check if this match has a multiplier
if (match_groups[1] == None) :
# it doesn't
keyword = match_groups[0]
else :
# it does
keyword = match_groups[0] + "{" + match_groups[1] + "}"
new_string *= int(match_groups[1])
# check if there is any other information provided
if (len(new_string) > 0 and match_count > 1) :
# there is
# loop through the remaining matches
i = 1
while (i < match_count):
# check if this match is a multiplier
if (match_groups[i].startswith("{") and match_groups[i].endswith("}")) :
# it is
# update the replacement string
new_string *= int(match_groups[i][1:-1])
# check if this match is a case enforcer
elif (match_groups[i].startswith("[") and match_groups[i].endswith("]")) :
# it is
# check if this case enforcer is valid
case_enforcer = match_groups[i][1:-1].lower()
if (case_enforcer in str_func) :
# it is
# update the replacement string
new_string = getattr(new_string, str_func[case_enforcer], new_string)()

i += 1

# replace the keyword with the new_string
string = string.replace("|!" + keyword + "!|", new_string)
string = string.replace("|!" + "".join(match_groups) + "!|", new_string)

# check the pattern again
re_matches = re.search(re_pattern, string)
Expand Down
7 changes: 5 additions & 2 deletions classes/CLI.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# Python Project Manager v1.2.1 #
# Python Project Manager v1.3.0 #
# #
# Copyright 2016, PedroHenriques #
# http://www.pedrojhenriques.com #
Expand Down Expand Up @@ -130,7 +130,10 @@ def processHelp(self) :

# store the necessary arguments
# desired topic, if 1 was given, or None otherwise
self.args["topic"] = sys.argv[2] if (len(sys.argv) > 2) else None
if (len(sys.argv) > 2) :
self.args["topic"] = sys.argv[2]
else :
self.args["topic"] = None

# all OK
return(True)
2 changes: 1 addition & 1 deletion classes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# #
# Python Project Manager v1.2.1 #
# Python Project Manager v1.3.0 #
# #
# Copyright 2016, PedroHenriques #
# http://www.pedrojhenriques.com #
Expand Down
34 changes: 30 additions & 4 deletions data/file.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,50 @@
{
"php" : {
"blank" : {
"extension" : "php",
"content" : "<?php\n|!copyright!|\n\n\n\n?>"
},
"class" : {
"extension" : "php",
"content" : "<?php\n|!copyright!|\n\nclass |!file_name!| {\n\tpublic function __construct() {}\n}\n\n?>"
"content" : "<?php\n|!copyright!|\n\nclass |!file_name[t]!| {\n\tpublic function __construct() {}\n}\n\n?>"
},
"ajax" : {
"extension" : "php",
"content" : "<?php\nif (session_status() == PHP_SESSION_NONE) {\n\tsession_start();\n}\n\n|!copyright!|\n\nrequire_once(dirname(__DIR__).\"\\\\templates\\\\init.php\");\n\nrequire(PROJECT_ROOT.\"\\\\release\\\\code\\\\ajax_files\\\\|!file_name!|.php\");\n\n?>"
"content" : "<?php\nif (session_status() == PHP_SESSION_NONE) {\n\tsession_start();\n}\n\n|!copyright!|\n\n// load the init file\n// try with the 2 slashes\nif (file_exists(dirname(__DIR__).\"\\\\templates\\\\init.php\")) {\n\trequire_once(dirname(__DIR__).\"\\\\templates\\\\init.php\");\n}elseif (file_exists(dirname(__DIR__).\"/templates/init.php\")) {\n\trequire_once(dirname(__DIR__).\"/templates/init.php\");\n}else{\n\t// couldn't find the init file\n\techo(\"Couldn't load the init file!\");\n\texit;\n}\n\nrequire(General::getServerPath(PROJECT_ROOT.\"\\\\release\\\\code\\\\ajax_files\\\\|!file_name!|.php\"));\n\n?>"
}
},
"ruby" : {
"blank" : {
"extension" : "rb",
"content" : "|!copyright!|\n\n"
},
"class" : {
"extension" : "rb",
"content" : "|!copyright!|\n\nclass |!file_name!|\n\tdef initialize()\n\tend"
"content" : "|!copyright!|\n\nclass |!file_name[t]!|\n\tdef initialize()\n\tend"
}
},
"python" : {
"blank" : {
"extension" : "py",
"content" : "|!copyright!|\n\n"
},
"class" : {
"extension" : "py",
"content" : "|!copyright!|\n\nclass |!file_name!| :\n\t\"\"\"String with class information\"\"\"\n\n\tdef __init__(self) :\n\t\tpass"
"content" : "|!copyright!|\n\nclass |!file_name[t]!| :\n\t\"\"\"String with class information\"\"\"\n\n\tdef __init__(self) :\n\t\tpass"
}
},
"cpp" : {
"blank" : {
"extension" : "cpp",
"content" : "|!copyright!|\n\n"
},
"class" : {
"extension" : "cpp",
"content" : "|!copyright!|\n\n#include \"|!file_name[t]!|.h\"\n\n|!file_name[t]!|::|!file_name[t]!|() {}\n\n|!file_name[t]!|::~|!file_name[t]!|() {}"
},
"header" : {
"extension" : "h",
"content" : "|!copyright!|\n\n// include guard: |!file_name[uc]!|_H\n#ifndef |!file_name[uc]!|_H\n#define |!file_name[uc]!|_H\n\n// includes\n\n// class definition\nclass |!file_name[t]!| {\n\tpublic:\n\t\t|!file_name[t]!|();\n\t\t~|!file_name[t]!|();};\n\n// include guard: |!file_name[uc]!|_H\n#endif"
}
},
"css" : {
Expand Down
10 changes: 8 additions & 2 deletions data/help.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
{
"project" : "=> Creates a new project. The command syntax is:\n\tproject path name type\n- path is the path, relative to the current directory, where the project should be created. Use a fullstop if the current directory is the desired path.\n- name is the project's directory name\n- type is the type of the project, as defined in \"project.json\". Use : to separate JSON tiers.",
"file" : "=> Creates a new file. The command syntax is:\n\tfile path name type [-flags]\n- path is the path, relative to the current directory, where the file should be created. Use a fullstop if the current directory is the desired path.\n- name is the file's name (without the extension).\n- type is the type of the file, as defined in \"file.json\". Use : to separate JSON tiers.\n- [-flags] is an optional argument where configuration flags can be provided. The supported flags are:\n\tf = force the creation of the file by creating any directories in the path that don't exist.\n\to = if a file with the same path already exists, it will be overwritten."
"project" : {
"base" : "=> Creates a new project. The command syntax is:\n\tproject path name type\n- path is the path, relative to the current directory, where the project should be created. Use a fullstop if the current directory is the desired path.\n- name is the project's directory name\n- type is the type of the project, as defined in \"project.json\". Type \"help project:type\" for a list of supported project types.",
"type" : "=> The project types supported by this program are:"
},
"file" : {
"base" : "=> Creates a new file. The command syntax is:\n\tfile path name type [-flags]\n- path is the path, relative to the current directory, where the file should be created. Use a fullstop if the current directory is the desired path.\n- name is the file's name (without the extension).\n- type is the type of the file, as defined in \"file.json\". Type \"help file:type\" for a list of supported file types.\n- [-flags] is an optional argument where configuration flags can be provided. The supported flags are:\n\tf = force the creation of the file by creating any directories in the path that don't exist.\n\to = if a file with the same path already exists, it will be overwritten.",
"type" : "=> The file types supported by this program are:"
}
}
4 changes: 3 additions & 1 deletion data/keywords.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"js" : {"start" : "/*!!", "end" : "*/", "char" : "*"},
"scss" : {"start" : "/*!!", "end" : "*/", "char" : "*"},
"rb" : {"start" : "#", "end" : "#", "char" : "#"},
"py" : {"start" : "#", "end" : "#", "char" : "#"}
"py" : {"start" : "#", "end" : "#", "char" : "#"},
"cpp" : {"start" : "/*", "end" : "*/", "char" : "*"},
"h" : {"start" : "/*", "end" : "*/", "char" : "*"}
}
}
}
Loading

0 comments on commit 0b48ba2

Please sign in to comment.