def main()

in scripts/generate_type_stubs_and_docs.py [0:0]


def main():
    parser = argparse.ArgumentParser(
        description="Generate type stub files (.pyi) and Sphinx documentation for Pedalboard."
    )
    parser.add_argument(
        "--docs-output-dir", default="docs", help="Output directory for documentation HTML files."
    )
    parser.add_argument(
        "--docs-input-dir",
        default=os.path.join("docs", "source"),
        help="Input directory for Sphinx.",
    )
    parser.add_argument(
        "--skip-regenerating-type-hints",
        action="store_true",
        help="If set, don't bother regenerating or reprocessing type hint files.",
    )
    parser.add_argument(
        "--check",
        action="store_true",
        help=(
            "If set, compare the existing files with those that would have been generated if this"
            " script were re-run."
        ),
    )
    parser.add_argument(
        "--skip-comparing",
        nargs="*",
        default=["*.js", "*.css"],
        help=(
            "If set and if --check is passed, the provided filenames (including '*' globs) will be"
            " ignored when comparing expected file contents against actual file contents."
        ),
    )
    args = parser.parse_args()

    patch_mypy_stubtest()
    patch_pybind11_stubgen()
    patch_sphinx_to_read_pyi()

    if not args.skip_regenerating_type_hints:
        with isolated_imports(
            {
                "pedalboard",
                "pedalboard.io",
                "pedalboard_native",
                "pedalboard_native.io",
                "pedalboard_native.utils",
            }
        ):
            # print("Generating type stubs from pure-Python code...")
            # subprocess.check_call(["stubgen", "-o", tempdir, "pedalboard/_pedalboard.py"])

            # Generate .pyi stubs files from Pedalboard's native (Pybind11) source.
            # Documentation will be copied from the Pybind11 docstrings, and these stubs
            # files will be used as the "source of truth" for both IDE autocompletion
            # and Sphinx documentation.
            print("Generating type stubs from native code...")
            pybind11_stubgen_main(["-o", "pedalboard_native", "pedalboard_native", "--no-setup-py"])

            # Move type hints out of pedalboard_native/something_else/*:
            native_dir = pathlib.Path("pedalboard_native")
            native_subdir = [f for f in native_dir.glob("*") if "stubs" in f.name][0]
            shutil.copytree(native_subdir, native_dir, dirs_exist_ok=True)
            shutil.rmtree(native_subdir)

            # Post-process the type hints generaetd by pybind11_stubgen; we can't patch
            # everything easily, so we do string manipulation after-the-fact and run ``black``.
            print("Postprocessing generated type hints...")
            postprocess_type_hints_main(
                ["pedalboard_native", "pedalboard_native"] + (["--check"] if args.check else [])
            )

            # Run mypy.stubtest to ensure that the results are correct and nothing got missed:
            if sys.version_info > (3, 6):
                print("Running `mypy.stubtest` to validate stubs match...")
                test_stubs(
                    mypy_parse_options(
                        [
                            "pedalboard",
                            "--allowlist",
                            "stubtest.allowlist",
                            "--ignore-missing-stub",
                            "--ignore-unused-allowlist",
                        ]
                    )
                )
        # Re-run this same script in a fresh interpreter, but with skip_regenerating_type_hints
        # enabled:
        subprocess.check_call(
            [psutil.Process(os.getpid()).exe()] + sys.argv + ["--skip-regenerating-type-hints"]
        )
        return

    # Why is this necessary? I don't know, but without it, things fail.
    print("Importing numpy to ensure a successful Pedalboard stub import...")
    import numpy  # noqa

    print("Importing .pyi files for our native modules...")
    for modname in ["pedalboard_native", "pedalboard_native.io", "pedalboard_native.utils"]:
        import_stub(".", modname)

    print("Running Sphinx...")
    if args.check:
        missing_files = []
        mismatched_files = []
        with TemporaryDirectory() as tempdir:
            sphinx_build_main(["-b", "html", args.docs_input_dir, tempdir, "-v", "-v", "-v"])
            postprocess_sphinx_output(tempdir, SPHINX_REPLACEMENTS)
            remove_non_public_files(tempdir)

            for dirpath, _dirnames, filenames in os.walk(tempdir):
                prefix = dirpath.replace(tempdir, "").lstrip(os.path.sep)
                for filename in filenames:
                    if glob_matches(filename, args.skip_comparing):
                        print(f"Skipping comparison of file: {filename}")
                        continue
                    expected_path = os.path.join(tempdir, prefix, filename)
                    actual_path = os.path.join(args.docs_output_dir, prefix, filename)
                    if not os.path.isfile(actual_path):
                        missing_files.append(os.path.join(prefix, filename))
                    else:
                        with open(expected_path, "rb") as e, open(actual_path, "rb") as a:
                            if e.read() != a.read():
                                mismatched_files.append(os.path.join(prefix, filename))
            if missing_files or mismatched_files:
                error_lines = []
                if missing_files:
                    error_lines.append(
                        f"{len(missing_files):,} file(s) were expected in {args.docs_output_dir},"
                        " but not found:"
                    )
                    for missing_file in missing_files:
                        error_lines.append(f"\t{missing_file}")
                if mismatched_files:
                    error_lines.append(
                        f"{len(mismatched_files):,} file(s) in {args.docs_output_dir} did not match"
                        " expected values:"
                    )
                    for mismatched_file in mismatched_files:
                        expected_path = os.path.join(tempdir, mismatched_file)
                        actual_path = os.path.join(args.docs_output_dir, mismatched_file)
                        try:
                            with open(expected_path) as e, open(actual_path) as a:
                                diff = difflib.context_diff(
                                    e.readlines(),
                                    a.readlines(),
                                    os.path.join("expected", mismatched_file),
                                    os.path.join("actual", mismatched_file),
                                )
                            error_lines.append("\n".join([trim_diff_line(x) for x in diff]))
                        except UnicodeDecodeError:
                            error_lines.append(
                                f"Binary file {mismatched_file} does not match expected contents."
                            )
                raise ValueError("\n".join(error_lines))
        print("Done! Generated type stubs and documentation are valid.")
    else:
        sphinx_build_main(["-b", "html", args.docs_input_dir, args.docs_output_dir])
        postprocess_sphinx_output(args.docs_output_dir, SPHINX_REPLACEMENTS)
        remove_non_public_files(args.docs_output_dir)
        print(f"Done! Commit the contents of `{args.docs_output_dir}` to Git.")