main.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
  3. # Copyright (c) https://github.com/pylint-dev/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 pylint import constants
  9. from pylint.config.arguments_manager import _ArgumentsManager
  10. from pylint.config.arguments_provider import _ArgumentsProvider
  11. from pylint.lint import discover_package_path
  12. from pylint.lint.utils import augmented_sys_path
  13. from pylint.pyreverse import writer
  14. from pylint.pyreverse.diadefslib import DiadefsHandler
  15. from pylint.pyreverse.inspector import Linker, project_from_files
  16. from pylint.pyreverse.utils import (
  17. check_graphviz_availability,
  18. check_if_graphviz_supports_format,
  19. insert_default_options,
  20. )
  21. from pylint.typing import Options
  22. DIRECTLY_SUPPORTED_FORMATS = (
  23. "dot",
  24. "puml",
  25. "plantuml",
  26. "mmd",
  27. "html",
  28. )
  29. DEFAULT_COLOR_PALETTE = (
  30. # colorblind scheme taken from https://personal.sron.nl/~pault/
  31. "#77AADD", # light blue
  32. "#99DDFF", # light cyan
  33. "#44BB99", # mint
  34. "#BBCC33", # pear
  35. "#AAAA00", # olive
  36. "#EEDD88", # light yellow
  37. "#EE8866", # orange
  38. "#FFAABB", # pink
  39. "#DDDDDD", # pale grey
  40. )
  41. OPTIONS_GROUPS = {
  42. "FILTERING": "Filtering and Scope",
  43. "DISPLAY": "Display Options",
  44. "OUTPUT": "Output Control",
  45. "PROJECT": "Project Configuration",
  46. }
  47. OPTIONS: Options = (
  48. # Filtering and Scope options
  49. (
  50. "filter-mode",
  51. {
  52. "short": "f",
  53. "default": "PUB_ONLY",
  54. "dest": "mode",
  55. "type": "string",
  56. "action": "store",
  57. "metavar": "<mode>",
  58. "group": OPTIONS_GROUPS["FILTERING"],
  59. "help": """Filter attributes and functions according to <mode>. Correct modes are:
  60. 'PUB_ONLY' filter all non public attributes [DEFAULT], equivalent to PRIVATE+SPECIAL
  61. 'ALL' no filter
  62. 'SPECIAL' filter Python special functions except constructor
  63. 'OTHER' filter protected and private attributes""",
  64. },
  65. ),
  66. (
  67. "class",
  68. {
  69. "short": "c",
  70. "action": "extend",
  71. "metavar": "<class>",
  72. "type": "csv",
  73. "dest": "classes",
  74. "default": None,
  75. "group": OPTIONS_GROUPS["FILTERING"],
  76. "help": "Create a class diagram with all classes related to <class>;\
  77. this uses by default the options -ASmy",
  78. },
  79. ),
  80. (
  81. "show-ancestors",
  82. {
  83. "short": "a",
  84. "action": "store",
  85. "metavar": "<ancestor>",
  86. "type": "int",
  87. "default": None,
  88. "group": OPTIONS_GROUPS["FILTERING"],
  89. "help": "Show <ancestor> generations of ancestor classes not in <projects>.",
  90. },
  91. ),
  92. (
  93. "all-ancestors",
  94. {
  95. "short": "A",
  96. "default": None,
  97. "action": "store_true",
  98. "group": OPTIONS_GROUPS["FILTERING"],
  99. "help": "Show all ancestors of all classes in <projects>.",
  100. },
  101. ),
  102. (
  103. "show-associated",
  104. {
  105. "short": "s",
  106. "action": "store",
  107. "metavar": "<association_level>",
  108. "type": "int",
  109. "default": None,
  110. "group": OPTIONS_GROUPS["FILTERING"],
  111. "help": "Show <association_level> levels of associated classes not in <projects>.",
  112. },
  113. ),
  114. (
  115. "all-associated",
  116. {
  117. "short": "S",
  118. "default": None,
  119. "action": "store_true",
  120. "group": OPTIONS_GROUPS["FILTERING"],
  121. "help": "Show all classes associated with the target classes, including indirect associations.",
  122. },
  123. ),
  124. (
  125. "show-builtin",
  126. {
  127. "short": "b",
  128. "action": "store_true",
  129. "default": False,
  130. "group": OPTIONS_GROUPS["FILTERING"],
  131. "help": "Include builtin objects in representation of classes.",
  132. },
  133. ),
  134. (
  135. "show-stdlib",
  136. {
  137. "short": "L",
  138. "action": "store_true",
  139. "default": False,
  140. "group": OPTIONS_GROUPS["FILTERING"],
  141. "help": "Include standard library objects in representation of classes.",
  142. },
  143. ),
  144. (
  145. "max-depth",
  146. {
  147. "dest": "max_depth",
  148. "action": "store",
  149. "default": None,
  150. "metavar": "<depth>",
  151. "type": "int",
  152. "group": OPTIONS_GROUPS["FILTERING"],
  153. "help": (
  154. "Maximum depth of packages/modules to include in the diagram, relative to the "
  155. "deepest specified package. A depth of 0 shows only the specified packages/modules, "
  156. "while 1 includes their immediate children, etc. When specifying nested packages, "
  157. "depth is calculated from the deepest package level. If not specified, all "
  158. "packages/modules in the hierarchy are shown."
  159. ),
  160. },
  161. ),
  162. # Display Options
  163. (
  164. "module-names",
  165. {
  166. "short": "m",
  167. "default": None,
  168. "type": "yn",
  169. "metavar": "<y or n>",
  170. "group": OPTIONS_GROUPS["DISPLAY"],
  171. "help": "Include module name in the representation of classes.",
  172. },
  173. ),
  174. (
  175. "only-classnames",
  176. {
  177. "short": "k",
  178. "action": "store_true",
  179. "default": False,
  180. "group": OPTIONS_GROUPS["DISPLAY"],
  181. "help": "Don't show attributes and methods in the class boxes; this disables -f values.",
  182. },
  183. ),
  184. (
  185. "no-standalone",
  186. {
  187. "action": "store_true",
  188. "default": False,
  189. "group": OPTIONS_GROUPS["DISPLAY"],
  190. "help": "Only show nodes with connections.",
  191. },
  192. ),
  193. (
  194. "colorized",
  195. {
  196. "dest": "colorized",
  197. "action": "store_true",
  198. "default": False,
  199. "group": OPTIONS_GROUPS["DISPLAY"],
  200. "help": "Use colored output. Classes/modules of the same package get the same color.",
  201. },
  202. ),
  203. (
  204. "max-color-depth",
  205. {
  206. "dest": "max_color_depth",
  207. "action": "store",
  208. "default": 2,
  209. "metavar": "<depth>",
  210. "type": "int",
  211. "group": OPTIONS_GROUPS["DISPLAY"],
  212. "help": "Use separate colors up to package depth of <depth>. Higher depths will reuse colors.",
  213. },
  214. ),
  215. (
  216. "color-palette",
  217. {
  218. "dest": "color_palette",
  219. "action": "store",
  220. "default": DEFAULT_COLOR_PALETTE,
  221. "metavar": "<color1,color2,...>",
  222. "type": "csv",
  223. "group": OPTIONS_GROUPS["DISPLAY"],
  224. "help": "Comma separated list of colors to use for the package depth coloring.",
  225. },
  226. ),
  227. # Output Control options
  228. (
  229. "output",
  230. {
  231. "short": "o",
  232. "dest": "output_format",
  233. "action": "store",
  234. "default": "dot",
  235. "metavar": "<format>",
  236. "type": "string",
  237. "group": OPTIONS_GROUPS["OUTPUT"],
  238. "help": (
  239. "Create a *.<format> output file if format is available. Available "
  240. f"formats are: {', '.join('.' + fmt for fmt in DIRECTLY_SUPPORTED_FORMATS)}. Any other "
  241. "format will be tried to be created by using the 'dot' command line "
  242. "tool, which requires a graphviz installation. In this case, these additional "
  243. "formats are available (see `Graphviz output formats <https://graphviz.org/docs/outputs/>`_)."
  244. ),
  245. },
  246. ),
  247. (
  248. "output-directory",
  249. {
  250. "default": "",
  251. "type": "path",
  252. "short": "d",
  253. "action": "store",
  254. "metavar": "<output_directory>",
  255. "group": OPTIONS_GROUPS["OUTPUT"],
  256. "help": "Set the output directory path.",
  257. },
  258. ),
  259. # Project Configuration options
  260. (
  261. "ignore",
  262. {
  263. "type": "csv",
  264. "metavar": "<file[,file...]>",
  265. "dest": "ignore_list",
  266. "default": constants.DEFAULT_IGNORE_LIST,
  267. "group": OPTIONS_GROUPS["PROJECT"],
  268. "help": "Files or directories to be skipped. They should be base names, not paths.",
  269. },
  270. ),
  271. (
  272. "project",
  273. {
  274. "default": "",
  275. "type": "string",
  276. "short": "p",
  277. "metavar": "<project name>",
  278. "group": OPTIONS_GROUPS["PROJECT"],
  279. "help": "Set the project name. This will later be appended to the output file names.",
  280. },
  281. ),
  282. (
  283. "source-roots",
  284. {
  285. "type": "glob_paths_csv",
  286. "metavar": "<path>[,<path>...]",
  287. "default": (),
  288. "group": OPTIONS_GROUPS["PROJECT"],
  289. "help": "Add paths to the list of the source roots. Supports globbing patterns. The "
  290. "source root is an absolute path or a path relative to the current working directory "
  291. "used to determine a package namespace for modules located under the source root.",
  292. },
  293. ),
  294. (
  295. "verbose",
  296. {
  297. "action": "store_true",
  298. "default": False,
  299. "group": OPTIONS_GROUPS["PROJECT"],
  300. "help": "Makes pyreverse more verbose/talkative. Mostly useful for debugging.",
  301. },
  302. ),
  303. )
  304. # Base class providing common behaviour for pyreverse commands
  305. class Run(_ArgumentsManager, _ArgumentsProvider):
  306. options = OPTIONS
  307. name = "pyreverse"
  308. def __init__(self, args: Sequence[str]) -> None:
  309. # Immediately exit if user asks for version
  310. if "--version" in args:
  311. print("pyreverse is included in pylint:")
  312. print(constants.full_version)
  313. sys.exit(0)
  314. _ArgumentsManager.__init__(self, prog="pyreverse", description=__doc__)
  315. _ArgumentsProvider.__init__(self, self)
  316. # Parse options
  317. insert_default_options()
  318. self.args = self._parse_command_line_configuration(args)
  319. if self.config.output_format not in DIRECTLY_SUPPORTED_FORMATS:
  320. check_graphviz_availability()
  321. print(
  322. f"Format {self.config.output_format} is not supported natively."
  323. " Pyreverse will try to generate it using Graphviz..."
  324. )
  325. check_if_graphviz_supports_format(self.config.output_format)
  326. def run(self) -> int:
  327. """Checking arguments and run project."""
  328. if not self.args:
  329. print(self.help())
  330. return 1
  331. extra_packages_paths = list(
  332. {discover_package_path(arg, self.config.source_roots) for arg in self.args}
  333. )
  334. with augmented_sys_path(extra_packages_paths):
  335. project = project_from_files(
  336. self.args,
  337. project_name=self.config.project,
  338. black_list=self.config.ignore_list,
  339. verbose=self.config.verbose,
  340. )
  341. linker = Linker(project, tag=True)
  342. handler = DiadefsHandler(self.config, self.args)
  343. diadefs = handler.get_diadefs(project, linker)
  344. writer.DiagramWriter(self.config).write(diadefs)
  345. return 0
  346. if __name__ == "__main__":
  347. arguments = sys.argv[1:]
  348. Run(arguments).run()