From 20b99ee568fd15ca2897f995e9775861ef293544 Mon Sep 17 00:00:00 2001
From: Alberto Bertogli <albertito@blitiri.com.ar>
Date: Sun, 24 May 2020 14:20:56 +0100
Subject: [PATCH 3/7] 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.
---
 git-arr  | 13 +++++++------
 git.py   | 40 ++++++++++++++++++++++++----------------
 utils.py | 42 ++++++++++++++++++++++--------------------
 3 files changed, 53 insertions(+), 42 deletions(-)

diff --git a/git-arr b/git-arr
index 05ac171..b109f23 100755
--- a/git-arr
+++ b/git-arr
@@ -9,8 +9,9 @@ import optparse
 import os
 import re
 import sys
+from typing import Union
 
-import bottle
+import bottle  # type: ignore
 
 import git
 import utils
@@ -328,10 +329,10 @@ def is_404(e):
         return e.status_code == 404
 
 
-def generate(output, only=None):
+def generate(output: str, only=None):
     """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
         dirname = os.path.dirname(path)
 
@@ -339,7 +340,7 @@ def generate(output, only=None):
             os.makedirs(dirname)
 
         if mtime:
-            path_mtime = 0
+            path_mtime: Union[float, int] = 0
             if os.path.exists(path):
                 path_mtime = os.stat(path).st_mtime
 
@@ -380,8 +381,8 @@ def generate(output, only=None):
         print(from_path, "->", to_path)
         os.symlink(to_path, from_path)
 
-    def write_tree(r, bn, mtime):
-        t = r.tree(bn)
+    def write_tree(r: git.Repo, bn: str, mtime):
+        t: git.Tree = r.tree(bn)
 
         write_to("r/%s/b/%s/t/index.html" % (r.name, bn), tree, (r, bn), mtime)
 
diff --git a/git.py b/git.py
index 3b2b5b7..846c1a6 100644
--- a/git.py
+++ b/git.py
@@ -14,13 +14,16 @@ import email.utils
 import datetime
 import urllib.request, urllib.parse, urllib.error
 from html import escape
+from typing import Any, Dict, IO, Iterable, List, Optional, Tuple, Union
 
 
 # Path to the git binary.
 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.
 
     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,
         )
 
+        assert p.stdin is not None
         p.stdin.write(stdin)
         p.stdin.close()
 
+    assert p.stdout is not None
+
     if raw:
         return p.stdout
 
@@ -58,13 +64,13 @@ def run_git(repo_path, params, stdin=None, silent_stderr=False, raw=False):
 class GitCommand(object):
     """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._path = path
         self._cmd = cmd
-        self._args = list(args)
-        self._kwargs = {}
-        self._stdin_buf = None
+        self._args: List[str] = list(args)
+        self._kwargs: Dict[str, str] = {}
+        self._stdin_buf: Optional[bytes] = None
         self._raw = False
         self._override = False
         for k, v in kwargs:
@@ -77,17 +83,17 @@ class GitCommand(object):
         k = k.replace("_", "-")
         self._kwargs[k] = v
 
-    def arg(self, a):
+    def arg(self, a: str):
         """Adds an argument."""
         self._args.append(a)
 
-    def raw(self, b):
+    def raw(self, b: bool):
         """Request raw rather than utf8-encoded command output."""
         self._override = True
         self._raw = b
         self._override = False
 
-    def stdin(self, s):
+    def stdin(self, s: Union[str, bytes]):
         """Sets the contents we will send in stdin."""
         self._override = True
         if isinstance(s, str):
