Files
qt6-migration-pipeline/strip_c_comments_safe.sh
T

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)