- ReadMe API v2 now requires category.uri instead of category.id. - ReadMe v2 page responses include a renderable field indicating whether the document is MDX-compatible and can be rendered successfully. The script now checks this field and prints detailed compilation errors if rendering fails.
154 lines
4.5 KiB
Python
154 lines
4.5 KiB
Python
import os
|
||
from urllib.parse import quote
|
||
from time import sleep
|
||
import requests
|
||
import re
|
||
from enum import Enum
|
||
|
||
# readme url
|
||
URL = "https://api.readme.com/v2/branches/stable"
|
||
CATEGORY_SLUG = "JSON-RPC API Reference"
|
||
|
||
|
||
class Action(Enum):
|
||
ADD = 'add'
|
||
UPDATE = 'update'
|
||
DELETE = 'delete'
|
||
|
||
|
||
def getListOfRPCDocs(headers):
|
||
response = requests.get(f"{URL}/categories/reference/{quote(CATEGORY_SLUG)}/pages", headers=headers)
|
||
if response.status_code == 200:
|
||
return response.json().get('data', [])
|
||
else:
|
||
return []
|
||
|
||
|
||
def check_renderable(response, action, title):
|
||
try:
|
||
data = response.json()
|
||
except Exception:
|
||
print("Non-JSON response:")
|
||
print(response.text)
|
||
return False
|
||
|
||
renderable = data.get("renderable")
|
||
if renderable is None:
|
||
# Some endpoints don’t include renderable (e.g. DELETE)
|
||
return True
|
||
|
||
if not renderable.get("status", False):
|
||
print(f"\n❌ RENDER FAILED for {action.value.upper()} '{title}'")
|
||
print("Error :", renderable.get("error"))
|
||
print("Message:", renderable.get("message"))
|
||
return False
|
||
|
||
return True
|
||
|
||
|
||
def publishDoc(action, title, body, order, headers):
|
||
payload = {
|
||
"title": title,
|
||
"type": "basic",
|
||
"content": {
|
||
"body": body,
|
||
},
|
||
"category": {
|
||
"uri": f"/branches/1/categories/reference/{CATEGORY_SLUG}"
|
||
},
|
||
"hidden": False,
|
||
"order": order,
|
||
}
|
||
|
||
if action == Action.ADD:
|
||
payload["slug"] = title
|
||
response = requests.post(URL + "/reference", json=payload, headers=headers)
|
||
if response.status_code != 201:
|
||
print("❌ HTTP ERROR:", response.status_code)
|
||
print(response.text)
|
||
return
|
||
|
||
if not check_renderable(response, action, title):
|
||
raise RuntimeError(f"Renderable check failed for {title}")
|
||
|
||
print("✅ Created", title)
|
||
|
||
elif action == Action.UPDATE:
|
||
response = requests.patch(f"{URL}/reference/{title}", json=payload, headers=headers)
|
||
if response.status_code != 200:
|
||
print("❌ HTTP ERROR:", response.status_code)
|
||
print(response.text)
|
||
return
|
||
|
||
if not check_renderable(response, action, title):
|
||
raise RuntimeError(f"Renderable check failed for {title}")
|
||
|
||
print("✅ Updated", title)
|
||
|
||
elif action == Action.DELETE:
|
||
response = requests.delete(f"{URL}/reference/{title}", headers=headers)
|
||
|
||
if response.status_code != 204:
|
||
print("❌ DELETE FAILED:", title)
|
||
print(response.text)
|
||
else:
|
||
print("🗑️ Deleted", title)
|
||
|
||
else:
|
||
print("Invalid action")
|
||
|
||
|
||
def extract_rpc_commands(rst_content):
|
||
manpages_block = re.search(
|
||
r"\.\. block_start manpages(.*?)" r"\.\. block_end manpages",
|
||
rst_content,
|
||
re.DOTALL,
|
||
)
|
||
if manpages_block:
|
||
commands = re.findall(
|
||
r"\b([a-zA-Z0-9_-]+)" r"\s+<([^>]+)>\n", manpages_block.group(1)
|
||
)
|
||
return commands
|
||
return []
|
||
|
||
|
||
def main():
|
||
# define headers for requests
|
||
headers = {
|
||
"accept": "application/json",
|
||
"content-type": "application/json",
|
||
"Authorization": "Bearer " + os.environ.get("README_API_KEY"),
|
||
}
|
||
|
||
# path to the rst file from where we fetch all the RPC commands
|
||
path_to_rst = "doc/index.rst"
|
||
with open(path_to_rst, "r") as file:
|
||
rst_content = file.read()
|
||
|
||
commands_from_local = extract_rpc_commands(rst_content)
|
||
commands_from_readme = getListOfRPCDocs(headers)
|
||
|
||
# Compare local and server commands list to get the list of command to add or delete
|
||
commands_local_title = set(command[0] for command in commands_from_local)
|
||
commands_readme_title = set(command['title'] for command in commands_from_readme)
|
||
commands_to_delete = commands_readme_title - commands_local_title
|
||
commands_to_add = commands_local_title - commands_readme_title
|
||
for name in commands_to_delete:
|
||
publishDoc(Action.DELETE, name, "", 0, headers)
|
||
sleep(3)
|
||
|
||
if commands_from_local:
|
||
order = 0
|
||
for name, file in commands_from_local:
|
||
with open("doc/" + file) as f:
|
||
body = f.read()
|
||
publishDoc(Action.ADD if name in commands_to_add else Action.UPDATE, name, body, order, headers)
|
||
order = order + 1
|
||
sleep(3)
|
||
else:
|
||
print("No commands found in the Manpages block.")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|