From 72e3ee686a633a2ab9f50a3116a5ec775b50b407 Mon Sep 17 00:00:00 2001
From: gilch <gilch@users.noreply.github.com>
Date: Mon, 4 Oct 2021 16:21:48 -0600
Subject: [PATCH 1/3] Refactor transpile

---
 src/hissp/reader.py | 78 ++++++++++++++++-----------------------------
 1 file changed, 28 insertions(+), 50 deletions(-)

diff --git a/src/hissp/reader.py b/src/hissp/reader.py
index d83a4ad2e..4295b1145 100644
--- a/src/hissp/reader.py
+++ b/src/hissp/reader.py
@@ -14,9 +14,7 @@
 
 import ast
 import builtins
-import os
 import re
-import sys
 from collections import namedtuple
 from contextlib import contextmanager, nullcontext
 from functools import reduce
@@ -24,10 +22,9 @@
 from itertools import chain
 from keyword import iskeyword as _iskeyword
 from pathlib import Path, PurePath
-from pprint import pformat, pprint
+from pprint import pformat
 from threading import Lock
-from types import ModuleType
-from typing import Any, Iterable, Iterator, NewType, Optional, Tuple, Union
+from typing import Any, Iterable, Iterator, NewType, Tuple, Union
 
 from hissp.compiler import Compiler, MAYBE, readerless
 from hissp.munger import force_qz_encode, munge
@@ -437,54 +434,35 @@ def is_qualifiable(symbol):
     )
 
 
-def transpile(package: Optional[resources.Package], *modules: Union[str, PurePath]):
-    """
-    Compiles the named modules to Python files.
-    If the package is None or "", it uses the current working directory without using a package.
-    Lissp files must know their package at compile time to resolve imports correctly.
-    """
-    # TODO: allow pathname without + ".lissp"?
-    if package:
-        for module in modules:
-            transpile_module(package, module + ".lissp")
-    else:
-        for module in modules:
-            with open(module + ".lissp") as f:
-                code = f.read()
-            out = module + ".py"
-            _write_py(out, module, code)
-
-
-def transpile_module(
-    package: resources.Package,
-    resource: Union[str, PurePath],
-    out: Union[None, str, bytes, Path] = None,
-):
-    """Transpile a single submodule in a package."""
-    code = resources.read_text(package, resource)
-    path: Path
-    with resources.path(package, resource) as path:
-        out = out or path.with_suffix(".py")
-        if isinstance(package, ModuleType):
-            package = package.__package__
-        if isinstance(package, os.PathLike):
-            resource = resource.stem
-        _write_py(out, f"{package}.{resource.split('.')[0]}", code)
+def transpile(package, *modules):
+    """Transpiles the named Python modules from Lissp.
 
+    A .lissp file of the same name must be present. If the package is
+    empty, `transpile` writes modules to the current working
+    directory without a package.
+    """
+    t = transpile_package if package else transpile_file
+    for m in modules:
+        t(package, f"{m}.lissp")
 
-def _write_py(out, qualname, code):
-    with open(out, "w") as f:
-        print(f"compiling {qualname} as", out, file=sys.stderr)
-        if code.startswith("#!"):  # ignore shebang line
-            _, _, code = code.partition("\n")
-        f.write(Lissp(qualname, evaluate=True, filename=str(out)).compile(code))
 
+def transpile_package(package, resource):
+    """Locates & transpiles a packaged .lissp resource file to .py."""
+    with resources.path(package, resource) as path:
+        transpile_file(package, path)
 
-def main():
-    """Calls `transpile` with arguments from `sys.argv`."""
-    transpile(*sys.argv[1:])
 
+def transpile_file(package, path: Union[Path, str]):
+    """Transpiles a single .lissp file to .py in the same location.
 
-if __name__ == "__main__":
-    # TODO: test CLI
-    main()
+    Code in .lissp files is executed upon compilation. This is necessary
+    because macro definitions can alter the compilation of subsequent
+    top-level forms. Lissp files must know their package at compile time
+    to resolve imports correctly. The package can be empty.
+    """
+    path = Path(path).resolve(strict=True)
+    qualname = f"{package or ''}{'.' if package else ''}{PurePath(path.name).stem}"
+    python = Lissp(
+        qualname=qualname, evaluate=True, filename=str(path)
+    ).compile(re.sub(r'^#!.*\n', '', path.read_text()))
+    path.with_suffix('.py').write_text(python, 'utf8')

