Last active
February 18, 2025 15:34
-
-
Save tgran2028/e60b80ab879ccabde92cccc38c94b9ba to your computer and use it in GitHub Desktop.
CLI program to utilize GitHub's markdown rendering API endpoint.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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