Introduce type annotations

This patch introduces type annotations, which can be checked with mypy.

The coverage is not very comprehensive for now, but it is a starting
point and will be expanded in later patches.
This commit is contained in:
Alberto Bertogli 2020-05-24 14:20:56 +01:00
parent ad950208bf
commit 20b99ee568
3 changed files with 53 additions and 42 deletions

13
git-arr

@ -9,8 +9,9 @@ import optparse
import os import os
import re import re
import sys import sys
from typing import Union
import bottle import bottle # type: ignore
import git import git
import utils import utils
@ -328,10 +329,10 @@ def is_404(e):
return e.status_code == 404 return e.status_code == 404
def generate(output, only=None): def generate(output: str, only=None):
"""Generate static html to the output directory.""" """Generate static html to the output directory."""
def write_to(path, func_or_str, args=(), mtime=None): def write_to(path: str, func_or_str, args=(), mtime=None):
path = output + "/" + path path = output + "/" + path
dirname = os.path.dirname(path) dirname = os.path.dirname(path)
@ -339,7 +340,7 @@ def generate(output, only=None):
os.makedirs(dirname) os.makedirs(dirname)
if mtime: if mtime:
path_mtime = 0 path_mtime: Union[float, int] = 0
if os.path.exists(path): if os.path.exists(path):
path_mtime = os.stat(path).st_mtime path_mtime = os.stat(path).st_mtime
@ -380,8 +381,8 @@ def generate(output, only=None):
print(from_path, "->", to_path) print(from_path, "->", to_path)
os.symlink(to_path, from_path) os.symlink(to_path, from_path)
def write_tree(r, bn, mtime): def write_tree(r: git.Repo, bn: str, mtime):
t = r.tree(bn) t: git.Tree = r.tree(bn)
write_to("r/%s/b/%s/t/index.html" % (r.name, bn), tree, (r, bn), mtime) write_to("r/%s/b/%s/t/index.html" % (r.name, bn), tree, (r, bn), mtime)

40
git.py

