I've written my own local "source control". Rather than using a commit-based system, when you're ready to release a version, you can run a command which will create a .zip
copy of your source code, and it's saved to a versions
folder.
How does it work?
Each "project" written with this has a specific file structure that looks like the below. A file structure like this is required in order for the source control to work.
/[Project Name]
/source
...
/versions
...
/info
changelog.txt
readme.txt
There are also three commands that are used. new
, push
, and changedir
. Each command argument is double colon, ::
, separated, and look something like the following:
new::[project Name]::[project info]
- Create a new project.push::[version name]::[version info]
- Push a new version to theversions
folder as a.zip
file.changedir::[directory]
- Change to a new directory.
Concerns
There are a few things I'm concerned about here.
- Have I designed this in a clear intuitive way? The current design right now feels clunky and hard to use.
- Am I over-documenting things? While I love documentation, if this is too much, tips on documentation would be appreciated.
- Am I correctly handling errors?
import os
import re
import shutil
def command_new_project(project_name: str, project_description: str):
"""Generate a new project.
This function generates a new project. A
project structure looks like this:
/[project name]
/source
...
/versions
...
/info
readme.txt
changelog.txt
Keyword arguments:
project_name -- The name of the project.
project_description -- A brief description of the project.
"""
os.makedirs("./{0}".format(project_name))
os.chdir("./{0}".format(project_name))
os.makedirs("./source")
os.makedirs("./versions")
os.makedirs("./info")
with \
open("./info/readme.txt", "w+") as readme, \
open("./info/changelog.txt", "w+") as changelog:
readme.write(project_description)
def command_push_version(version_number: str, version_description: str):
"""Push a new version.
This function pushes a the contents of the
./[project name]/source folder to a new folder
in ./[project name]/versions named with the
version number.
Keyword arguments:
version_number -- The version number. Must contain valid characters for folder names.
version_description -- A brief description of the changes in the version to be written to the changelog.
"""
if re.match("[a-zA-Z0-9_\-\s\.]+", version_number):
with open("./info/changelog.txt", "w") as changelog:
shutil.make_archive("./versions/{0}".format(version_number), format="zip", root_dir="./source")
changelog.write("\nVersion {0}".format(version_number))
changelog.write(version_description)
def command_change_directory(directory: str):
"""Change to a new directory.
This function allows the user to navigate to
another directory.
Keyword arguments:
directory -- The directory to navigate to.
"""
try:
os.chdir(directory)
except FileNotFoundError:
pass
def validate_user_input(valid_command, valid_arguments_length, command, arguments):
"""Validate user input.
This function checks to make sure that the
format that a user enters a command in is
correct.
Keyword arguments:
valid_command -- The valid command to check against.
valid_arguments_length -- The valid length of the list of inputted arguments.
command -- The command.
arguments -- The arguments of the command.
"""
if len(arguments) == valid_arguments_length:
if command == valid_command:
return True
else:
return False
else:
return False
def execute_user_input(tokenized_user_input: list):
"""Execute tokenized user input.
This function executes user input, like the
command "new::Awesome Project::A really awesome project."
after it's been tokenized.
Keyword arguments:
tokenized_user_input -- The tokenized form of the user's input.
"""
command = tokenized_user_input[0]
arguments = tokenized_user_input[1:]
if validate_user_input("new", 2, command, arguments):
command_new_project(arguments[0], arguments[1])
if validate_user_input("push", 2, command, arguments):
command_push_version(arguments[0], arguments[1])
if validate_user_input("changedir", 1, command, [arguments]):
command_change_directory(arguments[0])
def tokenize_user_input(user_input):
"""Tokenize user input into a list.
This function tokenizes the user's input into a
form that the execute_user_input function can
correctly interpret.
Keyword arguments:
user_input -- The user's input.
"""
tokenized_user_input = re.split("\s*::\s*", user_input)
return tokenized_user_input
def main():
while True:
user_input = input("lsc> ")
execute_user_input(tokenize_user_input(user_input))
if __name__ == "__main__":
main()
If you're curious about this project, you can find it on Github, here.
<CommentReview>
Pretty cool, but sorely missing a way to get a version back into the working directory.<CommentReview />
– RubberDuck Jul 29 at 1:22