Coverage for src / gitq / output.py: 100%

46 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-15 15:32 -0400

1import sys 

2import os 

3import re 

4import shlex 

5from contextlib import contextmanager 

6from typing import ContextManager, List, Any 

7from contextvars import ContextVar 

8 

9 

10class Output: 

11 

12 indentation: ContextVar[int] = ContextVar( 

13 "indentation", default=int(os.getenv("GITQ_INDENT", "0")) 

14 ) 

15 

16 @classmethod 

17 def indent(cls) -> ContextManager: 

18 return cls.heading() 

19 

20 @classmethod 

21 @contextmanager 

22 def heading(cls, message: str | None = None): 

23 n = cls.indentation.get() + 1 

24 if message is not None: 

25 print(" " * (n - 1) + "#" * n, message) 

26 sys.stdout.flush() 

27 token = cls.indentation.set(n) 

28 try: 

29 os.environ["GITQ_INDENT"] = str(n) 

30 yield 

31 finally: 

32 cls.indentation.reset(token) 

33 n = cls.indentation.get() 

34 if n == 0: 

35 del os.environ["GITQ_INDENT"] 

36 else: 

37 os.environ["GITQ_INDENT"] = str(n) 

38 

39 @classmethod 

40 def log_cmd(cls, cmd: List | str, comment: str = ""): 

41 def quote(x): 

42 return shlex.quote(re.sub(r"\n.*", "...", str(x), flags=re.DOTALL)) 

43 

44 if not isinstance(cmd, str): 

45 cmd = " ".join(map(quote, cmd)) 

46 if comment: 

47 cmd += " # " + comment 

48 print(" " * cls.indentation.get() + "+", cmd) 

49 sys.stdout.flush() 

50 

51 @classmethod 

52 def print(cls, *args: Any) -> None: 

53 for line in " ".join(map(str, args)).splitlines(): 

54 print(" " * cls.indentation.get() + line) 

55 

56 @classmethod 

57 def flush(cls): 

58 sys.stdout.flush() 

59 sys.stderr.flush()