nixos-render-docs-redirects: init (#357383)

Co-authored-by: Valentin Gagarin <valentin@gagarin.work>
This commit is contained in:
Priyanshu Tripathi 2024-11-29 19:33:08 +05:30 committed by GitHub
parent c8089754ee
commit 836d207c6c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 300 additions and 7 deletions

View File

@ -9,6 +9,8 @@
mkShellNoCC,
documentation-highlighter,
nixos-render-docs,
nixos-render-docs-redirects,
writeShellScriptBin,
nixpkgs ? { },
}:
@ -105,8 +107,14 @@ stdenvNoCC.mkDerivation (
buildArgs = "./.";
open = "/share/doc/nixpkgs/manual.html";
};
nixos-render-docs-redirects' = writeShellScriptBin "redirects" "${lib.getExe nixos-render-docs-redirects} --file ${toString ../redirects.json} $@";
in
mkShellNoCC { packages = [ devmode' ]; };
mkShellNoCC {
packages = [
devmode'
nixos-render-docs-redirects'
];
};
tests.manpage-urls = callPackage ../tests/manpage-urls.nix { };
};

View File

@ -10,7 +10,11 @@ let
buildArgs = "../../release.nix -A manualHTML.${builtins.currentSystem}";
open = "/${outputPath}/${indexPath}";
};
nixos-render-docs-redirects = pkgs.writeShellScriptBin "redirects" "${pkgs.lib.getExe pkgs.nixos-render-docs-redirects} --file ${toString ./redirects.json} $@";
in
pkgs.mkShellNoCC {
packages = [ devmode ];
packages = [
devmode
nixos-render-docs-redirects
];
}

View File

@ -0,0 +1,22 @@
{ lib, python3 }:
python3.pkgs.buildPythonApplication {
pname = "nixos-render-docs-redirects";
version = "0.0";
pyproject = true;
src = ./src;
build-system = with python3.pkgs; [ setuptools ];
nativeCheckInputs = with python3.pkgs; [
pytestCheckHook
];
meta = {
description = "Redirects manipulation for nixos manuals";
license = lib.licenses.mit;
maintainers = with lib.maintainers; [ getpsyched ];
mainProgram = "redirects";
};
}

View File

@ -0,0 +1,130 @@
import argparse
import json
import sys
from pathlib import Path
def add_content(redirects: dict[str, list[str]], identifier: str, path: str) -> dict[str, list[str]]:
if identifier in redirects:
raise IdentifierExists(identifier)
# Insert the new identifier in alphabetical order
new_redirects = list(redirects.items())
insertion_index = 0
for i, (key, _) in enumerate(new_redirects):
if identifier > key:
insertion_index = i + 1
else:
break
new_redirects.insert(insertion_index, (identifier, [f"{path}#{identifier}"]))
return dict(new_redirects)
def move_content(redirects: dict[str, list[str]], identifier: str, path: str) -> dict[str, list[str]]:
if identifier not in redirects:
raise IdentifierNotFound(identifier)
redirects[identifier].insert(0, f"{path}#{identifier}")
return redirects
def rename_identifier(
redirects: dict[str, list[str]],
old_identifier: str,
new_identifier: str
) -> dict[str, list[str]]:
if old_identifier not in redirects:
raise IdentifierNotFound(old_identifier)
if new_identifier in redirects:
raise IdentifierExists(new_identifier)
# To minimise the diff, we recreate the redirects mapping allowing
# the new key to be updated in-place, preserving the index.
new_redirects = {}
current_path = ""
for key, value in redirects.items():
if key == old_identifier:
new_redirects[new_identifier] = value
current_path = value[0].split('#')[0]
continue
new_redirects[key] = value
new_redirects[new_identifier].insert(0, f"{current_path}#{new_identifier}")
return new_redirects
def remove_and_redirect(
redirects: dict[str, list[str]],
old_identifier: str,
new_identifier: str
) -> dict[str, list[str]]:
if old_identifier not in redirects:
raise IdentifierNotFound(old_identifier)
if new_identifier not in redirects:
raise IdentifierNotFound(new_identifier)
redirects[new_identifier].extend(redirects.pop(old_identifier))
return redirects
def main():
parser = argparse.ArgumentParser(description="redirects manipulation for nixos manuals")
commands = parser.add_subparsers(dest="command", required=True)
parser.add_argument("-f", "--file", type=Path, required=True)
add_content_cmd = commands.add_parser("add-content")
add_content_cmd.add_argument("identifier", type=str)
add_content_cmd.add_argument("path", type=str)
move_content_cmd = commands.add_parser("move-content")
move_content_cmd.add_argument("identifier", type=str)
move_content_cmd.add_argument("path", type=str)
rename_id_cmd = commands.add_parser("rename-identifier")
rename_id_cmd.add_argument("old_identifier", type=str)
rename_id_cmd.add_argument("new_identifier", type=str)
remove_redirect_cmd = commands.add_parser("remove-and-redirect")
remove_redirect_cmd.add_argument("identifier", type=str)
remove_redirect_cmd.add_argument("target_identifier", type=str)
args = parser.parse_args()
with open(args.file) as file:
redirects = json.load(file)
try:
if args.command == "add-content":
redirects = add_content(redirects, args.identifier, args.path)
print(f"Added new identifier: {args.identifier}")
elif args.command == "move-content":
redirects = move_content(redirects, args.identifier, args.path)
print(f"Moved '{args.identifier}' to the new path: {args.path}")
elif args.command == "rename-identifier":
redirects = rename_identifier(redirects, args.old_identifier, args.new_identifier)
print(f"Renamed identifier from {args.old_identifier} to {args.new_identifier}")
elif args.command == "remove-and-redirect":
redirects = remove_and_redirect(redirects, args.identifier, args.target_identifier)
print(f"Redirect from '{args.identifier}' to '{args.target_identifier}' added.")
except Exception as error:
print(error, file=sys.stderr)
else:
with open(args.file, "w") as file:
json.dump(redirects, file, indent=2)
file.write("\n")
class IdentifierExists(Exception):
def __init__(self, identifier: str):
self.identifier = identifier
def __str__(self):
return f"The identifier '{self.identifier}' already exists."
class IdentifierNotFound(Exception):
def __init__(self, identifier: str):
self.identifier = identifier
def __str__(self):
return f"The identifier '{self.identifier}' does not exist in the redirect mapping."

