From 826fb3ffe33ab13fac043c74e844a9384b3d9d89 Mon Sep 17 00:00:00 2001
From: Starbeamrainbowlabs <sbrl@starbeamrainbowlabs.com>
Date: Thu, 7 Jun 2018 15:15:23 +0100
Subject: [PATCH] Initial commit. Didn't realise I'd be using this in so many
 different places!

---
 README.md     | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++
 build-example | 124 ++++++++++++++++++++++++++++++++
 lantern.sh    | 164 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 481 insertions(+)
 create mode 100644 README.md
 create mode 100755 build-example
 create mode 100755 lantern.sh

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1bdf454
--- /dev/null
+++ b/README.md
@@ -0,0 +1,193 @@
+# Lantern Build Engine
+
+> A simple bash-based build engine for automating complex, fiddly, and boring development tasks.
+
+Wow, this ran away from me! This was initially a simple bash script for a private project of mine, but it seems to have turned into a general-purpose engine all by itself. I guess that stealing it for a bunch of University projects had something to do with that.... :P
+
+## Installing
+The lantern build engine is meant to be included in your project as a git submodule.
+
+1. Add this repository as a git submodule: `git submodule add {{clone-url}} {{path/to/destination-directory}}`
+2. `source` the file `lantern.sh` into your build script.
+
+An example build script can be found in this repository - it's called `build-example`.
+
+## Usage
+The lantern build engine basically exposes a set of utility function that make writing a flexible build script _much_ easier. Start by copying the example build script, and customising it to suit your own needs.
+
+Each task is a function that's prefixed with `task_`, so you can wire up task dependencies as complex or as simple as you like! At the end of your build script, execute the provided `tasks_run` function like this to execute all the requested tasks: `tasks_run $@`
+
+### `set-title "{{title name}}"`
+Sets the title of the terminal the build script is running in. Has no effect if the build script isn't being run in a terminal as far as I'm aware.
+
+```bash
+set-title "Current item: ${item_name}";
+```
+
+### `cursor_position {{x}} {{y}}`
+Sets the cursor's position on the screen.
+
+```bash
+cursor_position 10 20; # Sets the cursor's position to (10, 20)
+```
+
+### `cursor_up {{rows}}`
+Moves the cursor up by a specified number of rows.
+
+```bash
+cursor_up 5;
+```
+
+### `cursor_down {{rows}}`
+Moves the cursor down by a specified number of rows.
+
+```bash
+cursor_down 5;
+```
+
+### `cursor_left {{cols}}`
+Moves the cursor left by a specified number of columns.
+
+```bash
+cursor_left 5;
+```
+
+### `cursor_right {{cols}}`
+Moves the cursor right by a specified number of columns.
+
+```bash
+cursor_right 5;
+```
+
+### `cursor_save`
+Saves the cursor's current position. Takes no arguments.
+
+```bash
+cursor_save;
+```
+
+### `cursor_restore`
+Restores the cursor's position to the last saved position. Takes no arguments.
+
+```bash
+cursor_restore;
+```
+
+### `right_aligned_text {{width}} "{{text}}"`
+Right-aligns the specified text such that it's on the the right-hand-side of the terminal when `echo`ed. The `{{width}}` parameter here is the width of the provided text, as ANSI colour escape codes throw the width calculations off (a pull request fixing this would be most welcome!).
+
+```bash
+right_aligned_text 12 "Hello, World!";
+# Example output: "            Hello, World!" (without quotes, of course)
+```
+
+### `task_status {{2-char-code}}`
+Used internally. Generates a 2-character status code like OpenRC and others do in the terminal on startup. See the example for more details.
+
+```bash
+set_status "!!";
+# Output: "[ !! ]" (With the square brackets in bright blue)
+```
+
+### `stage_begin "{{stage_name}}"`
+Begins a build stage. The output will fill the terminal's width.
+
+```bash
+stage_begin "Building";
+# Output for a 20 column wide terminal:
+# ----[ Building ]----
+```
+
+### `stage_end {{exit_code}}`
+Ends a stage, displaying either `ok` or `!!`, depending on whether the exit code provided is 0 or not (an exit code of 0 means success). The output will be coloured, of course - this isn't displayed below due to limitations in markdown.
+
+```bash
+echo "Testing";
+stage_end $?;
+
+# Output for a 20 column wide terminal:
+# -------[ ok ]-------
+```
+
+### `task_begin "{{task_name}}"`
+Begins a task. Works similarly to `stage_begin` - the visuals are just different.
+
+```bash
+task_begin "Checking environment";
+# Output for:
+#  * Checking environment
+```
+
+### `task_end {{exit_code}}`
+The same as `stage_end`, but the visuals are a bit different. The 2-character status code (` [ xy ] `) is displayed at the right-hand side on the line above the line that the cursor is currently on.
+
+```bash
+task_begin "Checking environment";
+task_end 0;
+# Output:
+# " * Checking environment          [ ok ] " (without quotes)
+```
+
+```bash
+task_begin "Combobulating discombobulators";
+# ......
+task_end $?;
+```
+
+...might output something like this:
+
+```
+ * Combobulating discombobulators
+Output
+More output
+Even moar output
+Yay for output                         [ ok ] 
+(cursor position is here)
+```
+
+### `subtask_begin "{{task_name}}"`
+For splitting up your tasks even further! Generally, if you find yourself using a lot of these, try upgrading to stages and tasks instead of tasks and subtasks - particularly because these don't play so well with output, due to changing the colour of the asterisk (this may be fixed in the future, if the appropriate magic can be conjured :P).
+
+```bash
+subtask_begin "Checking git";
+# Output: " * \tChecking git" (without quotes, and replacing "\t" with a tab character)
+```
+
+### `subtask_end {{exit_code}}`
+The counterpart to `subtask_begin`, this changes the asterisk colour and outputs a 2-character status code to complete a subtask.
+
+```bash
+subtask_begin "Checking git";
+which git 1>/dev/null 2>&1;
+subtask_end $?;
+# Output:
+#  * \tChecking git            [ ok ]
+# (replacing "\t" with a tab character)
+```
+
+### `check_command "{{binary_name}} {{subtask}}"`
+Automagically checks for the presence of a command. Kills the process with an exit code of `2` if it can't be found. Setting `{{subtask}}` to a non-empty string causes `subtask_begin` / `subtask_end` to be called appropriately - otherwise it's invisible.
+
+```bash
+task_begin "Checking environment";
+check_command "git" true;
+check_command "msbuild" true;
+task_end 0;
+```
+
+### `tasks_run {{task_name}} {{task_name}} .....`
+Runs the specified tasks in the specified order. Call this at the bottom of your build script.
+
+```bash
+tasks_run setup; # Runs the task_setup function
+tasks_run build package; # Runs the build and package tasks as above
+tasks_run $@; # Passes all arguments (except the script name / path itself) to the task runner
+```
+
+## Contributing
+Contributions are welcome! Simply fork this repository, make your changes, and submit a Pull Request (aka Merge Request).
+
+All contributions must be declared to have the `Mozilla Public License 2.0` (the same license that this repository is under).
+
+## License
+The lantern build engine is licensed under the _Mozilla Public License 2.0_ (MPL-2.0). This license can be found in the _LICENSE_ file in this repository, along with a link to an easy-to-read summary.
\ No newline at end of file
diff --git a/build-example b/build-example
new file mode 100755
index 0000000..ec57a4a
--- /dev/null
+++ b/build-example
@@ -0,0 +1,124 @@
+#!/usr/bin/env bash
+
+project_name=micro-lanterns;
+
+build_output_folder="dist/";
+
+###############################################################################
+
+source $(dirname $0)/lantern.sh
+
+if [[ "$#" -lt 1 ]]; then
+	echo -e "${FBLE}${project_name}${RS} build script";
+	echo -e "    by Starbeamrainbowlabs";
+	echo -e "${LC}Powered by the lantern build engine, v${version}${RS}";
+	echo -e "";
+	echo -e "${CSECTION}Usage${RS}";
+	echo -e "    ./build ${CTOKEN}{action}${RS} ${CTOKEN}{action}${RS} ${CTOKEN}{action}${RS} ...";
+	echo -e "";
+	echo -e "${CSECTION}Available actions${RS}";
+	echo -e "    ${CACTION}setup${RS}            - Perform initial setup";
+	echo -e "    ${CACTION}main${RS}             - Perform a regular build";
+	echo -e "    ${CACTION}sprites${RS}          - Build the spritesheet";
+	echo -e "    ${CACTION}js${RS}               - Build the javascript";
+	echo -e "    ${CACTION}dev-server${RS}       - Start a development server";
+	echo -e "    ${CACTION}dev-server-stop${RS}  - Stop the currently running development server";
+	echo -e "";
+	
+	exit 1;
+fi
+
+###############################################################################
+
+function task_setup {
+	task_begin "Setting up";
+	
+	check_command git true;
+	check_command msbuild true;
+	check_command node true;
+	check_command npm true;
+	
+	subtask_begin "Creating build output directory";
+	mkdir -p "${build_output_folder}";
+	subtask_end $?;
+	
+	subtask_begin "Initialising submodules";
+	git submodule update --init;
+	subtask_end $?;
+	
+	task_end 0;
+}
+
+function task_dev-server {
+	task_begin "Starting development server";
+	php -S [::1]:40482 -t "${build_output_folder}" &
+	exit_code=$?;
+	echo $! >/tmp/micro-lantern-dev-server.pid;
+	task_end $?; # Should be 0 unless php died for some reason
+	sleep 1;
+}
+
+function task_dev-server-stop {
+	task_begin "Stopping development server";
+	
+	kill "$(cat /tmp/micro-lantern-dev-server.pid)";
+	rm /tmp/micro-lantern-dev-server.pid;
+	
+	task_end $?;
+}
+
+function task_main {
+	run_task js;
+	
+	task_begin "Copying html";
+	cp index.html "${build_output_folder}";
+	task_end $?;
+	
+	task_begin "Copying css";
+	cp theme.css "${build_output_folder}";
+	task_end $?;
+}
+
+function task_js {
+	task_begin "Validating scripts";
+	
+	scripts/validate.sh
+	
+	task_end $?;
+	
+	task_begin "Running webpack";
+	
+	node_modules/webpack/bin/webpack.js --config webpack.config.js
+	exit_code=$?;
+	
+	task_end ${exit_code};
+	if [[ "${exit_code}" -ne 0 ]]; then
+		exit 1;
+	fi
+}
+
+function task_sprites {
+	# Build SpritePacker if required
+	if [ ! -f "./tools/SpritePacker/SpritePacker-CLI/bin/Debug/SpritePackerCLI.exe" ]; then
+		task_begin "Building SpritePacker";
+		
+		cd tools/SpritePacker;
+		msbuild; exit_code=$?;
+		
+		task_end $exit_code;
+		
+		if [[ "${exit_code}" -ne 0 ]]; then
+			exit ${exit_code};
+		fi
+	fi
+	
+	task_begin "Building Sprites";
+	
+	echo -e "Not implemented yet";
+	
+	task_end 0;
+}
+
+###############################################################################
+
+tasks_run $@;
diff --git a/lantern.sh b/lantern.sh
new file mode 100755
index 0000000..bc1ae13
--- /dev/null
+++ b/lantern.sh
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+version="0.5";
+
+##########################
+### Colour Definitions ###
+#### ANSI color codes ####
+RS="\033[0m"    # reset
+HC="\033[1m"    # hicolor
+UL="\033[4m"    # underline
+INV="\033[7m"   # inverse background and foreground
+LC="\033[2m"    # locolor / dim
+FBLK="\033[30m" # foreground black
+FRED="\033[31m" # foreground red
+FGRN="\033[32m" # foreground green
+FYEL="\033[33m" # foreground yellow
+FBLE="\033[34m" # foreground blue
+FMAG="\033[35m" # foreground magenta
+FCYN="\033[36m" # foreground cyan
+FWHT="\033[37m" # foreground white
+BBLK="\033[40m" # background black
+BRED="\033[41m" # background red
+BGRN="\033[42m" # background green
+BYEL="\033[43m" # background yellow
+BBLE="\033[44m" # background blue
+BMAG="\033[45m" # background magenta
+BCYN="\033[46m" # background cyan
+BWHT="\033[47m" # background white
+
+CSECTION=${HC}${FBLE};
+CTOKEN=${FCYN};
+CACTION=${FYEL};
+##########################
+
+
+# Sets the title of the terminal window.
+# $1 - The new text to set the title to.
+function set-title { echo -e '\033]2;'$1'\033\\'; }
+
+#######################
+### Cursor Movement ###
+# http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html
+function cursor_position {
+	echo -ne "\033[$1;$2H";
+}
+function cursor_up {
+	echo -ne "\033[$1A";
+}
+function cursor_down {
+	echo -ne "\033[$1B";
+}
+function cursor_right {
+	echo -ne "\033[$1C";
+}
+function cursor_left {
+	echo -ne "\033[$1D";
+}
+function cursor_save {
+	echo -ne "\033[s";
+}
+function cursor_restore {
+	echo -e "\033[u"
+}
+#######################
+
+# $1 - The width of the text to draw
+# $2 - The text to draw
+function right_aligned_text {
+	cursor_save
+	echo -ne "\r"; # Reset to the beginning of the line
+	location_start=$((( $(tput cols) - $1))); # Calculate where we need to draw at
+	cursor_right ${location_start}; # Jump ahead to the right place
+	echo -ne "$2"; # Draw the string
+	cursor_restore
+}
+
+# $1 - The 2-character status to show
+function task_status {
+	right_aligned_text 6 "${HC}${FBLE}[${RS} $1 ${HC}${FBLE}]${RS}";
+}
+
+# $1 - The stage name
+function stage_begin {
+	term_width=$(tput cols);
+	title_length=$(echo -ne "$1" | wc -m);
+	padding_length=$((term_width / 2 - title_length / 2 - 2 - 1));
+	echo -e "${HC}${FBLE}$(printf '%*s' ${padding_length} | tr ' ' '-')[${RS} $1 ${HC}${FBLE}]$(printf '%*s' ${padding_length} | tr ' ' '-')${RS}";
+	
+}
+
+# $1 - The exit code
+function stage_end {
+	display_text="${HC}${FGRN}ok${RS}"
+	if [[ $1 -ne 0 ]]; then
+		display_text="${HC}${FRED}!!${RS}";
+	fi
+	term_width=$(tput cols);
+	padding_length=$((term_width / 2 - 3 - 1));
+	echo -e "${HC}${FBLE}$(printf '%*s' ${padding_length} | tr ' ' '-')[${RS} ${display_text} ${HC}${FBLE}]$(printf '%*s' ${padding_length} | tr ' ' '-')${HC}${FBLE}";
+	
+}
+
+# $1 - The task name
+function task_begin {
+	echo -ne " ${FGRN}*${RS} $1";
+	echo -e "";
+}
+
+# $1 - Exit code
+# $2 - Error message (only displayed if the exit code isn't 0)
+function task_end {
+	cursor_up 1;
+	
+	if [[ "$1" -ne "0" ]]; then
+		echo -ne " ${FRED}*${RS} $2";
+		task_status "${HC}${FRED}!!${RS}";
+	else
+		task_status "${HC}${FGRN}ok${RS}";
+	fi
+	echo -e "";
+}
+
+# $1 - Task name
+function subtask_begin {
+	echo -ne "    ${FBLE}*${RS} $1";
+}
+
+# $1 - exit code
+# $2 - error message (only displayed if the exit code isn't 0)
+function subtask_end {
+	if [[ "$1" -ne "0" ]]; then
+		echo -ne "$2";
+		echo -e "\r    ${FRED}*${RS}";
+	else
+		echo -e "\r    ${FGRN}*${RS}";
+	fi
+}
+
+# $1 - Command name to check for
+# $2 - Whether to call subtask_begin/end
+function check_command {
+	if [ "$2" != "" ]; then
+		subtask_begin "Checking for $1";
+	fi
+	which $1 >/dev/null 2>&1; exit_code=$?
+	if [[ "${exit_code}" -ne 0 ]]; then
+		task_end ${exit_code} "Error: Couldn't locate $1. Make sure it's installed and in your path.";
+		if [ "$3" != "optional" ]; then
+			exit 2;
+		fi
+	fi
+	
+	if [ "$2" != "" ]; then
+		subtask_end 0;
+	fi
+}
+
+function tasks_run {
+	while test $# -gt 0
+	do
+		task_$1;
+		shift
+	done
+}
