Skip to content

Instantly share code, notes, and snippets.

@job-gordon
Created November 1, 2025 23:06
Show Gist options
  • Select an option

  • Save job-gordon/8d708d195adc2f7ee7f3688db20b44a1 to your computer and use it in GitHub Desktop.

Select an option

Save job-gordon/8d708d195adc2f7ee7f3688db20b44a1 to your computer and use it in GitHub Desktop.
vm format convert
#!/bin/bash
# --- Bash Version Check ---
if [[ -z "${BASH_VERSINFO[0]}" || "${BASH_VERSINFO[0]}" -lt 4 ]]; then
echo "Error: This script requires Bash version 4.0 or higher." >&2
echo "You are running: $BASH_VERSION" >&2
exit 1
fi
# --- Default Variables ---
input_file=""
output_type=""
output_file=""
# --- Dynamic Data Arrays ---
# 1. Associative array to store the key-value data
declare -A vm_data
# 2. Standard array to store the *order* of the keys as they appear
declare -a headers_ordered
# --- Functions ---
# Function to show usage
usage() {
echo "Usage: $0 -t <csv|markdown|html> [-i inputfile] [-o outputfile]"
echo " -t Output type (csv, markdown, or html)"
echo " -i Optional input file (default: read from standard input)"
echo " -o Optional output file (default: print to console)"
exit 1
}
# Function to parse input (from file or stdin)
parse_input() {
while IFS= read -r line; do
# Skip empty lines or lines without a colon
if [[ -z "$line" || "$line" != *":"* ]]; then
continue
fi
# Extract key: everything before the first colon
key="${line%%:*}"
# Extract value: everything after the first colon
value="${line#*:}"
# --- Strip leading/trailing whitespace (Bash 4+ method) ---
key="${key#"${key%%[![:space:]]*}"}"
key="${key%"${key##*[![:space:]]}"}"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
if [ -n "$key" ]; then
# --- New Dynamic Header Logic ---
# Check if this key has been set before.
# We use ${vm_data[$key]+_} which is a portable way to check if a key is set,
# even if its value is empty.
if [[ -z ${vm_data[$key]+_} ]]; then
# This is a new key, add it to our ordered list
headers_ordered+=("$key")
fi
# Store or overwrite the value
vm_data["$key"]="$value"
fi
done
}
# Function to generate CSV output
generate_csv() {
# 1. Print Header (from our dynamic array)
(IFS=,; echo "${headers_ordered[*]}")
# 2. Build data row
local data_row=()
for header in "${headers_ordered[@]}"; do
data_row+=("${vm_data[$header]}")
done
# 3. Print data row
(IFS=,; echo "${data_row[*]}")
}
# Function to generate Markdown table output
generate_markdown() {
# 1. Print Header (from our dynamic array)
printf "|"
for header in "${headers_ordered[@]}"; do
printf " %s |" "$header"
done
printf "\n"
# 2. Print Separator
printf "|"
for header in "${headers_ordered[@]}"; do
printf " %s |" "$(seq -s'-' 1 ${#header} | tr -d '0-9')"
done
printf "\n"
# 3. Print Data
printf "|"
for header in "${headers_ordered[@]}"; do
local value="${vm_data[$header]}"
# Backslash-escape colons to prevent Markdown emoji rendering
local escaped_value="${value//:/\\:}"
printf " %s |" "$escaped_value"
done
printf "\n"
}
# Function to generate HTML table output
generate_html() {
echo "<table>"
# 1. Header Row (from our dynamic array)
echo " <thead>"
echo " <tr>"
for header in "${headers_ordered[@]}"; do
echo " <th>$header</th>"
done
echo " </tr>"
echo " </thead>"
# 2. Body Row
echo " <tbody>"
echo " <tr>"
for header in "${headers_ordered[@]}"; do
local value="${vm_data[$header]}"
if [ -z "$value" ]; then
echo " <td>&nbsp;</td>"
else
echo " <td>$value</td>"
fi
done
echo " </tr>"
echo " </tbody>"
echo "</table>"
}
# --- Argument Parsing ---
while getopts "i:t:o:" opt; do
case $opt in
i) input_file="$OPTARG" ;;
t) output_type="$OPTARG" ;;
o) output_file="$OPTARG" ;;
\?) usage ;;
esac
done
# --- Validation ---
if [ -n "$input_file" ] && [ ! -r "$input_file" ]; then
echo "Error: Input file (-i) '$input_file' is not readable." >&2
usage
fi
if [[ "$output_type" != "csv" && "$output_type" != "markdown" && "$output_type" != "html" ]]; then
echo "Error: Invalid output type (-t). Must be 'csv', 'markdown', or 'html'." >&2
usage
fi
# --- Main Logic ---
# 1. Read and parse the input. This will populate
# both 'vm_data' and 'headers_ordered' arrays.
if [ -n "$input_file" ]; then
parse_input < "$input_file"
else
parse_input
fi
# 2. Set up output redirection
if [ -n "$output_file" ]; then
exec > "$output_file"
fi
# 3. Call the correct generation function
case "$output_type" in
csv)
generate_csv
;;
markdown)
generate_markdown
;;
html)
generate_html
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment