Skip to content

Instantly share code, notes, and snippets.

@c0m1c5an5
Created June 9, 2026 14:13
Show Gist options
  • Select an option

  • Save c0m1c5an5/e3c43b6a01e404003637603d5a1ab1f0 to your computer and use it in GitHub Desktop.

Select an option

Save c0m1c5an5/e3c43b6a01e404003637603d5a1ab1f0 to your computer and use it in GitHub Desktop.
Python rewrite of carlocorradini/inline
#!/usr/bin/env python3
"""Inline bash `source` / `.` directives into a single script."""
import argparse
import logging
import re
import shutil
import sys
from pathlib import Path
from typing import Optional
log = logging.getLogger("inline")
def setup_logging(verbosity: int) -> None:
if verbosity == 0:
root_level = logging.WARNING
tool_level = logging.WARNING
elif verbosity == 1:
root_level = logging.INFO
tool_level = logging.INFO
elif verbosity == 2:
root_level = logging.INFO
tool_level = logging.DEBUG
else:
root_level = logging.DEBUG
tool_level = logging.DEBUG
logging.root.setLevel(root_level)
logging.getLogger("inline").setLevel(tool_level)
fmt = logging.Formatter("%(asctime)s %(levelname)-8s %(message)-100s [%(filename)s:%(lineno)d]")
hdlr = logging.StreamHandler()
hdlr.setFormatter(fmt)
logging.root.addHandler(hdlr)
def parse_inline_skip(line: str) -> bool:
return bool(re.search(r"#\s*inline\s+skip", line))
def parse_shebang(line: str) -> bool:
return line.startswith("#!")
def parse_source(line: str) -> Optional[str]:
m = re.match(r"""^(?:\s*)(?:source|\.)\s+["']?([^"'\s]+)["']?""", line)
return m.group(1) if m else None
def parse_shellcheck_source(line: str) -> Optional[str]:
m = re.match(r'^\s*#\s*shellcheck\s+source="?([^"\s]+)"?', line)
return m.group(1) if m else None
def _resolve_source(ref: str, file_dir: Path, shellcheck_hint: Optional[str]) -> Optional[Path]:
if ref.startswith("$") and "/" in ref:
ref = str(file_dir / ref.split("/", 1)[1])
elif "/" not in ref:
log.debug("Searching $PATH for '%s'", ref)
found = shutil.which(ref)
if found:
return Path(found)
if not ref.startswith("/"):
ref = str(file_dir / ref)
path = Path(ref).expanduser().resolve()
log.debug("Path candidate '%s'", path)
if path.is_file():
return path
if shellcheck_hint:
log.debug("'%s' not found, trying shellcheck hint", path)
hint = shellcheck_hint if shellcheck_hint.startswith("/") else str(file_dir / shellcheck_hint)
hint_path = Path(hint).resolve()
log.debug("Path candidate '%s'", hint_path)
if hint_path.is_file():
return hint_path
return None
def _inline(file: Path, visited: set) -> list[str]:
file = file.resolve()
log.info("Reading '%s'", file)
visited.add(file)
out: list[str] = []
prev_line: Optional[str] = None
with file.open() as lines:
for line in lines:
line = line.rstrip("\n")
log.debug("Analyzing line '%s'", line)
if parse_shellcheck_source(line):
out.append(line)
elif ref := parse_source(line):
if parse_inline_skip(line):
log.warning("Skipping source '%s'", line)
out.append(line)
else:
hint = parse_shellcheck_source(prev_line) if prev_line else None
path = _resolve_source(ref, file.parent, hint)
if path is None:
log.error("Unable to resolve source file path '%s'", ref)
sys.exit(1)
log.debug("'%s' resolved to '%s'", ref, path)
out.append(f"# {line}")
if path in visited:
log.error("Recursion detected: '%s' sourced from '%s'", ref, file)
sys.exit(1)
out.append(f"# -----BEGIN {path}-----")
out.extend(l for l in _inline(path, visited) if not parse_shebang(l))
out.append(f"# -----END {path}-----")
else:
out.append(line)
prev_line = line
return out
def inline(file: Path) -> list:
return _inline(file.resolve(), set())
def main() -> None:
parser = argparse.ArgumentParser(description="Inline bash source directives into a single script.")
parser.add_argument("in_file", help="Input script")
parser.add_argument("--out-file", help="Output file")
parser.add_argument("--overwrite", action="store_true", help="Overwrite the input file")
parser.add_argument(
"-v", "--verbose", action="count", default=0, help="Increase verbosity (repeatable up to 3 times)."
)
args = parser.parse_args()
setup_logging(verbosity=args.verbose)
in_file = Path(args.in_file)
if not in_file.is_file():
log.error("Input file '%s' does not exist", in_file)
sys.exit(1)
if args.overwrite:
out_file = in_file
elif args.out_file:
out_file = Path(args.out_file)
else:
out_file = in_file.with_stem(in_file.stem + ".inlined")
if not args.overwrite and out_file.exists():
log.error("Output file '%s' already exists", out_file)
sys.exit(1)
log.info("Inlining '%s'", in_file)
result = inline(in_file)
log.info("Writing '%s'", out_file)
out_file.write_text("\n".join(result) + "\n")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment