#!/usr/bin/env python3 # Copyright (c) 2023, Cameron Youell # Copyright (c) 2025, ayeteadoe # # SPDX-License-Identifier: BSD-2-Clause import argparse from pathlib import Path def generate_header_file(output_path: Path) -> None: content = """#pragma once #include #include #include namespace URL { class PublicSuffixData { protected: PublicSuffixData(); public: PublicSuffixData(PublicSuffixData const&) = delete; PublicSuffixData& operator=(PublicSuffixData const&) = delete; static PublicSuffixData* the() { static PublicSuffixData* s_the; if (!s_the) s_the = new PublicSuffixData; return s_the; } bool is_public_suffix(StringView host); Optional get_public_suffix(StringView string); }; } """ with open(output_path, "w", encoding="utf-8") as f: f.write(content) def generate_implementation_file(input_path: Path, output_path: Path) -> None: content = """#include #include #include #include namespace URL { static constexpr auto s_public_suffixes = Array {""" reversed_lines = [] with open(input_path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if line.startswith("//") or not line: continue reversed_line = ".".join(line.split(".")[::-1]) reversed_lines.append(reversed_line) reversed_lines.sort() for item in reversed_lines: content += f'\n "{item}"sv,' content += """ }; PublicSuffixData::PublicSuffixData() { } bool PublicSuffixData::is_public_suffix(StringView host) { return binary_search(s_public_suffixes, host); } Optional PublicSuffixData::get_public_suffix(StringView string) { auto input = string.split_view('.'); input.reverse(); StringBuilder overall_search_string; StringBuilder search_string; for (auto part : input) { search_string.clear(); search_string.append(overall_search_string.string_view()); search_string.append(part); if (is_public_suffix(search_string.string_view())) { overall_search_string.append(part); overall_search_string.append('.'); continue; } search_string.clear(); search_string.append(overall_search_string.string_view()); search_string.append('.'); if (is_public_suffix(search_string.string_view())) { overall_search_string.append(part); overall_search_string.append('.'); continue; } break; } auto view = overall_search_string.string_view().split_view('.'); view.reverse(); StringBuilder return_string_builder; return_string_builder.join('.', view); if (return_string_builder.is_empty()) return Optional {}; return MUST(return_string_builder.to_string()); } } """ with open(output_path, "w", encoding="utf-8") as f: f.write(content) def main(): parser = argparse.ArgumentParser(description="Generate public suffix data files", add_help=False) parser.add_argument("-H", action="help", help="Show this help message and exit") parser.add_argument("-h", "-generated-header-path", required=True, help="Path to the header file to generate") parser.add_argument( "-c", "-generated_implementation_path", required=True, help="Path to the implementation file to generate" ) parser.add_argument("-p", "-public-suffix-list-path", required=True, help="Path to the public suffix list") args = parser.parse_args() generate_header_file(Path(args.h)) generate_implementation_file(Path(args.p), Path(args.c)) if __name__ == "__main__": main()