@ -14,13 +14,16 @@ import email.utils
import datetime import datetime
import urllib.request, urllib.parse, urllib.error import urllib.request, urllib.parse, urllib.error
from html import escape from html import escape
from typing import Any, Dict, IO, Iterable, List, Optional, Tuple, Union
# Path to the git binary. # Path to the git binary.
GIT_BIN = "git" GIT_BIN = "git"
def run_git(repo_path, params, stdin=None, silent_stderr=False, raw=False): def run_git(
repo_path: str, params, stdin: bytes = None, silent_stderr=False, raw=False
) -> Union[IO[str], IO[bytes]]:
"""Invokes git with the given parameters. """Invokes git with the given parameters.
This function invokes git with the given parameters, and returns a This function invokes git with the given parameters, and returns a
@ -44,9 +47,12 @@ def run_git(repo_path, params, stdin=None, silent_stderr=False, raw=False):
stderr=stderr, stderr=stderr,
) )
assert p.stdin is not None
p.stdin.write(stdin) p.stdin.write(stdin)
p.stdin.close() p.stdin.close()
assert p.stdout is not None
if raw: if raw:
return p.stdout return p.stdout
@ -58,13 +64,13 @@ def run_git(repo_path, params, stdin=None, silent_stderr=False, raw=False):
class GitCommand(object): class GitCommand(object):
"""Convenient way of invoking git.""" """Convenient way of invoking git."""
def __init__(self, path, cmd, *args, **kwargs): def __init__(self, path: str, cmd: str, *args, **kwargs):
self._override = True self._override = True
self._path = path self._path = path
self._cmd = cmd self._cmd = cmd
self._args = list(args) self._args: List[str] = list(args)
self._kwargs = {} self._kwargs: Dict[str, str] = {}
self._stdin_buf = None self._stdin_buf: Optional[bytes] = None
self._raw = False self._raw = False
self._override = False self._override = False
for k, v in kwargs: for k, v in kwargs:
@ -77,17 +83,17 @@ class GitCommand(object):
k = k.replace("_", "-") k = k.replace("_", "-")
self._kwargs[k] = v self._kwargs[k] = v
def arg(self, a): def arg(self, a: str):
"""Adds an argument.""" """Adds an argument."""
self._args.append(a) self._args.append(a)
def raw(self, b): def raw(self, b: bool):
"""Request raw rather than utf8-encoded command output.""" """Request raw rather than utf8-encoded command output."""
self._override = True self._override = True
self._raw = b self._raw = b
self._override = False self._override = False
def stdin(self, s): def stdin(self, s: Union[str, bytes]):
"""Sets the contents we will send in stdin.""" """Sets the contents we will send in stdin."""
self._override = True self._override = True
if isinstance(s, str): if isinstance(s, str):
@ -130,14 +136,14 @@ class smstr:
.html -> an HTML-embeddable representation. .html -> an HTML-embeddable representation.
""" """
def __init__(self, raw): def __init__(self, raw: str):
if not isinstance(raw, (str, bytes)): if not isinstance(raw, (str, bytes)):
raise TypeError( raise TypeError(
"The raw string must be instance of 'str', not %s" % type(raw) "The raw string must be instance of 'str', not %s" % type(raw)
) )
self.raw = raw self.raw = raw
if isinstance(raw, bytes): if isinstance(raw, bytes):
self.unicode = raw.decode("utf8", errors="backslashreplace") self.unicode: str = raw.decode("utf8", errors="backslashreplace")
else: else:
self.unicode = raw self.unicode = raw
self.url = urllib.request.pathname2url(raw) self.url = urllib.request.pathname2url(raw)
@ -177,7 +183,7 @@ class smstr:
return html return html
def unquote(s): def unquote(s: str):
"""Git can return quoted file names, unquote them. Always return a str.""" """Git can return quoted file names, unquote them. Always return a str."""
if not (s[0] == '"' and s[-1] == '"'): if not (s[0] == '"' and s[-1] == '"'):
# Unquoted strings are always safe, no need to mess with them # Unquoted strings are always safe, no need to mess with them
@ -204,10 +210,10 @@ def unquote(s):
class Repo: class Repo:
"""A git repository.""" """A git repository."""
def __init__(self, path, name=None, info=None): def __init__(self, path: str, name=None, info=None):
self.path = path self.path = path
self.name = name self.name = name
self.info = info or SimpleNamespace() self.info: Any = info or SimpleNamespace()
def cmd(self, cmd): def cmd(self, cmd):
"""Returns a GitCommand() on our path.""" """Returns a GitCommand() on our path."""
@ -535,11 +541,13 @@ class Diff:
class Tree: class Tree:
""" A git tree.""" """ A git tree."""
def __init__(self, repo, ref): def __init__(self, repo: Repo, ref: str):
self.repo = repo self.repo = repo
self.ref = ref self.ref = ref
def ls(self, path, recursive=False): def ls(
self, path, recursive=False
) -> Iterable[Tuple[str, smstr, Optional[int]]]:
"""Generates (type, name, size) for each file in path.""" """Generates (type, name, size) for each file in path."""
cmd = self.repo.cmd("ls-tree") cmd = self.repo.cmd("ls-tree")
cmd.long = None cmd.long = None
@ -575,7 +583,7 @@ class Tree:
class Blob: class Blob:
"""A git blob.""" """A git blob."""
def __init__(self, raw_content): def __init__(self, raw_content: bytes):
self.raw_content = raw_content self.raw_content = raw_content
self._utf8_content = None self._utf8_content = None