View File

@ -0,0 +1,16 @@
[project]
name = "nixos-render-docs-redirects"
version = "0.0"
description = "redirects manipulation for nixos manuals"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
[project.scripts]
redirects = "nixos_render_docs_redirects:main"
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

View File

@ -0,0 +1,86 @@
import unittest
from nixos_render_docs_redirects import (
add_content,
move_content,
rename_identifier,
remove_and_redirect,
IdentifierExists,
IdentifierNotFound,
)
class RedirectsTestCase(unittest.TestCase):
def test_add_content(self):
initial_redirects = {
"bar": ["path/to/bar.html#bar"],
"foo": ["path/to/foo.html#foo"],
}
final_redirects = {
"bar": ["path/to/bar.html#bar"],
"baz": ["path/to/baz.html#baz"],
"foo": ["path/to/foo.html#foo"],
}
result = add_content(initial_redirects, "baz", "path/to/baz.html")
self.assertEqual(list(result.items()), list(final_redirects.items()))
with self.assertRaises(IdentifierExists):
add_content(result, "foo", "another/path.html")
def test_move_content(self):
initial_redirects = {
"foo": ["path/to/foo.html#foo"],
"bar": ["path/to/bar.html#bar"],
}
final_redirects = {
"foo": ["new/path.html#foo", "path/to/foo.html#foo"],
"bar": ["path/to/bar.html#bar"],
}
result = move_content(initial_redirects, "foo", "new/path.html")
self.assertEqual(list(result.items()), list(final_redirects.items()))
with self.assertRaises(IdentifierNotFound):
move_content(result, "baz", "path.html")
def test_rename_identifier(self):
initial_redirects = {
"foo": ["path/to/foo.html#foo"],
"bar": ["path/to/bar.html#bar"],
"baz": ["path/to/baz.html#baz"],
}
final_redirects = {
"foo": ["path/to/foo.html#foo"],
"boo": ["path/to/bar.html#boo", "path/to/bar.html#bar"],
"baz": ["path/to/baz.html#baz"],
}
result = rename_identifier(initial_redirects, "bar", "boo")
self.assertEqual(list(result.items()), list(final_redirects.items()))
with self.assertRaises(IdentifierNotFound):
rename_identifier(result, "bar", "boo")
with self.assertRaises(IdentifierExists):
rename_identifier(result, "boo", "boo")
def test_remove_and_redirect(self):
initial_redirects = {
"foo": ["new/path.html#foo", "path/to/foo.html#foo"],
"bar": ["path/to/bar.html#bar"],
"baz": ["path/to/baz.html#baz"],
}
final_redirects = {
"bar": ["path/to/bar.html#bar", "new/path.html#foo", "path/to/foo.html#foo"],
"baz": ["path/to/baz.html#baz"],
}
result = remove_and_redirect(initial_redirects, "foo", "bar")
self.assertEqual(list(result.items()), list(final_redirects.items()))
with self.assertRaises(IdentifierNotFound):
remove_and_redirect(result, "foo", "bar")
with self.assertRaises(IdentifierNotFound):
remove_and_redirect(initial_redirects, "foo", "baz")

View File

@ -47,23 +47,49 @@ The first element of an identifier's redirects list must denote its current loca
If you moved content, add its new location as the first element of the redirects mapping.
Please update doc/redirects.json or nixos/doc/manual/redirects.json!
""") # TODO: automatically detect if you just missed adding a new location, and make a tool to do that for you
""")
if self.identifiers_without_redirects:
error_messages.append(f"""
Identifiers present in the source must have a mapping in the redirects file.
- {"\n - ".join(self.identifiers_without_redirects)}
This can happen when an identifier was added or renamed.
Please update doc/redirects.json or nixos/doc/manual/redirects.json!
""") # TODO: add tooling in the development shell to do that automatically and point to that command
Added new content?
redirects add-content identifier path
Moved existing content to a different output path?
redirects move-content identifier path
Renamed existing identifiers?
redirects rename-identifier old-identifier new-identifier
Removed content? Redirect to alternatives or relevant release notes.
redirects remove-and-redirect identifier target-identifier
Note that you need to run `nix-shell doc` or `nix-shell nixos/doc/manual` to be able to run this command.
""")
if self.orphan_identifiers:
error_messages.append(f"""
Keys of the redirects mapping must correspond to some identifier in the source.
- {"\n - ".join(self.orphan_identifiers)}
This can happen when an identifier was removed or renamed.
Please update doc/redirects.json or nixos/doc/manual/redirects.json!
""") # TODO: add tooling in the development shell to do that automatically and point to that command
Added new content?
redirects add-content identifier path
Moved existing content to a different output path?
redirects move-content identifier path
Renamed existing identifiers?
redirects rename-identifier old-identifier new-identifier
Removed content? (good for redirecting deprecations to new content or release notes)
redirects remove-and-redirect identifier target-identifier
Note that you need to run `nix-shell doc` or `nix-shell nixos/doc/manual` to be able to run this command.
""")
error_messages.append("NOTE: If your Manual build passes locally and you see this message in CI, you probably need a rebase.")
return "\n".join(error_messages)