Files
cHTTP/misc/amalg.py
T
2025-07-20 21:19:58 +02:00

235 lines
7.8 KiB
Python

#!/usr/bin/env python3
"""
AmalgamationBuilder - Implementation with improved #line support
"""
import re
import os
from typing import Set
class AmalgamationBuilder:
def __init__(self):
self.body = ""
self.local_includes: Set[str] = set() # These should deduplicate items
self.global_includes: Set[str] = set()
def add(self, filepath: str):
"""Add a file to the amalgamation"""
try:
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
except FileNotFoundError:
print(f"Warning: File '{filepath}' not found, skipping")
return
original_line_number = 1
skipped_lines = 0
first_content_line = True
self.body += "//////////////////////////////////////////////////////////////////////\n"
self.body += "// " + filepath + "\n"
self.body += "//////////////////////////////////////////////////////////////////////\n"
self.body += "\n"
for line in lines:
# Check if the line is an include
if self._is_include_line(line):
include_info = self._extract_include_info(line)
if include_info:
filename, uses_angular_brackets = include_info
if uses_angular_brackets:
self.global_includes.add(filename)
else:
self.local_includes.add(filename)
original_line_number += 1
skipped_lines += 1
continue
# Check if the line is part of a header guard
if self._is_header_guard_line(line):
original_line_number += 1
skipped_lines += 1
continue
# Skip empty lines at the beginning of files
if first_content_line and line.strip() == '':
original_line_number += 1
skipped_lines += 1
continue
# Add #line directive when starting a new file or after skipping significant content
if first_content_line or skipped_lines > 0:
# Only add #line if we skipped lines or it's a new file
# self.body += f'#line {original_line_number} "{filepath}"\n'
first_content_line = False
skipped_lines = 0
# Append the current line to body
self.body += line
original_line_number += 1
def _is_include_line(self, line: str) -> bool:
"""Check if a line is an #include directive"""
# Skip obviously non-include lines for performance
if '#include' not in line:
return False
# Check if this line is commented out
if self._is_line_commented(line):
return False
# Regex to match include pattern
include_pattern = re.compile(r'^\s*#\s*include\s*[<"][^>"]+[>"]')
return bool(include_pattern.match(line.strip()))
def _extract_include_info(self, line: str) -> tuple[str, bool] | None:
"""Extract filename and bracket type from include line"""
# Pattern to capture include details
include_pattern = re.compile(r'^\s*#\s*include\s*([<"])([^>"]+)([>"])')
match = include_pattern.match(line.strip())
if match:
open_delim = match.group(1)
filename = match.group(2)
close_delim = match.group(3)
# Validate matching delimiters
if (open_delim == '<' and close_delim == '>') or \
(open_delim == '"' and close_delim == '"'):
uses_angular_brackets = (open_delim == '<')
return filename, uses_angular_brackets
return None
def _is_header_guard_line(self, line: str) -> bool:
"""Check if a line is part of a header guard (assumes _INCLUDED suffix)"""
stripped = line.strip()
# Simplified header guard patterns for _INCLUDED suffix
header_guard_patterns = [
re.compile(r'#ifndef\s+\w+_INCLUDED'),
re.compile(r'#define\s+\w+_INCLUDED'),
re.compile(r'#endif\s*//.*_INCLUDED'),
re.compile(r'#endif\s*/\*.*_INCLUDED.*\*/'),
]
return any(pattern.match(stripped) for pattern in header_guard_patterns)
def _is_line_commented(self, line: str) -> bool:
"""Basic check if line is commented out"""
include_pos = line.find('#include')
if include_pos == -1:
return False
# Check for // comment before #include
comment_pos = line.find('//')
if comment_pos != -1 and comment_pos < include_pos:
return True
# Check for /* comment before #include (basic case)
before_include = line[:include_pos]
if '/*' in before_include and '*/' not in before_include:
return True
return False
def result(self) -> str:
"""Generate the final amalgamated result"""
result = ""
# Write all global includes at the top
for item in sorted(self.global_includes):
result += f'#include <{item}>\n'
if self.global_includes:
result += "\n" # Add spacing after includes
# Add the body content
result += self.body
return result
def main():
"""Example usage of AmalgamationBuilder"""
# Build the header
print("Building header...")
header_builder = AmalgamationBuilder()
header_files = [
"src/basic.h",
"src/parse.h",
"src/engine.h",
"src/client.h",
"src/server.h",
"src/cert.h",
"src/router.h"
]
for file in header_files:
header_builder.add(file)
header = header_builder.result()
# Build the source
print("Building source...")
source_builder = AmalgamationBuilder()
source_files = [
"src/cert.h",
"src/socket.h",
"src/basic.c",
"src/parse.c",
"src/engine.c",
"src/socket.c",
"src/client.c",
"src/server.c",
"src/router.c"
]
for file in source_files:
source_builder.add(file)
source = source_builder.result()
# Write results
print("Writing chttp.h...")
with open("chttp.h", 'w', encoding='utf-8') as f:
f.write('/*\n')
f.write(' * cHTTP Library - Amalgamated Header\n')
f.write(' * Generated automatically - do not edit manually\n')
f.write(' */\n\n')
f.write('#ifndef HTTP_AMALGAMATION_H\n')
f.write('#define HTTP_AMALGAMATION_H\n\n')
f.write('#ifdef __cplusplus\n')
f.write('extern "C" {\n')
f.write('#endif\n\n')
f.write(header)
f.write('\n#ifdef __cplusplus\n')
f.write('}\n')
f.write('#endif\n\n')
f.write('#endif /* HTTP_AMALGAMATION_H */\n')
print("Writing chttp.c...")
with open("chttp.c", 'w', encoding='utf-8') as f:
f.write('/*\n')
f.write(' * cHTTP Library - Amalgamated Source\n')
f.write(' * Generated automatically - do not edit manually\n')
f.write(' */\n\n')
f.write('#include "chttp.h"\n\n')
f.write(source)
print("Amalgamation complete!")
print(f"Header: {len(header.splitlines())} lines")
print(f"Source: {len(source.splitlines())} lines")
# Show some statistics
print(f"\nHeader global includes: {len(header_builder.global_includes)}")
print(f"Source global includes: {len(source_builder.global_includes)}")
print(f"Local includes found: {len(header_builder.local_includes | source_builder.local_includes)}")
if __name__ == "__main__":
main()