import asyncio
import re
import sys
from pathlib import Path
from urllib.parse import urlparse
from playwright.async_api import async_playwright
INDEX_URL = "https://developer.salesforce.com/docs/data/data-cloud-query-guide/references/dc-sql-reference/data-cloud-sql-context.html"
SECTION_PREFIX = "https://developer.salesforce.com/docs/data/data-cloud-query-guide/references/dc-sql-reference/"
OUT_FILE = Path(__file__).parent / "dc_sql_reference.md"
async def collect_nav_links(page, url: str) -> list[str]:
await page.goto(url, wait_until="networkidle", timeout=60_000)
links = await page.eval_on_selector_all("a[href]", "els => els.map(e => e.href)")
seen: set[str] = set()
result: list[str] = []
for link in links:
bare = link.split("#")[0]
if bare.startswith(SECTION_PREFIX) and bare not in seen:
seen.add(bare)
result.append(bare)
return result
async def scrape_page(page, url: str) -> str:
try:
await page.goto(url, wait_until="networkidle", timeout=60_000)
except Exception as e:
return f"\n\n---\n\n## {url}\n\n_Error loading page: {e}_\n\n"
try:
await page.wait_for_selector("article, main, [role='main']", timeout=15_000)
except Exception:
pass
title = await page.title()
title = re.sub(r"\s*[|\-–]\s*Salesforce.*$", "", title).strip()
content = await page.eval_on_selector_all(
"article *, main *",
"""els => {
const blocks = [];
for (const el of els) {
if (el.closest('nav, aside, header, footer, .sidebar, .toc')) continue;
const tag = el.tagName.toLowerCase();
const text = el.innerText?.trim();
if (!text) continue;
if (['h1','h2','h3','h4'].includes(tag)) {
const level = '#'.repeat(parseInt(tag[1]) + 1);
blocks.push(level + ' ' + text);
} else if (tag === 'pre' || tag === 'code') {
blocks.push('```\\n' + text + '\\n```');
} else if (tag === 'li') {
blocks.push('- ' + text);
} else if (['p', 'td', 'th'].includes(tag)) {
blocks.push(text);
}
}
const deduped = [];
for (const b of blocks) {
if (deduped[deduped.length - 1] !== b) deduped.push(b);
}
return deduped.join('\\n\\n');
}"""
)
if not content:
content = await page.inner_text("body")
slug = url.replace(SECTION_PREFIX, "").replace(".html", "")
return f"\n\n---\n\n# {title or slug}\n\n_URL: {url}_\n\n{content}\n"
async def main() -> None:
async with async_playwright() as pw:
browser = await pw.chromium.launch(headless=True)
page = await browser.new_page()
print("Collecting nav links …", file=sys.stderr)
links = await collect_nav_links(page, INDEX_URL)
if not links:
links = [INDEX_URL]
print(f"Found {len(links)} page(s)", file=sys.stderr)
parts = [
f"# Salesforce Data Cloud SQL Reference\n\n"
f"_Scraped from: {INDEX_URL}_\n\n"
f"_To refresh: `uv run --with playwright python3 scripts/scrape_dc_sql_reference.py`_\n"
]
for i, url in enumerate(links, 1):
print(f" [{i}/{len(links)}] {url}", file=sys.stderr)
parts.append(await scrape_page(page, url))
await browser.close()
OUT_FILE.write_text("\n".join(parts), encoding="utf-8")
size_kb = OUT_FILE.stat().st_size // 1024
print(f"\nWrote {OUT_FILE} ({size_kb} KB, {len(links)} page(s))", file=sys.stderr)
print(f"Next step: diff against previous version and update the sql_dialect string in src/server.rs")
if __name__ == "__main__":
asyncio.run(main())