455 lines
11 KiB
Bash
Executable File
455 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
BACKUP_EXT=".bak"
|
|
DRY_RUN=0
|
|
WITH_CPP=0
|
|
WITH_BASH=0
|
|
WITH_PYTHON=0
|
|
WITH_CMAKE=0
|
|
TARGET_DIR="."
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage:
|
|
strip_c_comments_safe.sh [OPTIONEN] [VERZEICHNIS]
|
|
|
|
Optionen:
|
|
--dry-run Nur Vorschau anzeigen, keine Dateien ändern
|
|
--backup-ext EXT Backup-Endung setzen, Standard: .bak
|
|
--cpp Zusätzlich auch C++-Dateien verarbeiten
|
|
--bash Zusätzlich auch Bash-Skripte (*.sh) verarbeiten
|
|
--python Zusätzlich auch Python-Skripte (*.py) verarbeiten
|
|
--cmake Zusätzlich auch CMake-Skripte (*.cmake) verarbeiten
|
|
-h, --help Hilfe anzeigen
|
|
|
|
Argumente:
|
|
VERZEICHNIS Zielverzeichnis, Standard: aktuelles Verzeichnis
|
|
|
|
Beispiele:
|
|
./strip_c_comments_safe.sh
|
|
./strip_c_comments_safe.sh src
|
|
./strip_c_comments_safe.sh --dry-run include
|
|
./strip_c_comments_safe.sh --cpp src
|
|
./strip_c_comments_safe.sh --bash scripts
|
|
./strip_c_comments_safe.sh --python tools
|
|
./strip_c_comments_safe.sh --cmake cmake
|
|
./strip_c_comments_safe.sh --cpp --backup-ext .orig ./project
|
|
EOF
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--dry-run)
|
|
DRY_RUN=1
|
|
shift
|
|
;;
|
|
--backup-ext)
|
|
BACKUP_EXT="${2:-}"
|
|
if [[ -z "$BACKUP_EXT" ]]; then
|
|
echo "Fehler: --backup-ext braucht ein Argument" >&2
|
|
exit 1
|
|
fi
|
|
shift 2
|
|
;;
|
|
--cpp)
|
|
WITH_CPP=1
|
|
shift
|
|
;;
|
|
--bash)
|
|
WITH_BASH=1
|
|
shift
|
|
;;
|
|
--python)
|
|
WITH_PYTHON=1
|
|
shift
|
|
;;
|
|
--cmake)
|
|
WITH_CMAKE=1
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
-*)
|
|
echo "Unbekannte Option: $1" >&2
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
*)
|
|
if [[ "$TARGET_DIR" != "." ]]; then
|
|
echo "Fehler: Es darf nur ein Verzeichnis angegeben werden" >&2
|
|
usage >&2
|
|
exit 1
|
|
fi
|
|
TARGET_DIR="$1"
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ $# -gt 0 ]]; then
|
|
if [[ "$TARGET_DIR" != "." ]]; then
|
|
echo "Fehler: Es darf nur ein Verzeichnis angegeben werden" >&2
|
|
usage >&2
|
|
exit 1
|
|
fi
|
|
TARGET_DIR="$1"
|
|
shift
|
|
fi
|
|
|
|
if [[ ! -d "$TARGET_DIR" ]]; then
|
|
echo "Fehler: Verzeichnis nicht gefunden: $TARGET_DIR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
TARGET_DIR=$(cd -- "$TARGET_DIR" && pwd)
|
|
|
|
process_file() {
|
|
local mode="$1"
|
|
local file="$2"
|
|
local tmp
|
|
tmp="$(mktemp)"
|
|
|
|
if [[ "$mode" == "python" ]]; then
|
|
python3 - "$file" > "$tmp" <<'PY'
|
|
import io
|
|
import re
|
|
import sys
|
|
import tokenize
|
|
|
|
path = sys.argv[1]
|
|
with open(path, "rb") as f:
|
|
data = f.read()
|
|
|
|
tokens = []
|
|
for tok in tokenize.tokenize(io.BytesIO(data).readline):
|
|
if tok.type == tokenize.ENCODING:
|
|
continue
|
|
if tok.type == tokenize.COMMENT:
|
|
line = tok.start[0]
|
|
text = tok.string
|
|
keep = (line == 1 and text.startswith("#!")) or (
|
|
line <= 2 and re.search(r"coding[:=]\s*[-\w.]+", text)
|
|
)
|
|
if not keep:
|
|
continue
|
|
tokens.append(tok)
|
|
|
|
result = tokenize.untokenize(tokens)
|
|
if isinstance(result, str):
|
|
sys.stdout.write(result)
|
|
else:
|
|
sys.stdout.buffer.write(result)
|
|
PY
|
|
else
|
|
COMMENT_MODE="$mode" perl -0777 -pe '
|
|
my $mode = $ENV{COMMENT_MODE} // "c";
|
|
my $out = "";
|
|
my $i = 0;
|
|
my $len = length($_);
|
|
my $state = "code";
|
|
my $line = 1;
|
|
|
|
sub is_ident {
|
|
my ($ch) = @_;
|
|
return defined($ch) && $ch =~ /[A-Za-z0-9_]/;
|
|
}
|
|
|
|
sub skip_line_comment {
|
|
my ($s, $idx, $total) = @_;
|
|
while ($idx < $total && substr($s, $idx, 1) ne "\n") {
|
|
$idx++;
|
|
}
|
|
return $idx;
|
|
}
|
|
|
|
while ($i < $len) {
|
|
my $c = substr($_, $i, 1);
|
|
my $c2 = $i + 1 < $len ? substr($_, $i, 2) : "";
|
|
|
|
if ($mode eq "c") {
|
|
if ($state eq "code") {
|
|
if ($c2 eq "/*") {
|
|
my $prev = length($out) ? substr($out, -1, 1) : "";
|
|
$i += 2;
|
|
|
|
my $saw_nl = 0;
|
|
while ($i < $len) {
|
|
my $d = substr($_, $i, 1);
|
|
my $d2 = $i + 1 < $len ? substr($_, $i, 2) : "";
|
|
$saw_nl = 1 if $d eq "\n";
|
|
if ($d2 eq "*/") {
|
|
$i += 2;
|
|
last;
|
|
}
|
|
$i++;
|
|
}
|
|
|
|
my $next = $i < $len ? substr($_, $i, 1) : "";
|
|
|
|
if ($saw_nl) {
|
|
$out .= "\n";
|
|
$line++;
|
|
} elsif (is_ident($prev) && is_ident($next)) {
|
|
$out .= " ";
|
|
}
|
|
|
|
next;
|
|
}
|
|
elsif ($c2 eq "//") {
|
|
my $prev = length($out) ? substr($out, -1, 1) : "";
|
|
$i += 2;
|
|
$i = skip_line_comment($_, $i, $len);
|
|
my $next = $i < $len ? substr($_, $i, 1) : "";
|
|
|
|
if ($i < $len && $next eq "\n") {
|
|
$out .= "\n";
|
|
$line++;
|
|
$i++;
|
|
} elsif (is_ident($prev)) {
|
|
$out .= " ";
|
|
}
|
|
|
|
next;
|
|
}
|
|
elsif ($c eq "\"") {
|
|
$out .= $c;
|
|
$i++;
|
|
$state = "string";
|
|
next;
|
|
}
|
|
elsif ($c eq "'\''") {
|
|
$out .= $c;
|
|
$i++;
|
|
$state = "char";
|
|
next;
|
|
}
|
|
else {
|
|
$out .= $c;
|
|
$line++ if $c eq "\n";
|
|
$i++;
|
|
next;
|
|
}
|
|
}
|
|
elsif ($state eq "string") {
|
|
if ($c eq "\\") {
|
|
if ($i + 1 < $len) {
|
|
my $pair = substr($_, $i, 2);
|
|
$out .= $pair;
|
|
$line++ if $pair =~ /\n/;
|
|
$i += 2;
|
|
} else {
|
|
$out .= $c;
|
|
$i++;
|
|
}
|
|
next;
|
|
}
|
|
elsif ($c eq "\"") {
|
|
$out .= $c;
|
|
$i++;
|
|
$state = "code";
|
|
next;
|
|
}
|
|
else {
|
|
$out .= $c;
|
|
$line++ if $c eq "\n";
|
|
$i++;
|
|
next;
|
|
}
|
|
}
|
|
elsif ($state eq "char") {
|
|
if ($c eq "\\") {
|
|
if ($i + 1 < $len) {
|
|
my $pair = substr($_, $i, 2);
|
|
$out .= $pair;
|
|
$line++ if $pair =~ /\n/;
|
|
$i += 2;
|
|
} else {
|
|
$out .= $c;
|
|
$i++;
|
|
}
|
|
next;
|
|
}
|
|
elsif ($c eq "'\''") {
|
|
$out .= $c;
|
|
$i++;
|
|
$state = "code";
|
|
next;
|
|
}
|
|
else {
|
|
$out .= $c;
|
|
$line++ if $c eq "\n";
|
|
$i++;
|
|
next;
|
|
}
|
|
}
|
|
} else {
|
|
if ($state eq "code") {
|
|
if ($c eq "\n") {
|
|
$out .= $c;
|
|
$line++;
|
|
$i++;
|
|
next;
|
|
}
|
|
|
|
if ($c eq "#") {
|
|
my $bol = ($i == 0 || substr($_, $i - 1, 1) eq "\n") ? 1 : 0;
|
|
my $prev = length($out) ? substr($out, -1, 1) : "";
|
|
my $starts_comment = 0;
|
|
|
|
if ($line == 1 && $bol && $i + 1 < $len && substr($_, $i, 2) eq "#!") {
|
|
$starts_comment = 0;
|
|
} elsif ($bol || $prev =~ /[\s;]/) {
|
|
$starts_comment = 1;
|
|
}
|
|
|
|
if ($starts_comment) {
|
|
$i = skip_line_comment($_, $i, $len);
|
|
next;
|
|
}
|
|
}
|
|
|
|
if ($c eq "'\''") {
|
|
$out .= $c;
|
|
$i++;
|
|
$state = "single";
|
|
next;
|
|
}
|
|
elsif ($c eq "\"") {
|
|
$out .= $c;
|
|
$i++;
|
|
$state = "double";
|
|
next;
|
|
}
|
|
elsif ($c eq "\\") {
|
|
if ($i + 1 < $len) {
|
|
my $pair = substr($_, $i, 2);
|
|
$out .= $pair;
|
|
$line++ if $pair =~ /\n/;
|
|
$i += 2;
|
|
} else {
|
|
$out .= $c;
|
|
$i++;
|
|
}
|
|
next;
|
|
}
|
|
else {
|
|
$out .= $c;
|
|
$i++;
|
|
next;
|
|
}
|
|
}
|
|
elsif ($state eq "single") {
|
|
$out .= $c;
|
|
$i++;
|
|
$state = "code" if $c eq "'\''";
|
|
next;
|
|
}
|
|
elsif ($state eq "double") {
|
|
if ($c eq "\\") {
|
|
if ($i + 1 < $len) {
|
|
my $pair = substr($_, $i, 2);
|
|
$out .= $pair;
|
|
$line++ if $pair =~ /\n/;
|
|
$i += 2;
|
|
} else {
|
|
$out .= $c;
|
|
$i++;
|
|
}
|
|
next;
|
|
}
|
|
$out .= $c;
|
|
$line++ if $c eq "\n";
|
|
$i++;
|
|
$state = "code" if $c eq "\"";
|
|
next;
|
|
}
|
|
}
|
|
}
|
|
|
|
$_ = $out;
|
|
' "$file" > "$tmp"
|
|
fi
|
|
|
|
if cmp -s "$file" "$tmp"; then
|
|
rm -f "$tmp"
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
echo "=== $file ==="
|
|
diff -u -- "$file" "$tmp" || true
|
|
rm -f "$tmp"
|
|
else
|
|
cp -p -- "$file" "$file$BACKUP_EXT"
|
|
cat -- "$tmp" > "$file"
|
|
rm -f "$tmp"
|
|
echo "Geändert: $file (Backup: $file$BACKUP_EXT)"
|
|
fi
|
|
}
|
|
|
|
detect_mode() {
|
|
local file="$1"
|
|
case "$file" in
|
|
*.c|*.h|*.cpp|*.cxx|*.cc|*.hpp|*.hxx|*.hh)
|
|
echo "c"
|
|
;;
|
|
*.sh)
|
|
echo "bash"
|
|
;;
|
|
*.py)
|
|
echo "python"
|
|
;;
|
|
*.cmake)
|
|
echo "bash"
|
|
;;
|
|
*)
|
|
echo ""
|
|
;;
|
|
esac
|
|
}
|
|
|
|
find_expr=()
|
|
find_expr+=( -name "*.c" -o -name "*.h" )
|
|
|
|
if [[ "$WITH_CPP" -eq 1 ]]; then
|
|
find_expr+=( -o -name "*.cpp" -o -name "*.cxx" -o -name "*.cc" -o -name "*.hpp" -o -name "*.hxx" -o -name "*.hh" )
|
|
fi
|
|
|
|
if [[ "$WITH_BASH" -eq 1 ]]; then
|
|
find_expr+=( -o -name "*.sh" )
|
|
fi
|
|
|
|
if [[ "$WITH_PYTHON" -eq 1 ]]; then
|
|
find_expr+=( -o -name "*.py" )
|
|
fi
|
|
|
|
if [[ "$WITH_CMAKE" -eq 1 ]]; then
|
|
find_expr+=( -o -name "*.cmake" )
|
|
fi
|
|
|
|
declare -A seen_files=()
|
|
|
|
while IFS= read -r -d '' file; do
|
|
real_file="$file"
|
|
if [[ -L "$file" ]]; then
|
|
target=$(readlink "$file")
|
|
if [[ "$target" != /* ]]; then
|
|
dir="$(cd "$(dirname "$file")" && pwd -P)" || continue
|
|
target="$dir/$target"
|
|
fi
|
|
[[ -L "$target" ]] && continue
|
|
[[ ! -f "$target" ]] && continue
|
|
real_file="$target"
|
|
fi
|
|
[[ -n "${seen_files[$real_file]+x}" ]] && continue
|
|
seen_files[$real_file]=1
|
|
mode="$(detect_mode "$real_file")"
|
|
[[ -n "$mode" ]] && process_file "$mode" "$real_file"
|
|
done < <(find "$TARGET_DIR" \( -type f -o -type l \) \( "${find_expr[@]}" \) -print0) |