@ -5,16 +5,16 @@ These are mostly used in templates, for presentation purposes.
""" """
try: try:
import pygments import pygments # type: ignore
from pygments import highlight from pygments import highlight # type: ignore
from pygments import lexers from pygments import lexers # type: ignore
from pygments.formatters import HtmlFormatter from pygments.formatters import HtmlFormatter # type: ignore
except ImportError: except ImportError:
pygments = None pygments = None
try: try:
import markdown import markdown # type: ignore
import markdown.treeprocessors import markdown.treeprocessors # type: ignore
except ImportError: except ImportError:
markdown = None markdown = None
@ -23,14 +23,16 @@ import mimetypes
import string import string
import os.path import os.path
import git
def shorten(s, width=60):
def shorten(s: str, width=60):
if len(s) < 60: if len(s) < 60:
return s return s
return s[:57] + "..." return s[:57] + "..."
def can_colorize(s): def can_colorize(s: str):
"""True if we can colorize the string, False otherwise.""" """True if we can colorize the string, False otherwise."""
if pygments is None: if pygments is None:
return False return False
@ -54,7 +56,7 @@ def can_colorize(s):
return True return True
def can_markdown(repo, fname): def can_markdown(repo: git.Repo, fname: str):
"""True if we can process file through markdown, False otherwise.""" """True if we can process file through markdown, False otherwise."""
if markdown is None: if markdown is None:
return False return False
@ -75,14 +77,14 @@ def can_embed_image(repo, fname):
) )
def colorize_diff(s): def colorize_diff(s: str) -> str:
lexer = lexers.DiffLexer(encoding="utf-8") lexer = lexers.DiffLexer(encoding="utf-8")
formatter = HtmlFormatter(encoding="utf-8", cssclass="source_code") formatter = HtmlFormatter(encoding="utf-8", cssclass="source_code")
return highlight(s, lexer, formatter) return highlight(s, lexer, formatter)
def colorize_blob(fname, s): def colorize_blob(fname, s: str) -> str:
try: try:
lexer = lexers.guess_lexer_for_filename(fname, s, encoding="utf-8") lexer = lexers.guess_lexer_for_filename(fname, s, encoding="utf-8")
except lexers.ClassNotFound: except lexers.ClassNotFound:
@ -107,7 +109,7 @@ def colorize_blob(fname, s):
return highlight(s, lexer, formatter) return highlight(s, lexer, formatter)
def markdown_blob(s): def markdown_blob(s: str) -> str:
extensions = [ extensions = [
"markdown.extensions.fenced_code", "markdown.extensions.fenced_code",
"markdown.extensions.tables", "markdown.extensions.tables",
@ -116,7 +118,7 @@ def markdown_blob(s):
return markdown.markdown(s, extensions=extensions) return markdown.markdown(s, extensions=extensions)
def embed_image_blob(fname, image_data): def embed_image_blob(fname: str, image_data: bytes) -> str:
mimetype = mimetypes.guess_type(fname)[0] mimetype = mimetypes.guess_type(fname)[0]
b64img = base64.b64encode(image_data).decode("ascii") b64img = base64.b64encode(image_data).decode("ascii")
return '<img style="max-width:100%;" src="data:{0};base64,{1}" />'.format( return '<img style="max-width:100%;" src="data:{0};base64,{1}" />'.format(
@ -124,22 +126,22 @@ def embed_image_blob(fname, image_data):
) )
def is_binary(s): def is_binary(b: bytes):
# Git considers a blob binary if NUL in first ~8KB, so do the same. # Git considers a blob binary if NUL in first ~8KB, so do the same.
return b"\0" in s[:8192] return b"\0" in b[:8192]
def hexdump(s): def hexdump(s: bytes):
graph = string.ascii_letters + string.digits + string.punctuation + " " graph = string.ascii_letters + string.digits + string.punctuation + " "
s = s.decode("latin1") b = s.decode("latin1")
offset = 0 offset = 0
while s: while b:
t = s[:16] t = b[:16]
hexvals = ["%.2x" % ord(c) for c in t] hexvals = ["%.2x" % ord(c) for c in t]
text = "".join(c if c in graph else "." for c in t) text = "".join(c if c in graph else "." for c in t)
yield offset, " ".join(hexvals[:8]), " ".join(hexvals[8:]), text yield offset, " ".join(hexvals[:8]), " ".join(hexvals[8:]), text
offset += 16 offset += 16
s = s[16:] b = b[16:]
if markdown: if markdown: