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:
parent
ad950208bf
commit
20b99ee568
13
git-arr
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
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
|
||||||
|
|
||||||
|
42
utils.py
42
utils.py
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user