@@ -130,14 +136,14 @@ class smstr:
         .html    -> an HTML-embeddable representation.
     """
 
-    def __init__(self, raw):
+    def __init__(self, raw: str):
         if not isinstance(raw, (str, bytes)):
             raise TypeError(
                 "The raw string must be instance of 'str', not %s" % type(raw)
             )
         self.raw = raw
         if isinstance(raw, bytes):
-            self.unicode = raw.decode("utf8", errors="backslashreplace")
+            self.unicode: str = raw.decode("utf8", errors="backslashreplace")
         else:
             self.unicode = raw
         self.url = urllib.request.pathname2url(raw)
@@ -177,7 +183,7 @@ class smstr:
         return html
 
 
-def unquote(s):
+def unquote(s: str):
     """Git can return quoted file names, unquote them. Always return a str."""
     if not (s[0] == '"' and s[-1] == '"'):
         # Unquoted strings are always safe, no need to mess with them
@@ -204,10 +210,10 @@ def unquote(s):
 class Repo:
     """A git repository."""
 
-    def __init__(self, path, name=None, info=None):
+    def __init__(self, path: str, name=None, info=None):
         self.path = path
         self.name = name
-        self.info = info or SimpleNamespace()
+        self.info: Any = info or SimpleNamespace()
 
     def cmd(self, cmd):
         """Returns a GitCommand() on our path."""
@@ -535,11 +541,13 @@ class Diff:
 class Tree:
     """ A git tree."""
 
-    def __init__(self, repo, ref):
+    def __init__(self, repo: Repo, ref: str):
         self.repo = repo
         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."""
         cmd = self.repo.cmd("ls-tree")
         cmd.long = None
@@ -575,7 +583,7 @@ class Tree:
 class Blob:
     """A git blob."""
 
-    def __init__(self, raw_content):
+    def __init__(self, raw_content: bytes):
         self.raw_content = raw_content
         self._utf8_content = None
 
diff --git a/utils.py b/utils.py
index 0f25be1..9c2c45a 100644
--- a/utils.py
+++ b/utils.py
@@ -5,16 +5,16 @@ These are mostly used in templates, for presentation purposes.
 """
 
 try:
-    import pygments
-    from pygments import highlight
-    from pygments import lexers
-    from pygments.formatters import HtmlFormatter
+    import pygments  # type: ignore
+    from pygments import highlight  # type: ignore
+    from pygments import lexers  # type: ignore
+    from pygments.formatters import HtmlFormatter  # type: ignore
 except ImportError:
     pygments = None
 
 try:
-    import markdown
-    import markdown.treeprocessors
+    import markdown  # type: ignore
+    import markdown.treeprocessors  # type: ignore
 except ImportError:
     markdown = None
 
@@ -23,14 +23,16 @@ import mimetypes
 import string
 import os.path
 
+import git
 
-def shorten(s, width=60):
+
+def shorten(s: str, width=60):
     if len(s) < 60:
         return s
     return s[:57] + "..."
 
 
-def can_colorize(s):
+def can_colorize(s: str):
     """True if we can colorize the string, False otherwise."""
     if pygments is None:
         return False
@@ -54,7 +56,7 @@ def can_colorize(s):
     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."""
     if markdown is None:
         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")
     formatter = HtmlFormatter(encoding="utf-8", cssclass="source_code")
 
     return highlight(s, lexer, formatter)
 
 
-def colorize_blob(fname, s):
+def colorize_blob(fname, s: str) -> str:
     try:
         lexer = lexers.guess_lexer_for_filename(fname, s, encoding="utf-8")
     except lexers.ClassNotFound:
@@ -107,7 +109,7 @@ def colorize_blob(fname, s):
     return highlight(s, lexer, formatter)
 
 
-def markdown_blob(s):
+def markdown_blob(s: str) -> str:
     extensions = [
         "markdown.extensions.fenced_code",
         "markdown.extensions.tables",
@@ -116,7 +118,7 @@ def markdown_blob(s):
     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]
     b64img = base64.b64encode(image_data).decode("ascii")
     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.
-    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 + " "
-    s = s.decode("latin1")
+    b = s.decode("latin1")
     offset = 0
-    while s:
-        t = s[:16]
+    while b:
+        t = b[:16]
         hexvals = ["%.2x" % ord(c) 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
         offset += 16
-        s = s[16:]
+        b = b[16:]
 
 
 if markdown:
-- 
2.25.1

