blob: 52ea0713aec7ca4f13550060e828dbd8eb8416ac [file] [log] [blame]
#!/bin/sh -e
# Tool to bundle multiple C/C++ source files, inlining any includes.
#
# Note: this POSIX-compliant script is many times slower than the original bash
# implementation (due to the grep calls) but it runs and works everywhere.
#
# TODO: ROOTS and FOUND as arrays (since they fail on paths with spaces)
#
# Script released under a CC0 license.
# Common file roots
ROOTS="./"
# Files previously visited
FOUND=""
# Optional destination file (empty string to write to stdout)
DESTN=""
# Prints the script usage then exits
usage() {
echo "Usage: $0 [-r <path>] [-x <header>][-o <outfile>] infile"
echo " -r file root search paths"
echo " -x file to exclude from inlining"
echo " -o output file (otherwise stdout)"
echo "Example: $0 -r ../my/path - r ../other/path -o out.c in.c"
exit 1
}
# Tests that the grep implementation works as expected (older OSX grep fails)
test_grep() {
if ! echo '#include "foo"' | grep -Eq '^\s*#\s*include\s*".+"'; then
echo "Aborting: the grep implementation fails to parse include lines"
exit 1
fi
}
# Tests if list $1 has item $2 (returning zero on a match)
list_has_item() {
if echo "$1" | grep -Eq "(^|\s*)$2(\$|\s*)"; then
return 0
else
return 1
fi
}
# Adds a new line with the supplied arguments to $DESTN (or stdout)
write_line() {
if [ -n "$DESTN" ]; then
printf '%s\n' "$@" >> "$DESTN"
else
printf '%s\n' "$@"
fi
}
# Adds the contents of $1 with any of its includes inlined
add_file() {
# Match the path
local file=
if [ -f "$1" ]; then
file="$1"
else
for root in $ROOTS; do
if test -f "$root/$1"; then
file="$root/$1"
fi
done
fi
if [ -n "$file" ]; then
# Read the file
local line=
while IFS= read -r line; do
if echo "$line" | grep -Eq '^\s*#\s*include\s*".+"'; then
# We have an include directive so strip the (first) file
local inc=$(echo "$line" | grep -Eo '".*"' | grep -Eo '\w*(\.?\w+)+' | head -1)
if ! list_has_item "$FOUND" "$inc"; then
# And we've not previously encountered it
FOUND="$FOUND $inc"
write_line "/**** start inlining $inc ****/"
add_file "$inc"
write_line "/**** ended inlining $inc ****/"
else
write_line "/**** skipping file: $inc ****/"
fi
else
# Skip any 'pragma once' directives, otherwise write the source line
if echo "$line" | grep -Eqv '^\s*#\s*pragma\s*once\s*'; then
write_line "$line"
fi
fi
done < "$file"
else
write_line "#error Unable to find \"$1\""
fi
}
while getopts ":r:x:o:" opts; do
case $opts in
r)
ROOTS="$OPTARG $ROOTS"
;;
x)
FOUND="$OPTARG $FOUND"
;;
o)
DESTN="$OPTARG"
;;
*)
usage
;;
esac
done
shift $((OPTIND-1))
if [ -n "$1" ]; then
if [ -f "$1" ]; then
if [ -n "$DESTN" ]; then
printf "" > "$DESTN"
fi
test_grep
add_file $1
else
echo "Input file not found: \"$1\""
exit 1
fi
else
usage
fi
exit 0