main.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
  3. # Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
  4. """Create UML diagrams for classes and modules in <packages>."""
  5. from __future__ import annotations
  6. import sys
  7. from collections.abc import Sequence
  8. from typing import NoReturn
  9. from pylint import constants
  10. from pylint.config.arguments_manager import _ArgumentsManager
  11. from pylint.config.arguments_provider import _ArgumentsProvider
  12. from pylint.lint import discover_package_path
  13. from pylint.lint.utils import augmented_sys_path
  14. from pylint.pyreverse import writer
  15. from pylint.pyreverse.diadefslib import DiadefsHandler
  16. from pylint.pyreverse.inspector import Linker, project_from_files
  17. from pylint.pyreverse.utils import (
  18. check_graphviz_availability,
  19. check_if_graphviz_supports_format,
  20. insert_default_options,
  21. )
  22. from pylint.typing import Options
  23. DIRECTLY_SUPPORTED_FORMATS = (
  24. "dot",
  25. "vcg",
  26. "puml",
  27. "plantuml",
  28. "mmd",
  29. "html",
  30. )
  31. DEFAULT_COLOR_PALETTE = (
  32. "aliceblue",
  33. "antiquewhite",
  34. "aquamarine",
  35. "burlywood",
  36. "cadetblue",
  37. "chartreuse",
  38. "chocolate",
  39. "coral",
  40. "cornflowerblue",
  41. "cyan",
  42. "darkgoldenrod",
  43. "darkseagreen",
  44. "dodgerblue",
  45. "forestgreen",
  46. "gold",
  47. "hotpink",
  48. "mediumspringgreen",
  49. )
  50. OPTIONS: Options = (
  51. (
  52. "filter-mode",
  53. {
  54. "short": "f",
  55. "default": "PUB_ONLY",
  56. "dest": "mode",
  57. "type": "string",
  58. "action": "store",
  59. "metavar": "<mode>",
  60. "help": """filter attributes and functions according to
  61. <mode>. Correct modes are :
  62. 'PUB_ONLY' filter all non public attributes
  63. [DEFAULT], equivalent to PRIVATE+SPECIAL_A
  64. 'ALL' no filter
  65. 'SPECIAL' filter Python special functions
  66. except constructor
  67. 'OTHER' filter protected and private
  68. attributes""",
  69. },
  70. ),
  71. (
  72. "class",
  73. {
  74. "short": "c",
  75. "action": "extend",
  76. "metavar": "<class>",
  77. "type": "csv",
  78. "dest": "classes",
  79. "default": None,
  80. "help": "create a class diagram with all classes related to <class>;\
  81. this uses by default the options -ASmy",
  82. },
  83. ),
  84. (
  85. "show-ancestors",
  86. {
  87. "short": "a",
  88. "action": "store",
  89. "metavar": "<ancestor>",
  90. "type": "int",
  91. "default": None,
  92. "help": "show <ancestor> generations of ancestor classes not in <projects>",
  93. },
  94. ),
  95. (
  96. "all-ancestors",
  97. {
  98. "short": "A",
  99. "default": None,
  100. "action": "store_true",
  101. "help": "show all ancestors off all classes in <projects>",
  102. },
  103. ),
  104. (
  105. "show-associated",
  106. {
  107. "short": "s",
  108. "action": "store",
  109. "metavar": "<association_level>",
  110. "type": "int",
  111. "default": None,
  112. "help": "show <association_level> levels of associated classes not in <projects>",
  113. },
  114. ),
  115. (
  116. "all-associated",
  117. {
  118. "short": "S",
  119. "default": None,
  120. "action": "store_true",
  121. "help": "show recursively all associated off all associated classes",
  122. },
  123. ),
  124. (
  125. "show-builtin",
  126. {
  127. "short": "b",
  128. "action": "store_true",
  129. "default": False,
  130. "help": "include builtin objects in representation of classes",
  131. },
  132. ),
  133. (
  134. "module-names",
  135. {
  136. "short": "m",
  137. "default": None,
  138. "type": "yn",
  139. "metavar": "<y or n>",
  140. "help": "include module name in representation of classes",
  141. },
  142. ),
  143. (
  144. "only-classnames",
  145. {
  146. "short": "k",
  147. "action": "store_true",
  148. "default": False,
  149. "help": "don't show attributes and methods in the class boxes; this disables -f values",
  150. },
  151. ),
  152. (
  153. "output",
  154. {
  155. "short": "o",
  156. "dest": "output_format",
  157. "action": "store",
  158. "default": "dot",
  159. "metavar": "<format>",
  160. "type": "string",
  161. "help": (
  162. "create a *.<format> output file if format is available. Available "
  163. f"formats are: {', '.join(DIRECTLY_SUPPORTED_FORMATS)}. Any other "
  164. f"format will be tried to create by means of the 'dot' command line "
  165. f"tool, which requires a graphviz installation."
  166. ),
  167. },
  168. ),
  169. (
  170. "colorized",
  171. {
  172. "dest": "colorized",
  173. "action": "store_true",
  174. "default": False,
  175. "help": "Use colored output. Classes/modules of the same package get the same color.",
  176. },
  177. ),
  178. (
  179. "max-color-depth",
  180. {
  181. "dest": "max_color_depth",
  182. "action": "store",
  183. "default": 2,
  184. "metavar": "<depth>",
  185. "type": "int",
  186. "help": "Use separate colors up to package depth of <depth>",
  187. },
  188. ),
  189. (
  190. "color-palette",
  191. {
  192. "dest": "color_palette",
  193. "action": "store",
  194. "default": DEFAULT_COLOR_PALETTE,
  195. "metavar": "<color1,color2,...>",
  196. "type": "csv",
  197. "help": "Comma separated list of colors to use",
  198. },
  199. ),
  200. (
  201. "ignore",
  202. {
  203. "type": "csv",
  204. "metavar": "<file[,file...]>",
  205. "dest": "ignore_list",
  206. "default": constants.DEFAULT_IGNORE_LIST,
  207. "help": "Files or directories to be skipped. They should be base names, not paths.",
  208. },
  209. ),
  210. (
  211. "project",
  212. {
  213. "default": "",
  214. "type": "string",
  215. "short": "p",
  216. "metavar": "<project name>",
  217. "help": "set the project name.",
  218. },
  219. ),
  220. (
  221. "output-directory",
  222. {
  223. "default": "",
  224. "type": "path",
  225. "short": "d",
  226. "action": "store",
  227. "metavar": "<output_directory>",
  228. "help": "set the output directory path.",
  229. },
  230. ),
  231. (
  232. "source-roots",
  233. {
  234. "type": "glob_paths_csv",
  235. "metavar": "<path>[,<path>...]",
  236. "default": (),
  237. "help": "Add paths to the list of the source roots. Supports globbing patterns. The "
  238. "source root is an absolute path or a path relative to the current working directory "
  239. "used to determine a package namespace for modules located under the source root.",
  240. },
  241. ),
  242. )
  243. class Run(_ArgumentsManager, _ArgumentsProvider):
  244. """Base class providing common behaviour for pyreverse commands."""
  245. options = OPTIONS
  246. name = "pyreverse"
  247. def __init__(self, args: Sequence[str]) -> NoReturn:
  248. # Immediately exit if user asks for version
  249. if "--version" in args:
  250. print("pyreverse is included in pylint:")
  251. print(constants.full_version)
  252. sys.exit(0)
  253. _ArgumentsManager.__init__(self, prog="pyreverse", description=__doc__)
  254. _ArgumentsProvider.__init__(self, self)
  255. # Parse options
  256. insert_default_options()
  257. args = self._parse_command_line_configuration(args)
  258. if self.config.output_format not in DIRECTLY_SUPPORTED_FORMATS:
  259. check_graphviz_availability()
  260. print(
  261. f"Format {self.config.output_format} is not supported natively."
  262. " Pyreverse will try to generate it using Graphviz..."
  263. )
  264. check_if_graphviz_supports_format(self.config.output_format)
  265. sys.exit(self.run(args))
  266. def run(self, args: list[str]) -> int:
  267. """Checking arguments and run project."""
  268. if not args:
  269. print(self.help())
  270. return 1
  271. extra_packages_paths = list(
  272. {discover_package_path(arg, self.config.source_roots) for arg in args}
  273. )
  274. with augmented_sys_path(extra_packages_paths):
  275. project = project_from_files(
  276. args,
  277. project_name=self.config.project,
  278. black_list=self.config.ignore_list,
  279. )
  280. linker = Linker(project, tag=True)
  281. handler = DiadefsHandler(self.config)
  282. diadefs = handler.get_diadefs(project, linker)
  283. writer.DiagramWriter(self.config).write(diadefs)
  284. return 0
  285. if __name__ == "__main__":
  286. Run(sys.argv[1:])