Skip to content

Instantly share code, notes, and snippets.

@tgran2028
Last active February 18, 2025 15:34
Show Gist options
  • Save tgran2028/e60b80ab879ccabde92cccc38c94b9ba to your computer and use it in GitHub Desktop.
Save tgran2028/e60b80ab879ccabde92cccc38c94b9ba to your computer and use it in GitHub Desktop.
CLI program to utilize GitHub's markdown rendering API endpoint.
#!/usr/bin/env python3
import argparse
import os
import sys
from pathlib import Path
from typing import Callable
import json
import requests
from rich.console import Console
import tempfile as tf
import subprocess as sp
def beautify_html(html: str) -> str:
executable = "/home/tim/.local/share/pnpm/prettier"
return sp.check_output(
args=[
executable,
"--no-color",
"--no-config",
"--parser",
"html",
"--stdin-filepath",
"document.html",
],
input=html,
text=True,
encoding="utf-8",
stderr=sp.PIPE,
universal_newlines=True,
)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Convert markdown to html")
# Text input
text_group = parser.add_mutually_exclusive_group(required=True)
text_group.add_argument(
"-f",
"--file",
type=Path,
metavar="FILE",
help="Path to file containing markdown content",
)
text_group.add_argument("text", nargs="?", type=str, help="Markdown content")
# Authentication
parser.add_argument(
"-t",
"--token",
type=str,
metavar="API_TOKEN",
help="GitHub API token. "
"The token can also be provided using the `GITHUB_TOKEN` or `GH_TOKEN` environment variable",
)
# Output
parser.add_argument(
"-o",
"--output",
type=Path,
metavar="FILE",
help="Path to output file. If not provided, output will be printed to stdout",
)
parser.add_argument(
"-p",
"--pager",
help="Use a pager to display output. Disabled if output is written to a file",
action="store_true",
)
# Color
parser.add_argument(
"-c",
"--color",
choices=["always", "auto", "never"],
default="auto",
help="Color output. Default is auto",
)
parser.add_argument(
"-M",
"--no-color",
action="store_true",
help="Disable color output. This is equivalent to --color never",
)
# beautify
parser.add_argument(
"-P", "--pretty", action="store_true", help="Beautify the output html"
)
text_group.set_defaults(text=None)
return parser
def get_markdown_input(parser, args) -> str:
# Determine if input is being piped via stdin once.
stdin_in_use = not sys.stdin.isatty()
# Check for conflicting inputs
if (args.text and args.file) or (stdin_in_use and (args.text or args.file)):
parser.error(
"Multiple text inputs provided: Either pipe the markdown via stdin, provide an argument, or use a file (with -f, --file)"
)
# Read markdown content based on input method.
if args.text:
markdown = args.text
elif args.file:
with open(args.file, "r") as f:
markdown = f.read()
elif stdin_in_use:
markdown = sys.stdin.read()
else:
parser.error("No input text provided")
return markdown
def get_color_requirement(args) -> bool:
"""
Determine if color should be used based on the provided arguments.
"""
return (
not args.no_color
and not (args.output and args.color == "never")
and (args.color == "always" or (args.color == "auto" and sys.stdout.isatty()))
)
def get_output_func(args) -> Callable[[str], None]:
is_file_output = args.output is not None
use_color = get_color_requirement(args)
if is_file_output:
args.color = "never"
def func(
content: str, pager: bool = args.pager, no_color: bool = not use_color
) -> None:
with open(args.output, "w") as f:
f.write(content)
return func
else:
def func(
content: str, pager: bool = args.pager, no_color: bool = not use_color
) -> None:
console = Console(
color_system="auto",
file=sys.stdout,
force_terminal=True,
force_jupyter=False,
force_interactive=False,
no_color=no_color,
)
if pager:
with console.pager():
console.print(content)
else:
console.print(content)
return func
def get_api_token(parser, args) -> str:
token = os.getenv("GITHUB_TOKEN") or os.getenv("GH_TOKEN") or args.token
if not token:
parser.error(
"GitHub token not provided. Use -t, --token option or set the GITHUB_TOKEN or GH_TOKEN environment variable"
)
return token
def prepare_request(token: str, markdown: str) -> requests.PreparedRequest:
headers = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {token}",
"X-GitHub-Api-Version": "2022-11-28",
"Content-Type": "application/x-www-form-urlencoded",
}
req = requests.Request(
"POST",
"https://api.github.com/markdown",
data=json.dumps({"text": markdown}, indent=None, separators=(",", ":")),
headers=headers,
)
return req.prepare()
def main() -> None:
parser = build_parser()
args = parser.parse_args()
markdown = get_markdown_input(parser, args)
output_func = get_output_func(args)
token = get_api_token(parser, args)
s = requests.Session()
prepared = prepare_request(token, markdown)
response = s.send(prepared)
response.raise_for_status()
html_content = response.content.decode("utf-8")
if args.pretty:
html_content = beautify_html(html_content)
output_func(html_content)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment