Coverage for bookbuilderpy/versions.py: 91%

78 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-17 23:15 +0000

1"""Get the versions of all involved libraries and tools.""" 

2 

3import importlib.metadata as ilm 

4import platform 

5import subprocess # nosec 

6from typing import Final 

7 

8import bookbuilderpy.version as ver 

9from bookbuilderpy.logger import logger 

10 

11#: the name of the calibre executable tool 

12TOOL_CALIBRE: Final[str] = "calibre" 

13#: the name of the firefox driver tool 

14TOOL_FIREFOX_DRIVER: Final[str] = "geckodriver" 

15#: the name of the firefox browser executable tool 

16TOOL_FIREFOX: Final[str] = "firefox" 

17#: the name of the ghostscript executable tool 

18TOOL_GHOSTSCRIPT: Final[str] = "gs" 

19#: the name of the git executable tool 

20TOOL_GIT: Final[str] = "git" 

21#: the name of the pandoc executable tool 

22TOOL_PANDOC: Final[str] = "pandoc" 

23#: the name of the pdflatex executable tool 

24TOOL_PDFLATEX: Final[str] = "pdflatex" 

25#: the name of the rsvg-convert executable tool 

26TOOL_RSVG_CONVERT: Final[str] = "rsvg-convert" 

27#: the name of the tar executable tool 

28TOOL_TAR: Final[str] = "tar" 

29#: the name of the xelatex executable tool 

30TOOL_XELATEX: Final[str] = "xelatex" 

31#: the name of the xz executable tool 

32TOOL_XZ: Final[str] = "xz" 

33#: the name of the zip executable tool 

34TOOL_ZIP: Final[str] = "zip" 

35 

36 

37def __chkstr(n: str, 

38 purge_starts: tuple[str, ...] = 

39 ("copyright", "there is no", "covered by", 

40 "the lesser gnu g", "the gnu gen", "for more info", 

41 "named copying", "primary author", "currently maintaine", 

42 "the author", "latest sourc", "as of above", 

43 "encryption notice", "the encryption", "put in the", "and, to", 

44 "in both s", "the usa", "administration regulat", 

45 "this is free", "warranty", "the source ", "testing/gecko", 

46 "this program", "license", "you can obt", "written by", 

47 "no lsb mod", "(see the b", "bzip2 code ")) -> str | None: 

48 """ 

49 Check whether we should keep a version string. 

50 

51 :param n: the original string 

52 :param purge_starts: the strings to purge at the start 

53 :return: the string, or `None` if it can be purged 

54 """ 

55 n = n.strip() 

56 if len(n) <= 0: 

57 return None 

58 n = n.replace("\t", " ") 

59 nlen = len(n) 

60 while True: 

61 n = n.replace(" ", " ") 

62 nlen2 = len(n) 

63 if nlen2 >= nlen: 

64 break 

65 nlen = nlen2 

66 nl: Final[str] = n.lower() 

67 if any(nl.startswith(d) for d in purge_starts): 

68 return None 

69 return n 

70 

71 

72def _do_call(tool: str, arg: str) -> tuple[str, bool]: 

73 """ 

74 Invoke a sub-process. 

75 

76 :param tool: the tool 

77 :param arg: the argument 

78 :return: the output 

79 """ 

80 try: 

81 # nosemgrep 

82 ret = subprocess.run([tool, arg], check=False, # nosec # noqa 

83 text=True, capture_output=True, # nosec # noqa 

84 timeout=360) # nosec # noqa 

85 except FileNotFoundError: 

86 return f"{tool} not found", False 

87 except BaseException as be: 

88 return f"encountered {type(be)} when invoking {tool}", False 

89 

90 if ret.returncode != 0: 

91 return f"failed to invoke {tool}", False 

92 

93 lines = [a for a in [__chkstr(f) for f in ret.stdout.split("\n")] 

94 if a] 

95 if len(lines) <= 0: 

96 return f"{tool} query gives empty result", False 

97 return "\n".join(lines), True 

98 

99 

100class __Versions: 

101 """The internal singleton with the versions.""" 

102 

103 def __init__(self): 

104 """Initialize.""" 

105 #: the set of tool information 

106 self.__has_tool: Final[dict[str, tuple[str, bool]]] = {} 

107 #: the version string 

108 self.__versions: str | None = None 

109 

110 def has_tool(self, tool: str) -> bool: 

111 """ 

112 Check if the given tool is installed. 

113 

114 :param tool: the tool executable 

115 :return: `True` if the tool is installed, `False` otherwise. 

116 """ 

117 if tool in self.__has_tool: 

118 return self.__has_tool[tool][1] 

119 self.__has_tool[tool] = h = _do_call(tool, "--version") 

120 return h[1] 

121 

122 def get_versions(self) -> str: 

123 """ 

124 Get the versions of all involved libraries and tools. 

125 

126 :return: a string with version information of all libraries and tools 

127 """ 

128 if self.__versions: 

129 return self.__versions 

130 

131 logger("obtaining all version information.") 

132 versions: Final[list[str]] = \ 

133 [f"python version: {platform.python_version()}", 

134 f"python build: {platform.python_build()[1]}", 

135 f"python compiler: {platform.python_compiler()}", 

136 f"python implementation: {platform.python_implementation()}", 

137 f"bookbuilderpy: {ver.__version__}"] 

138 

139 for package in ["beautifulsoup4", "markdown", "minify_html", "pyyaml", 

140 "regex", "strip-hints", "urllib3", "yapf"]: 

141 version = ilm.version(package).strip() 

142 versions.append(f"package {package}: {version}") 

143 

144 versions.append(f"\nlinux: {_do_call('uname', '-a')[0]}") 

145 versions.append(_do_call("lsb_release", "-a")[0]) 

146 

147 for tool in [TOOL_CALIBRE, TOOL_FIREFOX, TOOL_FIREFOX_DRIVER, 

148 TOOL_GHOSTSCRIPT, TOOL_GIT, TOOL_PANDOC, 

149 TOOL_PDFLATEX, TOOL_RSVG_CONVERT, TOOL_TAR, 

150 TOOL_XELATEX, TOOL_XZ, TOOL_ZIP]: 

151 has: tuple[str, bool] 

152 if tool in self.__has_tool: 

153 has = self.__has_tool[tool] 

154 else: 

155 self.__has_tool[tool] = has = _do_call(tool, "--version") 

156 versions.append(f"\n{tool}: {has[0]}") 

157 

158 self.__versions = "\n".join(versions) 

159 return self.__versions 

160 

161 

162#: The shared internal singleton 

163__SINGLETON: Final[__Versions] = __Versions() 

164 

165 

166def has_tool(tool: str) -> bool: 

167 """ 

168 Check if the given tool is installed. 

169 

170 :param tool: the tool executable 

171 :return: `True` if the tool is installed, `False` otherwise. 

172 """ 

173 return __SINGLETON.has_tool(tool) 

174 

175 

176def get_versions() -> str: 

177 """ 

178 Get the versions of all involved libraries and tools. 

179 

180 :return: a string with version information of all libraries and tools 

181 """ 

182 return __SINGLETON.get_versions()