rdubwiley's picture
Deploy Agenda Parser
459bdc8 verified
Raw
History Blame Contribute Delete
7.47 kB
"""Typer CLI: turn federal/state regulation & statute citations into Cornell LII links.
Links are built offline by default. ``--verify`` (CFR) and ``search`` (federal)
are the only paths that reach the network, via the public eCFR API.
"""
from __future__ import annotations
import typer
from . import citations, ecfr, output, states, urls
from .citations import Citation, CitationError
from .client import HttpError, url_exists
app = typer.Typer(
add_completion=False,
no_args_is_help=True,
help="Query Cornell LII (law.cornell.edu) for federal & state regulation links.",
)
# Reusable --json flag, shared across commands.
JSON = typer.Option(False, "--json", "-j", help="Emit JSON instead of human-readable text.")
def _fail(message: str) -> "typer.NoReturn":
typer.secho(f"error: {message}", fg=typer.colors.RED, err=True)
raise typer.Exit(1)
def _parse(text: str | None, title: str | None, section: str | None, corpus: str) -> Citation:
"""Build a Citation from a free-form string or explicit --title/--section."""
if text:
try:
return citations.parse_cfr(text) if corpus == "cfr" else citations.parse_usc(text)
except CitationError as e:
_fail(str(e))
if title and section:
return Citation(corpus, str(title), str(section))
word = "CFR" if corpus == "cfr" else "USC"
_fail(f"Provide a citation (e.g. '29 {word} 1604.11') or both --title and --section.")
# --- Federal CFR (regulations) ------------------------------------------------
@app.command()
def cfr(
citation: str = typer.Argument(None, help="e.g. '29 CFR 1604.11' or '29/1604.11'"),
title: str = typer.Option(None, "--title", "-t", help="CFR title number."),
section: str = typer.Option(None, "--section", "-s", help="Section number, e.g. 1604.11."),
verify: bool = typer.Option(False, "--verify", help="Confirm the citation exists via the eCFR API."),
json_out: bool = JSON,
) -> None:
"""Resolve a Code of Federal Regulations citation to its LII page."""
cit = _parse(citation, title, section, "cfr")
result = {
"corpus": "cfr",
"citation": cit.label,
"title": cit.title,
"section": cit.section,
"url": urls.cfr_section_url(cit.title, cit.section),
}
if verify:
try:
v = ecfr.verify_cfr(cit.title, cit.section)
except HttpError as e:
_fail(f"eCFR verification failed: {e}")
result["verified"] = v["valid"]
result["date"] = v["date"]
if v.get("heading"):
result["label"] = v["heading"]
if v.get("detail"):
result["detail"] = v["detail"]
output.show(result, json_out)
@app.command("cfr-part")
def cfr_part(
title: str = typer.Argument(..., help="CFR title number, e.g. 29."),
part: str = typer.Argument(..., help="Part number, e.g. 1604."),
json_out: bool = JSON,
) -> None:
"""Link to a CFR part's browse page (lists its sections)."""
result = {
"corpus": "cfr",
"citation": f"{title} CFR Part {part}",
"url": urls.cfr_part_url(title, part),
}
output.show(result, json_out)
@app.command("cfr-title")
def cfr_title(
title: str = typer.Argument(..., help="CFR title number, e.g. 29."),
json_out: bool = JSON,
) -> None:
"""Link to a CFR title's index page."""
result = {"corpus": "cfr", "citation": f"{title} CFR", "url": urls.cfr_title_url(title)}
output.show(result, json_out)
# --- Federal USC (statutes) ---------------------------------------------------
@app.command()
def usc(
citation: str = typer.Argument(None, help="e.g. '42 USC 1983' or '42/1983'"),
title: str = typer.Option(None, "--title", "-t", help="USC title number."),
section: str = typer.Option(None, "--section", "-s", help="Section number, e.g. 1983."),
verify: bool = typer.Option(False, "--verify", help="Confirm the LII page resolves (HTTP check)."),
json_out: bool = JSON,
) -> None:
"""Resolve a US Code citation to its LII page."""
cit = _parse(citation, title, section, "usc")
url = urls.usc_section_url(cit.title, cit.section)
result = {
"corpus": "usc",
"citation": cit.label,
"title": cit.title,
"section": cit.section,
"url": url,
}
if verify:
ok = url_exists(url)
result["verified"] = ok
if not ok:
result["detail"] = "LII page did not return HTTP 200."
output.show(result, json_out)
# --- State materials ----------------------------------------------------------
@app.command("state-regs")
def state_regs(
state: str = typer.Argument(..., help="State name or USPS abbreviation, e.g. 'maine' or 'ME'."),
json_out: bool = JSON,
) -> None:
"""Link to a state's LII-hosted administrative code (regulations)."""
try:
slug = states.resolve_state(state)
except ValueError as e:
_fail(str(e))
result = {
"corpus": "state-regs",
"citation": f"{slug.replace('_', ' ').title()} regulations",
"url": urls.state_regs_url(slug),
}
output.show(result, json_out)
@app.command()
def state(
state: str = typer.Argument(..., help="State name or USPS abbreviation, e.g. 'new york' or 'NY'."),
json_out: bool = JSON,
) -> None:
"""Link to a state's LII portal page (statutes link out to official sources)."""
try:
slug = states.resolve_state(state)
except ValueError as e:
_fail(str(e))
result = {
"corpus": "state",
"citation": f"{slug.replace('_', ' ').title()} legal materials",
"url": urls.state_portal_url(slug),
}
output.show(result, json_out)
@app.command("states")
def states_cmd(json_out: bool = JSON) -> None:
"""Link to the LII states & regulations directories."""
result = {
"links": [
{"citation": "All states", "url": urls.states_directory_url()},
{"citation": "All state regulations", "url": urls.regulations_directory_url()},
]
}
output.show(result, json_out)
# --- Search -------------------------------------------------------------------
@app.command()
def search(
query: str = typer.Argument(..., help="Search terms."),
corpus: str = typer.Option("cfr", "--corpus", "-c", help="'cfr' = eCFR API deep links; 'lii' = LII site-search URL."),
limit: int = typer.Option(10, "--limit", "-n", help="Max results (cfr corpus)."),
json_out: bool = JSON,
) -> None:
"""Search federal regulations (eCFR -> LII links), or get the LII site-search URL."""
corpus = corpus.lower()
if corpus == "cfr":
try:
hits = ecfr.search_federal(query, limit)
except HttpError as e:
_fail(f"eCFR search failed: {e}")
output.show({"query": query, "corpus": "cfr", "links": hits}, json_out, attribution=True)
elif corpus == "lii":
result = {
"query": query,
"corpus": "lii",
"links": [
{
"citation": f"LII site search: {query}",
"url": urls.lii_search_url(query),
"note": "Open in a browser — LII search is not crawlable (robots.txt) and renders client-side.",
}
],
}
output.show(result, json_out, attribution=True)
else:
_fail("--corpus must be 'cfr' or 'lii'.")
if __name__ == "__main__":
app()