From 2169f145492525a86d9f2d88a22dcce4eb058d3f Mon Sep 17 00:00:00 2001
From: gilch <gilch@users.noreply.github.com>
Date: Mon, 4 Oct 2021 20:59:01 -0600
Subject: [PATCH 2/3] Update docs about transpile

---
 docs/command_line_reference.rst | 26 ++++++++++++++------------
 docs/faq.rst                    | 27 +++++++++++++++++++++------
 2 files changed, 35 insertions(+), 18 deletions(-)

diff --git a/docs/command_line_reference.rst b/docs/command_line_reference.rst
index 59598dc43..e23ecb7a7 100644
--- a/docs/command_line_reference.rst
+++ b/docs/command_line_reference.rst
@@ -32,15 +32,17 @@ whose minimal options were modeled after Python's most commonly used:
 The Lissp Compiler
 ------------------
 
-While the recommended way to compile Lissp modules is with
-`transpile <hissp.reader.transpile>`
-calls in the REPL, the main module, and the package ``__init__.py`` files,
-there is also a command-line interface in case that is needed by an external build system.
-
-The command usage is ``python -m hissp.reader package [module [module ...]]``,
-and it takes the same arguments as ``transpile``.
-
-The package can be an empty string for top-level modules.
-Remember to use the *module* names, not the *file* names.
-E.g. ``python -m hissp.reader "" spam eggs``,
-not ``python -m hissp.reader "" spam.lissp eggs.lissp``.
+The recommended way to compile Lissp modules is with
+`transpile <hissp.reader.transpile>` calls in `__init__.py` files (or the main module).
+
+This can be done manually in the REPL.
+However, an external build system may need to use shell commands.
+It is possible to run transpile commands in the shell via `python -c` or `lissp -c`.
+
+For example,
+
+.. code-block:: shell
+
+   $ alias lisspc="lissp -c '(hissp.reader..transpile_file None (getitem sys..argv -1))'"
+   $ lisspc spam.lissp
+
diff --git a/docs/faq.rst b/docs/faq.rst
index 5183f136b..5f7a848cc 100644
--- a/docs/faq.rst
+++ b/docs/faq.rst
@@ -1222,12 +1222,27 @@ Or equivalently, in Python:
 
    transpile(__package__, "sausage", "bacon")
 
-Consider putting the above in each package's ``__init__.py`` to
-auto-compile each Hissp module in the package on package import during
-development. You can disable it again on release, if desired, but this
-gives you fine-grained control over what gets compiled when. Note that
-you usually would want to recompile the whole project rather than only
-the changed files like Python does, because macros run at compile time.
+Consider putting the above in each package's ``__init__.py``
+to auto-compile each Hissp module in the package on package import during development.
+This gives you fine-grained control over what gets compiled when.
+If desired for a release,
+you can remove these lines after compilation,
+or disable them via some externally set parameter,
+such as an environment variable, config file, or global "constant".
+
+For example,
+
+.. code-block:: python
+
+   if __import__('os').getenv('AUTOCOMPILING_LISSP_PROJECT_FOO'):
+       from hissp.reader import transpile
+
+       transpile(__package__, "sausage", "bacon")
+
+
+Note that you usually *would* want to recompile the whole project
+rather than only the changed files like Python does,
+because macros run at compile time.
 Changing a macro in one file normally doesn't affect the code that uses
 it in other files until they are recompiled.
 

From afe73727e1190ae4156f2ab967b39b300f0f846e Mon Sep 17 00:00:00 2001
From: gilch <gilch@users.noreply.github.com>
Date: Mon, 4 Oct 2021 21:00:43 -0600
Subject: [PATCH 3/3] Remove raw_input() from sphinx docs

---
 src/hissp/repl.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/hissp/repl.py b/src/hissp/repl.py
index 6ca82045e..80808efd6 100644
--- a/src/hissp/repl.py
+++ b/src/hissp/repl.py
@@ -57,6 +57,7 @@ def runsource(self, source, filename="<input>", symbol="single"):
         return super().runsource(source, filename, symbol)
 
     def raw_input(self, prompt=""):
+        """:meta private:"""
         prompt = {sys.ps2: ps2, sys.ps1: ps1}.get(prompt, prompt)
         return super().raw_input(prompt)