Last active
February 28, 2025 11:54
-
-
Save lovasoa/4e8643264797d7087ff285ea57d40097 to your computer and use it in GitHub Desktop.
convert sql block comments (`/*`) to standard line comments (`--`) https://sql-comments-updater.netlify.app/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
SQL Block Comment to Line Comment Converter | |
This script processes SQL files or input text and replaces all block comments (/* */) | |
with standard SQL line comments (--), correctly handling nested block comments. | |
""" | |
import re | |
import sys | |
import argparse | |
from pathlib import Path | |
def replace_block_comments(sql_content: str) -> str: | |
""" | |
Replace SQL block comments (/* */) with line comments (--), handling nested comments. | |
Args: | |
sql_content (str): SQL content with block comments | |
Returns: | |
str: SQL content with block comments replaced by line comments | |
Examples: | |
>>> replace_block_comments("SELECT 1;") | |
'SELECT 1;' | |
>>> replace_block_comments("SELECT /* comment */ 1;") | |
'SELECT \\n-- comment\\n 1;' | |
>>> replace_block_comments("SELECT /* multi\\nline\\ncomment */ 1;") | |
'SELECT \\n-- multi\\n-- line\\n-- comment\\n 1;' | |
>>> replace_block_comments("/* Starts with comment */ SELECT 1;") | |
'\\n-- Starts with comment\\n SELECT 1;' | |
>>> replace_block_comments("SELECT 1 /* Ends with comment */;") | |
'SELECT 1 \\n-- Ends with comment\\n;' | |
>>> replace_block_comments("SELECT /* level1 \\n /* level2 comment */\\n */ 1;") | |
'SELECT \\n-- level1\\n-- -- level2 comment\\n 1;' | |
>>> replace_block_comments("SELECT '/* not a comment */' FROM table;") | |
"SELECT '/* not a comment */' FROM table;" | |
""" | |
output: list[str] = [] | |
i = 0 | |
length = len(sql_content) | |
in_block_comment = 0 # Tracks nesting level | |
comment_buffer: list[str] = [] | |
inside_string = False | |
string_delimiter = '' | |
comment_nesting_levels: list[int] = [] # Track nesting level for each character in comment | |
while i < length: | |
if inside_string: | |
if sql_content[i] == string_delimiter: | |
inside_string = False | |
output.append(sql_content[i]) | |
elif sql_content[i] in "'\"": | |
inside_string = True | |
string_delimiter = sql_content[i] | |
output.append(sql_content[i]) | |
elif sql_content[i:i+2] == "/*": | |
if in_block_comment == 0: | |
comment_buffer = [] | |
comment_nesting_levels = [] | |
output.append("\n") | |
in_block_comment += 1 | |
i += 1 # Skip next character too | |
elif sql_content[i:i+2] == "*/" and in_block_comment > 0: | |
in_block_comment -= 1 | |
i += 1 # Skip next character too | |
if in_block_comment == 0: | |
# Convert buffered comment into line comments | |
comment_text = "".join(comment_buffer).strip() | |
if comment_text: | |
lines = comment_text.split("\n") | |
for line_idx, line in enumerate(lines): | |
stripped_line = line.strip() | |
if stripped_line: | |
# Find the nesting level for this line | |
line_start_idx = sum(len(l) + 1 for l in lines[:line_idx]) | |
line_nesting = max([1] + [level for idx, level in enumerate(comment_nesting_levels) | |
if line_start_idx <= idx < line_start_idx + len(line)]) | |
# Add appropriate number of dashes based on nesting level | |
leading_dash = "-- " * line_nesting | |
output.append(f"{leading_dash}{stripped_line}\n") | |
comment_buffer.clear() | |
comment_nesting_levels.clear() | |
elif in_block_comment > 0: | |
comment_buffer.append(sql_content[i]) | |
comment_nesting_levels.append(in_block_comment) | |
else: | |
output.append(sql_content[i]) | |
i += 1 | |
result = "".join(output) | |
return result | |
def process_sql_file(file_path: Path) -> bool: | |
"""Process a single SQL file, replacing block comments with line comments.""" | |
try: | |
with open(file_path, 'r', encoding='utf-8') as f: | |
content = f.read() | |
new_content = replace_block_comments(content) | |
if new_content != content: | |
with open(file_path, 'w', encoding='utf-8') as f: | |
f.write(new_content) | |
print(f"Updated: {file_path}") | |
return True | |
except Exception as e: | |
print(f"Error processing {file_path}: {e}") | |
return False | |
def process_directory(directory_path: Path) -> tuple[int, int]: | |
"""Recursively process all SQL files in a directory.""" | |
files_processed = 0 | |
files_modified = 0 | |
for sql_file in directory_path.rglob('*.sql'): | |
files_processed += 1 | |
if process_sql_file(sql_file): | |
files_modified += 1 | |
return files_processed, files_modified | |
def main(): | |
"""Main function to parse arguments and process SQL files.""" | |
parser = argparse.ArgumentParser( | |
description='Convert SQL block comments to line comments in .sql files.' | |
) | |
parser.add_argument('directory', help='Directory to recursively search for SQL files', type=Path) | |
parser.add_argument('--test', action='store_true', help='Run doctests instead of processing files') | |
args = parser.parse_args() | |
if args.test: | |
import doctest | |
doctest.testmod(verbose=True) | |
return | |
directory = args.directory | |
if not directory.is_dir(): | |
print(f"Error: {directory} is not a valid directory.") | |
sys.exit(1) | |
print(f"Starting SQL comment conversion in {directory}...") | |
files_processed, files_modified = process_directory(directory) | |
print(f"\nSummary:\n Files processed: {files_processed}\n Files modified: {files_modified}") | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment