mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2025-09-05 19:44:13 +08:00
265 lines
8.1 KiB
Python
265 lines
8.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Stalwart SEL code remover
|
|
|
|
This script removes SEL code from the Stalwart codebase by:
|
|
1. Removing entire .rs files that contain "SPDX-License-Identifier: LicenseRef-SEL" in their first comment
|
|
2. Removing SEL snippets marked with SPDX-SnippetBegin/End from mixed files
|
|
|
|
Usage: python ossify.py <stalwart_repository>/crates
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import re
|
|
import argparse
|
|
from pathlib import Path
|
|
from typing import List, Tuple, Optional
|
|
|
|
|
|
def find_first_comment_block(content: str) -> Optional[str]:
|
|
"""
|
|
Find the first comment block in a Rust file.
|
|
Returns the comment content or None if no comment block is found.
|
|
"""
|
|
# Remove leading whitespace and find the first comment
|
|
lines = content.strip().split('\n')
|
|
|
|
if not lines:
|
|
return None
|
|
|
|
first_line = lines[0].strip()
|
|
|
|
# Check for block comment starting with /*
|
|
if first_line.startswith('/*'):
|
|
comment_lines = []
|
|
in_comment = True
|
|
|
|
for line in lines:
|
|
if in_comment:
|
|
comment_lines.append(line)
|
|
if '*/' in line:
|
|
break
|
|
|
|
return '\n'.join(comment_lines)
|
|
|
|
# Check for line comments starting with //
|
|
elif first_line.startswith('//'):
|
|
comment_lines = []
|
|
|
|
for line in lines:
|
|
stripped = line.strip()
|
|
if stripped.startswith('//'):
|
|
comment_lines.append(line)
|
|
elif stripped == '':
|
|
comment_lines.append(line) # Keep empty lines within comment block
|
|
else:
|
|
break # Stop at first non-comment, non-empty line
|
|
|
|
return '\n'.join(comment_lines)
|
|
|
|
return None
|
|
|
|
|
|
def should_remove_file(file_path: str) -> bool:
|
|
"""
|
|
Check if a .rs file should be completely removed based on its first comment.
|
|
Returns True if the file contains "SPDX-License-Identifier: LicenseRef-SEL" in the first comment.
|
|
"""
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
first_comment = find_first_comment_block(content)
|
|
if first_comment and 'SPDX-License-Identifier: LicenseRef-SEL' in first_comment:
|
|
return True
|
|
|
|
except Exception as e:
|
|
print(f"Error reading file {file_path}: {e}")
|
|
|
|
return False
|
|
|
|
|
|
def remove_proprietary_snippets(content: str) -> Tuple[str, int]:
|
|
"""
|
|
Remove proprietary snippets from file content.
|
|
Returns tuple of (modified_content, number_of_snippets_removed)
|
|
"""
|
|
snippets_removed = 0
|
|
|
|
# Pattern to match SPDX snippets that contain LicenseRef-SEL
|
|
# We look for SPDX-SnippetBegin, then check if the snippet contains LicenseRef-SEL,
|
|
# and if so, remove everything until SPDX-SnippetEnd
|
|
|
|
lines = content.split('\n')
|
|
result_lines = []
|
|
i = 0
|
|
|
|
while i < len(lines):
|
|
line = lines[i]
|
|
|
|
# Check if this line starts a snippet
|
|
if '// SPDX-SnippetBegin' in line:
|
|
# Look ahead to see if this snippet contains LicenseRef-SEL
|
|
snippet_start = i
|
|
snippet_lines = []
|
|
j = i
|
|
|
|
# Collect the snippet lines until we find SnippetEnd or reach end of file
|
|
while j < len(lines):
|
|
snippet_lines.append(lines[j])
|
|
if '// SPDX-SnippetEnd' in lines[j]:
|
|
break
|
|
j += 1
|
|
|
|
# Check if this snippet contains LicenseRef-SEL
|
|
snippet_content = '\n'.join(snippet_lines)
|
|
if 'SPDX-License-Identifier: LicenseRef-SEL' in snippet_content:
|
|
# Remove this snippet
|
|
snippets_removed += 1
|
|
i = j + 1 # Skip past the SnippetEnd line
|
|
continue
|
|
else:
|
|
# Keep this snippet as it's not proprietary
|
|
result_lines.append(line)
|
|
i += 1
|
|
else:
|
|
result_lines.append(line)
|
|
i += 1
|
|
|
|
return '\n'.join(result_lines), snippets_removed
|
|
|
|
|
|
def process_rust_file(file_path: str, dry_run: bool = False) -> dict:
|
|
"""
|
|
Process a single Rust file, removing proprietary content.
|
|
Returns a dictionary with processing results.
|
|
"""
|
|
result = {
|
|
'file': file_path,
|
|
'action': 'none',
|
|
'snippets_removed': 0,
|
|
'error': None
|
|
}
|
|
|
|
try:
|
|
# Check if the entire file should be removed
|
|
if should_remove_file(file_path):
|
|
result['action'] = 'file_removed'
|
|
if not dry_run:
|
|
os.remove(file_path)
|
|
return result
|
|
|
|
# Process snippets in the file
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
original_content = f.read()
|
|
|
|
modified_content, snippets_removed = remove_proprietary_snippets(original_content)
|
|
|
|
if snippets_removed > 0:
|
|
result['action'] = 'snippets_removed'
|
|
result['snippets_removed'] = snippets_removed
|
|
|
|
if not dry_run:
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
f.write(modified_content)
|
|
|
|
except Exception as e:
|
|
result['error'] = str(e)
|
|
|
|
return result
|
|
|
|
|
|
def find_rust_files(directory: str) -> List[str]:
|
|
"""Find all .rs files in the given directory recursively."""
|
|
rust_files = []
|
|
|
|
for root, dirs, files in os.walk(directory):
|
|
for file in files:
|
|
if file.endswith('.rs'):
|
|
rust_files.append(os.path.join(root, file))
|
|
|
|
return rust_files
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='Remove Enterprise licensed code from Stalwart codebase'
|
|
)
|
|
parser.add_argument(
|
|
'directory',
|
|
help='Directory containing Stalwart code to process'
|
|
)
|
|
parser.add_argument(
|
|
'--dry-run',
|
|
action='store_true',
|
|
help='Show what would be done without making changes'
|
|
)
|
|
parser.add_argument(
|
|
'--verbose',
|
|
action='store_true',
|
|
help='Show detailed output for each file'
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
if not os.path.isdir(args.directory):
|
|
print(f"Error: {args.directory} is not a valid directory")
|
|
sys.exit(1)
|
|
|
|
print(f"Processing Rust files in: {args.directory}")
|
|
if args.dry_run:
|
|
print("DRY RUN MODE - No changes will be made")
|
|
print()
|
|
|
|
rust_files = find_rust_files(args.directory)
|
|
|
|
if not rust_files:
|
|
print("No .rs files found in the specified directory")
|
|
return
|
|
|
|
print(f"Found {len(rust_files)} Rust files")
|
|
print()
|
|
|
|
files_removed = 0
|
|
files_with_snippets_removed = 0
|
|
total_snippets_removed = 0
|
|
errors = []
|
|
|
|
for file_path in rust_files:
|
|
result = process_rust_file(file_path, args.dry_run)
|
|
|
|
if result['error']:
|
|
errors.append(f"{file_path}: {result['error']}")
|
|
continue
|
|
|
|
if result['action'] == 'file_removed':
|
|
files_removed += 1
|
|
if args.verbose or args.dry_run:
|
|
action_text = "Would remove" if args.dry_run else "Removed"
|
|
print(f"{action_text} file: {file_path}")
|
|
|
|
elif result['action'] == 'snippets_removed':
|
|
files_with_snippets_removed += 1
|
|
total_snippets_removed += result['snippets_removed']
|
|
if args.verbose or args.dry_run:
|
|
action_text = "Would remove" if args.dry_run else "Removed"
|
|
print(f"{action_text} {result['snippets_removed']} snippet(s) from: {file_path}")
|
|
|
|
# Summary
|
|
print("\nSummary:")
|
|
action_text = "Would be" if args.dry_run else "Were"
|
|
print(f"- {files_removed} files {action_text.lower()} completely removed")
|
|
print(f"- {total_snippets_removed} proprietary snippets {action_text.lower()} removed from {files_with_snippets_removed} files")
|
|
|
|
if errors:
|
|
print(f"- {len(errors)} errors occurred:")
|
|
for error in errors:
|
|
print(f" {error}")
|
|
|
|
if args.dry_run:
|
|
print("\nRun without --dry-run to apply changes")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|