djlint 1.28.0 → 1.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (368) hide show
  1. package/.coveragerc +9 -0
  2. package/.editorconfig +21 -0
  3. package/.flake8 +0 -0
  4. package/.github/FUNDING.yml +2 -0
  5. package/.github/ISSUE_TEMPLATE/config.yml +6 -0
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
  7. package/.github/ISSUE_TEMPLATE/formatter_but_report.md +30 -0
  8. package/.github/ISSUE_TEMPLATE/linter_bug_report.md +34 -0
  9. package/.github/ISSUE_TEMPLATE/translation.md +14 -0
  10. package/.github/config.yml +4 -0
  11. package/.github/pull_request_template.md +10 -0
  12. package/.github/workflows/cancel.yml +17 -0
  13. package/.github/workflows/demo.yml +98 -0
  14. package/.github/workflows/issues.yml +23 -0
  15. package/.github/workflows/linkcheck.yaml +21 -0
  16. package/.github/workflows/lint.yml +25 -0
  17. package/.github/workflows/release.yml +57 -0
  18. package/.github/workflows/test.yml +85 -0
  19. package/.pre-commit-config.yaml +45 -0
  20. package/.pre-commit-hooks.yaml +71 -0
  21. package/.prettierignore +7 -0
  22. package/.prettierrc +11 -0
  23. package/.releaserc +16 -0
  24. package/CHANGELOG.md +13 -0
  25. package/MANIFEST.in +2 -0
  26. package/build_demo.sh +17 -0
  27. package/docs/.eleventy.js +425 -0
  28. package/docs/.eleventyignore +2 -0
  29. package/docs/.prettierignore +3 -0
  30. package/docs/.prettierrc +11 -0
  31. package/docs/package.json +73 -0
  32. package/docs/readme.md +17 -0
  33. package/docs/src/_data/configuration.json +646 -0
  34. package/docs/src/_data/eleventyComputed.js +22 -0
  35. package/docs/src/_data/i18n/index.js +159 -0
  36. package/docs/src/_data/js.js +7 -0
  37. package/docs/src/_data/locales.js +17 -0
  38. package/docs/src/_data/site.json +8 -0
  39. package/docs/src/_includes/demo.njk +157 -0
  40. package/docs/src/_includes/docs_layout.njk +34 -0
  41. package/docs/src/_includes/foot.njk +26 -0
  42. package/docs/src/_includes/index.njk +187 -0
  43. package/docs/src/_includes/languages_layout.njk +11 -0
  44. package/docs/src/_includes/layout.njk +111 -0
  45. package/docs/src/_includes/nav.njk +85 -0
  46. package/docs/src/_utils/minify-html.js +14 -0
  47. package/docs/src/_utils/rollupper.js +91 -0
  48. package/docs/src/_utils/scripts.11ty.js +27 -0
  49. package/docs/src/demo.njk +4 -0
  50. package/docs/src/docs/best-practices.md +78 -0
  51. package/docs/src/docs/changelog.md +452 -0
  52. package/docs/src/docs/configuration.md +145 -0
  53. package/docs/src/docs/docs.json +4 -0
  54. package/docs/src/docs/formatter.md +82 -0
  55. package/docs/src/docs/getting-started.md +126 -0
  56. package/docs/src/docs/ignoring-code.md +75 -0
  57. package/docs/src/docs/integrations.md +120 -0
  58. package/docs/src/docs/languages/angular.md +31 -0
  59. package/docs/src/docs/languages/django.md +40 -0
  60. package/docs/src/docs/languages/golang.md +31 -0
  61. package/docs/src/docs/languages/handlebars.md +31 -0
  62. package/docs/src/docs/languages/jinja.md +39 -0
  63. package/docs/src/docs/languages/languages.json +3 -0
  64. package/docs/src/docs/languages/mustache.md +31 -0
  65. package/docs/src/docs/languages/nunjucks.md +39 -0
  66. package/docs/src/docs/languages/twig.md +41 -0
  67. package/docs/src/docs/linter.md +199 -0
  68. package/docs/src/fr/demo.njk +4 -0
  69. package/docs/src/fr/docs/best-practices.md +78 -0
  70. package/docs/src/fr/docs/changelog.md +452 -0
  71. package/docs/src/fr/docs/configuration.md +144 -0
  72. package/docs/src/fr/docs/docs.json +3 -0
  73. package/docs/src/fr/docs/formatter.md +83 -0
  74. package/docs/src/fr/docs/getting-started.md +127 -0
  75. package/docs/src/fr/docs/ignoring-code.md +75 -0
  76. package/docs/src/fr/docs/integrations.md +100 -0
  77. package/docs/src/fr/docs/languages/angular.md +31 -0
  78. package/docs/src/fr/docs/languages/django.md +31 -0
  79. package/docs/src/fr/docs/languages/golang.md +31 -0
  80. package/docs/src/fr/docs/languages/handlebars.md +31 -0
  81. package/docs/src/fr/docs/languages/jinja.md +31 -0
  82. package/docs/src/fr/docs/languages/languages.json +3 -0
  83. package/docs/src/fr/docs/languages/mustache.md +31 -0
  84. package/docs/src/fr/docs/languages/nunjucks.md +31 -0
  85. package/docs/src/fr/docs/languages/twig.md +31 -0
  86. package/docs/src/fr/docs/linter.md +207 -0
  87. package/docs/src/fr/fr.json +4 -0
  88. package/docs/src/fr/index.njk +4 -0
  89. package/docs/src/index.njk +4 -0
  90. package/docs/src/lib/generate-content-hash.js +13 -0
  91. package/docs/src/robots.txt +4 -0
  92. package/docs/src/ru/demo.njk +4 -0
  93. package/docs/src/ru/docs/best-practices.md +78 -0
  94. package/docs/src/ru/docs/changelog.md +453 -0
  95. package/docs/src/ru/docs/configuration.md +142 -0
  96. package/docs/src/ru/docs/docs.json +3 -0
  97. package/docs/src/ru/docs/formatter.md +82 -0
  98. package/docs/src/ru/docs/getting-started.md +127 -0
  99. package/docs/src/ru/docs/ignoring-code.md +75 -0
  100. package/docs/src/ru/docs/integrations.md +100 -0
  101. package/docs/src/ru/docs/languages/angular.md +31 -0
  102. package/docs/src/ru/docs/languages/django.md +31 -0
  103. package/docs/src/ru/docs/languages/golang.md +31 -0
  104. package/docs/src/ru/docs/languages/handlebars.md +31 -0
  105. package/docs/src/ru/docs/languages/jinja.md +31 -0
  106. package/docs/src/ru/docs/languages/languages.json +3 -0
  107. package/docs/src/ru/docs/languages/mustache.md +31 -0
  108. package/docs/src/ru/docs/languages/nunjucks.md +31 -0
  109. package/docs/src/ru/docs/languages/twig.md +31 -0
  110. package/docs/src/ru/docs/linter.md +126 -0
  111. package/docs/src/ru/index.njk +4 -0
  112. package/docs/src/ru/ru.json +4 -0
  113. package/docs/src/sitemap.njk +16 -0
  114. package/docs/src/static/css/site.scss +467 -0
  115. package/docs/src/static/font/fontawesome/stylesheet.scss +78 -0
  116. package/docs/src/static/img/apple-touch-icon.png +0 -0
  117. package/docs/src/static/img/demo-min.gif +0 -0
  118. package/docs/src/static/img/demo.gif +0 -0
  119. package/docs/src/static/img/demo.webm +0 -0
  120. package/docs/src/static/img/favicon-16x16.png +0 -0
  121. package/docs/src/static/img/favicon-32x32.png +0 -0
  122. package/docs/src/static/img/favicon.ico +0 -0
  123. package/docs/src/static/img/icon-square.indd +0 -0
  124. package/docs/src/static/img/icon-square.png +0 -0
  125. package/docs/src/static/img/icon.png +0 -0
  126. package/docs/src/static/img/logo-192x192.png +0 -0
  127. package/docs/src/static/img/logo-512x512.png +0 -0
  128. package/docs/src/static/img/logos/angular.png +0 -0
  129. package/docs/src/static/img/logos/django.png +0 -0
  130. package/docs/src/static/img/logos/golang.png +0 -0
  131. package/docs/src/static/img/logos/handlebars.png +0 -0
  132. package/docs/src/static/img/logos/jinja.png +0 -0
  133. package/docs/src/static/img/logos/mustache.png +0 -0
  134. package/docs/src/static/img/logos/nunjucks.png +0 -0
  135. package/docs/src/static/img/logos/twig.png +0 -0
  136. package/docs/src/static/img/old_demo.gif +0 -0
  137. package/docs/src/static/img/style.css +122 -0
  138. package/docs/src/static/js/animate.js +69 -0
  139. package/docs/src/static/js/editor.js +171 -0
  140. package/docs/src/static/js/hamburger.js +19 -0
  141. package/docs/src/static/js/modal.js +47 -0
  142. package/docs/src/static/js/worker.js +142 -0
  143. package/docs/src/static/py/EditorConfig-99-py3-none-any.whl +0 -0
  144. package/docs/src/static/py/PyYAML-99-py3-none-any.whl +0 -0
  145. package/docs/src/static/py/click-99-py3-none-any.whl +0 -0
  146. package/docs/src/static/py/colorama-99-py3-none-any.whl +0 -0
  147. package/docs/src/static/py/cssbeautifier-99-py3-none-any.whl +0 -0
  148. package/docs/src/static/py/djlint-99-py3-none-any.whl +0 -0
  149. package/docs/src/static/py/html_tag_names-99-py3-none-any.whl +0 -0
  150. package/docs/src/static/py/html_void_elements-99-py3-none-any.whl +0 -0
  151. package/docs/src/static/py/jsbeautifier-99-py3-none-any.whl +0 -0
  152. package/docs/src/static/py/json5-99-py3-none-any.whl +0 -0
  153. package/docs/src/static/py/pathspec-99-py3-none-any.whl +0 -0
  154. package/docs/src/static/py/regex-2023.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +0 -0
  155. package/docs/src/static/py/six-1.16.0-py2.py3-none-any.whl +0 -0
  156. package/docs/src/static/py/tomli-2.0.1-py3-none-any.whl +0 -0
  157. package/docs/src/static/py/tqdm-4.65.0-py3-none-any.whl +0 -0
  158. package/icon.xcf +0 -0
  159. package/package.json +4 -33
  160. package/poetry.lock +835 -0
  161. package/pyproject.toml +100 -0
  162. package/renovate.json +44 -0
  163. package/scripts/updateVersion.js +16 -0
  164. package/src/djlint/__init__.py +451 -0
  165. package/src/djlint/__main__.py +4 -0
  166. package/src/djlint/formatter/__init__.py +1 -0
  167. package/src/djlint/formatter/attributes.py +212 -0
  168. package/src/djlint/formatter/compress.py +64 -0
  169. package/src/djlint/formatter/condense.py +221 -0
  170. package/src/djlint/formatter/css.py +60 -0
  171. package/src/djlint/formatter/expand.py +111 -0
  172. package/src/djlint/formatter/indent.py +418 -0
  173. package/src/djlint/formatter/js.py +58 -0
  174. package/src/djlint/helpers.py +321 -0
  175. package/src/djlint/lint.py +128 -0
  176. package/src/djlint/output.py +220 -0
  177. package/src/djlint/reformat.py +63 -0
  178. package/src/djlint/rules/H025.py +65 -0
  179. package/src/djlint/rules.yaml +279 -0
  180. package/src/djlint/settings.py +969 -0
  181. package/src/djlint/src.py +91 -0
  182. package/tests/__init__.py +0 -0
  183. package/tests/conftest.py +239 -0
  184. package/tests/test_cli.py +71 -0
  185. package/tests/test_config/__init__.py +0 -0
  186. package/tests/test_config/test_blank_line_after_tag.py +135 -0
  187. package/tests/test_config/test_blank_line_before_tag.py +141 -0
  188. package/tests/test_config/test_close_void_tags.py +31 -0
  189. package/tests/test_config/test_custom_blocks.py +56 -0
  190. package/tests/test_config/test_custom_html.py +45 -0
  191. package/tests/test_config/test_djlintrc/.djlintrc +38 -0
  192. package/tests/test_config/test_djlintrc/__init__.py +0 -0
  193. package/tests/test_config/test_djlintrc/test_config.py +44 -0
  194. package/tests/test_config/test_djlintrc_custom/.djlint-cust +38 -0
  195. package/tests/test_config/test_djlintrc_custom/.djlintrc +2 -0
  196. package/tests/test_config/test_djlintrc_custom/__init__.py +0 -0
  197. package/tests/test_config/test_djlintrc_custom/test_config.py +48 -0
  198. package/tests/test_config/test_excludes/__init__.py +0 -0
  199. package/tests/test_config/test_excludes/excluded.html +1 -0
  200. package/tests/test_config/test_excludes/foo/excluded.html +1 -0
  201. package/tests/test_config/test_excludes/html.html +1 -0
  202. package/tests/test_config/test_excludes/pyproject.toml +3 -0
  203. package/tests/test_config/test_excludes/test_config.py +30 -0
  204. package/tests/test_config/test_extension/__init__.py +0 -0
  205. package/tests/test_config/test_extension/html.test +1 -0
  206. package/tests/test_config/test_extension/pyproject.toml +3 -0
  207. package/tests/test_config/test_extension/test_config.py +27 -0
  208. package/tests/test_config/test_files/.djlintrc_global +9 -0
  209. package/tests/test_config/test_files/test.html +3 -0
  210. package/tests/test_config/test_files/test_config.py +123 -0
  211. package/tests/test_config/test_files/test_two.html +1 -0
  212. package/tests/test_config/test_format_attribute_template_tags.py +280 -0
  213. package/tests/test_config/test_format_css.py +197 -0
  214. package/tests/test_config/test_format_js.py +220 -0
  215. package/tests/test_config/test_gitignore/__init__.py +0 -0
  216. package/tests/test_config/test_gitignore/html_one.html +1 -0
  217. package/tests/test_config/test_gitignore/html_two.html +1 -0
  218. package/tests/test_config/test_gitignore/test_config.py +142 -0
  219. package/tests/test_config/test_ignore/__init__.py +0 -0
  220. package/tests/test_config/test_ignore/html.html +1 -0
  221. package/tests/test_config/test_ignore/html_two.html +12 -0
  222. package/tests/test_config/test_ignore/pyproject.toml +3 -0
  223. package/tests/test_config/test_ignore/test_config.py +32 -0
  224. package/tests/test_config/test_ignore_blocks.py +45 -0
  225. package/tests/test_config/test_ignore_case.py +39 -0
  226. package/tests/test_config/test_indent.py +63 -0
  227. package/tests/test_config/test_linter_output_format/__init__.py +0 -0
  228. package/tests/test_config/test_linter_output_format/html-one.html +1 -0
  229. package/tests/test_config/test_linter_output_format/html-two.html +1 -0
  230. package/tests/test_config/test_linter_output_format/pyproject.toml +3 -0
  231. package/tests/test_config/test_linter_output_format/test_config.py +34 -0
  232. package/tests/test_config/test_max_attribute_length.py +100 -0
  233. package/tests/test_config/test_max_line_length.py +94 -0
  234. package/tests/test_config/test_per_file_ignores/__init__.py +0 -0
  235. package/tests/test_config/test_per_file_ignores/html.html +2 -0
  236. package/tests/test_config/test_per_file_ignores/pyproject.toml +2 -0
  237. package/tests/test_config/test_per_file_ignores/test_config.py +25 -0
  238. package/tests/test_config/test_pragmas/__init__.py +0 -0
  239. package/tests/test_config/test_pragmas/html_five.html +2 -0
  240. package/tests/test_config/test_pragmas/html_four.html +5 -0
  241. package/tests/test_config/test_pragmas/html_one.html +1 -0
  242. package/tests/test_config/test_pragmas/html_six.html +2 -0
  243. package/tests/test_config/test_pragmas/html_three.html +4 -0
  244. package/tests/test_config/test_pragmas/html_two.html +2 -0
  245. package/tests/test_config/test_pragmas/pyproject.toml +3 -0
  246. package/tests/test_config/test_pragmas/test_config.py +142 -0
  247. package/tests/test_config/test_preserve_blank_lines.py +109 -0
  248. package/tests/test_config/test_preserve_leading_space.py +159 -0
  249. package/tests/test_config/test_profile/__init__.py +0 -0
  250. package/tests/test_config/test_profile/html.html +2 -0
  251. package/tests/test_config/test_profile/pyproject.toml +3 -0
  252. package/tests/test_config/test_profile/test_config.py +64 -0
  253. package/tests/test_config/test_pyproject/__init__.py +0 -0
  254. package/tests/test_config/test_pyproject/blank.html +0 -0
  255. package/tests/test_config/test_pyproject/pyproject.toml +36 -0
  256. package/tests/test_config/test_pyproject/test_config.py +44 -0
  257. package/tests/test_django/__init__.py +0 -0
  258. package/tests/test_django/test_asset.py +32 -0
  259. package/tests/test_django/test_autoescape.py +25 -0
  260. package/tests/test_django/test_block.py +29 -0
  261. package/tests/test_django/test_blocktrans.py +236 -0
  262. package/tests/test_django/test_blocktrans_trimmed.py +283 -0
  263. package/tests/test_django/test_comments.py +164 -0
  264. package/tests/test_django/test_filter.py +30 -0
  265. package/tests/test_django/test_for.py +34 -0
  266. package/tests/test_django/test_if.py +34 -0
  267. package/tests/test_django/test_ifchanged.py +37 -0
  268. package/tests/test_django/test_include.py +24 -0
  269. package/tests/test_django/test_load.py +24 -0
  270. package/tests/test_django/test_quoted.py +34 -0
  271. package/tests/test_django/test_spaceless.py +30 -0
  272. package/tests/test_django/test_tag_spaces.py +35 -0
  273. package/tests/test_django/test_templatetag.py +40 -0
  274. package/tests/test_django/test_verbatim.py +24 -0
  275. package/tests/test_django/test_with.py +32 -0
  276. package/tests/test_djlint/-.html +1 -0
  277. package/tests/test_djlint/__init__.py +0 -0
  278. package/tests/test_djlint/bad.html +6 -0
  279. package/tests/test_djlint/bad.html.dj +23 -0
  280. package/tests/test_djlint/bad.html.dj2 +2 -0
  281. package/tests/test_djlint/multiple_files/a/a.html +1 -0
  282. package/tests/test_djlint/multiple_files/b/b1.html +1 -0
  283. package/tests/test_djlint/multiple_files/b/b2.html +1 -0
  284. package/tests/test_djlint/test_djlint.py +242 -0
  285. package/tests/test_golang/__init__.py +0 -0
  286. package/tests/test_golang/test_if.py +24 -0
  287. package/tests/test_golang/test_range.py +24 -0
  288. package/tests/test_handlebars/__init__.py +0 -0
  289. package/tests/test_handlebars/test_each.py +34 -0
  290. package/tests/test_handlebars/test_else.py +24 -0
  291. package/tests/test_handlebars/test_with.py +24 -0
  292. package/tests/test_html/__init__.py +0 -0
  293. package/tests/test_html/test_alpinejs.py +65 -0
  294. package/tests/test_html/test_attributes.py +935 -0
  295. package/tests/test_html/test_aurelia.py +24 -0
  296. package/tests/test_html/test_basics.py +905 -0
  297. package/tests/test_html/test_bracket_same_line.py +177 -0
  298. package/tests/test_html/test_case.py +81 -0
  299. package/tests/test_html/test_cdata.py +34 -0
  300. package/tests/test_html/test_comments.py +333 -0
  301. package/tests/test_html/test_css.py +193 -0
  302. package/tests/test_html/test_doctype_declarations.py +202 -0
  303. package/tests/test_html/test_ignored.py +193 -0
  304. package/tests/test_html/test_interpolation.py +56 -0
  305. package/tests/test_html/test_line_break_after_multiline_tag.py +95 -0
  306. package/tests/test_html/test_next_line_empty.py +53 -0
  307. package/tests/test_html/test_selfclosing.py +55 -0
  308. package/tests/test_html/test_single_attribute_per_line.py +87 -0
  309. package/tests/test_html/test_srcset.py +43 -0
  310. package/tests/test_html/test_svg.py +104 -0
  311. package/tests/test_html/test_symbol_entities.py +34 -0
  312. package/tests/test_html/test_tag_code.py +24 -0
  313. package/tests/test_html/test_tag_dd.py +24 -0
  314. package/tests/test_html/test_tag_details_summary.py +24 -0
  315. package/tests/test_html/test_tag_dt.py +24 -0
  316. package/tests/test_html/test_tag_fig_caption.py +29 -0
  317. package/tests/test_html/test_tag_hr.py +29 -0
  318. package/tests/test_html/test_tag_picture.py +32 -0
  319. package/tests/test_html/test_tag_pre.py +45 -0
  320. package/tests/test_html/test_tag_script.py +516 -0
  321. package/tests/test_html/test_tag_small.py +24 -0
  322. package/tests/test_html/test_tag_span.py +119 -0
  323. package/tests/test_html/test_tag_textarea.py +82 -0
  324. package/tests/test_html/test_tags.py +842 -0
  325. package/tests/test_html/test_text.py +34 -0
  326. package/tests/test_html/test_whitespace.py +477 -0
  327. package/tests/test_html/test_yaml.py +156 -0
  328. package/tests/test_jinja/__init__.py +0 -0
  329. package/tests/test_jinja/test_call.py +34 -0
  330. package/tests/test_jinja/test_parenthesis.py +24 -0
  331. package/tests/test_linter/__init__.py +0 -0
  332. package/tests/test_linter/test_custom_rules/.djlint_rules.yaml +6 -0
  333. package/tests/test_linter/test_custom_rules/__init__.py +0 -0
  334. package/tests/test_linter/test_custom_rules/html.html +1 -0
  335. package/tests/test_linter/test_custom_rules/pyproject.toml +1 -0
  336. package/tests/test_linter/test_custom_rules/test_linter.py +29 -0
  337. package/tests/test_linter/test_custom_rules_bad/.djlint_rules.yaml +24 -0
  338. package/tests/test_linter/test_custom_rules_bad/__init__.py +0 -0
  339. package/tests/test_linter/test_custom_rules_bad/html.html +1 -0
  340. package/tests/test_linter/test_custom_rules_bad/pyproject.toml +1 -0
  341. package/tests/test_linter/test_custom_rules_bad/test_linter.py +29 -0
  342. package/tests/test_linter/test_django_linter.py +476 -0
  343. package/tests/test_linter/test_h005.py +45 -0
  344. package/tests/test_linter/test_h006.py +81 -0
  345. package/tests/test_linter/test_h007.py +45 -0
  346. package/tests/test_linter/test_h008.py +60 -0
  347. package/tests/test_linter/test_h009.py +45 -0
  348. package/tests/test_linter/test_h010.py +44 -0
  349. package/tests/test_linter/test_h037.py +150 -0
  350. package/tests/test_linter/test_ignore_rules.py +45 -0
  351. package/tests/test_linter/test_jinja_linter.py +227 -0
  352. package/tests/test_linter/test_linter.py +691 -0
  353. package/tests/test_linter/test_nunjucks_linter.py +59 -0
  354. package/tests/test_linter/test_python_module_rules/.djlint_rules.yaml +4 -0
  355. package/tests/test_linter/test_python_module_rules/__init__.py +0 -0
  356. package/tests/test_linter/test_python_module_rules/html_bad.html +3 -0
  357. package/tests/test_linter/test_python_module_rules/html_ok.html +3 -0
  358. package/tests/test_linter/test_python_module_rules/my_module.py +32 -0
  359. package/tests/test_linter/test_python_module_rules/pyproject.toml +1 -0
  360. package/tests/test_linter/test_python_module_rules/test_linter.py +26 -0
  361. package/tests/test_nunjucks/__init__.py +0 -0
  362. package/tests/test_nunjucks/test_functions.py +94 -0
  363. package/tests/test_nunjucks/test_macros.py +41 -0
  364. package/tests/test_nunjucks/test_set.py +206 -0
  365. package/tests/test_nunjucks/test_spaceless.py +24 -0
  366. package/tests/test_twig/__init__.py +0 -0
  367. package/tests/test_twig/test_comments.py +51 -0
  368. package/tox.ini +48 -0
@@ -0,0 +1,91 @@
1
+ """Build src file list."""
2
+ import re
3
+ from pathlib import Path
4
+ from typing import List
5
+
6
+ from click import echo
7
+ from colorama import Fore
8
+
9
+ from .settings import Config
10
+
11
+
12
+ def get_src(src: List[Path], config: Config) -> List[Path]:
13
+ """Get source files."""
14
+ paths = []
15
+ for item in src:
16
+ # normalize path
17
+
18
+ normalized_item = item.resolve()
19
+
20
+ if (
21
+ Path.is_file(normalized_item)
22
+ and no_pragma(config, normalized_item)
23
+ and (
24
+ (
25
+ config.use_gitignore
26
+ and not config.gitignore.match_file(normalized_item)
27
+ )
28
+ or not config.use_gitignore
29
+ )
30
+ ):
31
+ paths.append(normalized_item)
32
+ continue
33
+
34
+ # remove leading . from extension
35
+ extension = str(config.extension)
36
+ extension = extension[1:] if extension.startswith(".") else extension
37
+
38
+ paths.extend(
39
+ filter(
40
+ lambda x: not re.search(config.exclude, x.as_posix(), re.VERBOSE)
41
+ and no_pragma(config, x)
42
+ and (
43
+ (config.use_gitignore and not config.gitignore.match_file(x))
44
+ or not config.use_gitignore
45
+ ),
46
+ list(normalized_item.glob(f"**/*.{extension}")),
47
+ )
48
+ )
49
+
50
+ if len(paths) == 0:
51
+ echo(Fore.BLUE + "No files to check! 😢")
52
+
53
+ return paths
54
+
55
+
56
+ html_patterns = [re.compile(r"<!--\s*djlint\:on\s*-->")]
57
+ django_jinja_patterns = [
58
+ re.compile(r"\{#\s*djlint\:on\s*#\}"),
59
+ re.compile(r"\{%\s*comment\s*%\}\s*djlint\:on\s*\{%\s*endcomment\s*%\}"),
60
+ ]
61
+ nunjucks_patterns = [re.compile(r"\{#\s*djlint\:on\s*#\}")]
62
+ handlebars_patterns = [re.compile(r"\{\{!--\s*djlint\:on\s*--\}\}")]
63
+ golang_patterns = [re.compile(r"\{\{-?\s*/\*\s*djlint\:on\s*\*/\s*-?\}\}")]
64
+
65
+
66
+ def no_pragma(config: Config, this_file: Path) -> bool:
67
+ """Verify there is no pragma present."""
68
+ if config.require_pragma is False:
69
+ return True
70
+
71
+ with this_file.open(encoding="utf8") as open_file:
72
+ first_line = open_file.readline()
73
+
74
+ pragma_patterns = {
75
+ "html": html_patterns,
76
+ "django": django_jinja_patterns + html_patterns,
77
+ "jinja": django_jinja_patterns + html_patterns,
78
+ "nunjucks": nunjucks_patterns + html_patterns,
79
+ "handlebars": handlebars_patterns + html_patterns,
80
+ "golang": golang_patterns + html_patterns,
81
+ "angular": html_patterns,
82
+ "all": django_jinja_patterns
83
+ + nunjucks_patterns
84
+ + handlebars_patterns
85
+ + golang_patterns
86
+ + html_patterns,
87
+ }
88
+
89
+ return any(
90
+ re.match(pattern, first_line) for pattern in pragma_patterns[config.profile]
91
+ )
File without changes
@@ -0,0 +1,239 @@
1
+ """Djlint test config."""
2
+ # pylint: disable=W0621,C0116
3
+ import difflib
4
+ import os
5
+ import re
6
+ import shutil
7
+ import tempfile
8
+ from pathlib import Path
9
+ from types import SimpleNamespace
10
+ from typing import Dict, Generator, List, Optional, TextIO
11
+
12
+ import pytest
13
+ from _pytest.reports import BaseReport
14
+ from _pytest.terminal import TerminalReporter
15
+ from click.testing import CliRunner
16
+ from colorama import Fore, Style
17
+
18
+ from src.djlint import main as djlint
19
+ from src.djlint.settings import Config
20
+
21
+
22
+ class MyReporter(TerminalReporter): # type: ignore
23
+ """Override default reporter to print more interesting details."""
24
+
25
+ def short_test_summary(self):
26
+ """Override summary."""
27
+ failed = self.stats.get("failed", [])
28
+
29
+ if failed:
30
+ self.write_sep("=", "Short Test Summary")
31
+ for rep in failed:
32
+ self.write_line(f"failed {rep.nodeid}")
33
+
34
+ def summary_failures(self) -> None:
35
+ """Override failure printer."""
36
+ if self.config.option.tbstyle != "no":
37
+ reports: List[BaseReport] = self.getreports("failed")
38
+ if not reports:
39
+ return
40
+ self.write_sep("=", "FAILURES")
41
+ if self.config.option.tbstyle == "line":
42
+ for rep in reports:
43
+ line = self._getcrashline(rep)
44
+ self.write_line(line)
45
+ else:
46
+ for rep in reports:
47
+ msg = self._getfailureheadline(rep)
48
+ self.write_sep("_", msg, red=True, bold=True)
49
+ # modified version of _outrep_summary()
50
+ # only show error if not assertion error,
51
+ # otherwise our print function shows the diff better.
52
+ if not re.search(r"AssertionError:", rep.longreprtext):
53
+ rep.toterminal(self._tw)
54
+ showcapture = self.config.option.showcapture
55
+ if showcapture == "no":
56
+ return
57
+ for secname, content in rep.sections:
58
+ if showcapture != "all" and showcapture not in secname:
59
+ continue
60
+ # self._tw.sep("-", secname)
61
+ line_content = content
62
+ if content[-1:] == "\n":
63
+ line_content = content[:-1]
64
+ self._tw.line(line_content)
65
+ # continue original code
66
+ self._handle_teardown_sections(rep.nodeid)
67
+
68
+
69
+ @pytest.hookimpl(trylast=True)
70
+ def pytest_configure(config):
71
+ vanilla_reporter = config.pluginmanager.getplugin("terminalreporter")
72
+ my_reporter = MyReporter(config)
73
+ config.pluginmanager.unregister(vanilla_reporter)
74
+ config.pluginmanager.register(my_reporter, "terminalreporter")
75
+
76
+
77
+ @pytest.fixture()
78
+ def runner() -> CliRunner:
79
+ """Click runner for djlint tests."""
80
+ return CliRunner()
81
+
82
+
83
+ @pytest.fixture()
84
+ def tmp_file() -> Generator:
85
+ """Create a temp file for formatting."""
86
+ # pylint: disable=R1732
87
+ tmp = tempfile.NamedTemporaryFile(delete=False)
88
+ yield tmp
89
+ tmp.close()
90
+ os.unlink(tmp.name)
91
+
92
+
93
+ def printer(expected, source, actual):
94
+ width, _ = shutil.get_terminal_size()
95
+
96
+ expected_text = "Expected"
97
+ actual_text = "Actual"
98
+ diff_text = "Diff"
99
+ source_text = "Source"
100
+
101
+ expected_width = int((width - len(expected_text) - 2) / 2)
102
+ actual_width = int((width - len(actual_text) - 2) / 2)
103
+ diff_width = int((width - len(diff_text) - 2) / 2)
104
+ source_width = int((width - len(source_text) - 2) / 2)
105
+
106
+ color = {"-": Fore.YELLOW, "+": Fore.GREEN, "@": Style.BRIGHT + Fore.BLUE}
107
+
108
+ print()
109
+ print(
110
+ f"{Fore.BLUE}{Style.BRIGHT}{'─' * source_width} {source_text} {'─' * source_width}{Style.RESET_ALL}"
111
+ )
112
+ print()
113
+ print(source)
114
+ print()
115
+ print(
116
+ f"{Fore.BLUE}{Style.BRIGHT}{'─' * expected_width} {expected_text} {'─' * expected_width}{Style.RESET_ALL}"
117
+ )
118
+ print()
119
+ print(expected)
120
+ print()
121
+ print(
122
+ f"{Fore.BLUE}{Style.BRIGHT}{'─' * actual_width} {actual_text} {'─' * actual_width}{Style.RESET_ALL}"
123
+ )
124
+ print()
125
+ print(actual)
126
+ print()
127
+ print(
128
+ f"{Fore.BLUE}{Style.BRIGHT}{'─' * diff_width} {diff_text} {'─' * diff_width}{Style.RESET_ALL}"
129
+ )
130
+ print()
131
+ for diff in list(difflib.unified_diff(expected.split("\n"), actual.split("\n")))[
132
+ 2:
133
+ ]:
134
+ print(f"{ color.get(diff[:1], Style.RESET_ALL)}{diff}{Style.RESET_ALL}")
135
+
136
+
137
+ def lint_printer(source, expected, actual):
138
+ width, _ = shutil.get_terminal_size()
139
+
140
+ expected_text = "Expected Rules"
141
+ actual_text = "Actual"
142
+ source_text = "Source"
143
+
144
+ expected_width = int((width - len(expected_text) - 2) / 2)
145
+ actual_width = int((width - len(actual_text) - 2) / 2)
146
+ source_width = int((width - len(source_text) - 2) / 2)
147
+
148
+ print()
149
+ print(
150
+ f"{Fore.BLUE}{Style.BRIGHT}{'─' * source_width} {source_text} {'─' * source_width}{Style.RESET_ALL}"
151
+ )
152
+ print()
153
+ print(source)
154
+ print()
155
+
156
+ print(
157
+ f"{Fore.BLUE}{Style.BRIGHT}{'─' * expected_width} {expected_text} {'─' * expected_width}{Style.RESET_ALL}"
158
+ )
159
+ print()
160
+ for x in expected:
161
+ print(
162
+ f"{Fore.RED}{Style.BRIGHT}{x['code']}{Style.RESET_ALL} {x['line']} {x['match']}"
163
+ )
164
+ print(f' {x["message"]}')
165
+ print()
166
+
167
+ print(
168
+ f"{Fore.BLUE}{Style.BRIGHT}{'─' * actual_width} {actual_text} {'─' * actual_width}{Style.RESET_ALL}"
169
+ )
170
+ print()
171
+
172
+ for x in actual:
173
+ print(
174
+ f"{Fore.RED}{Style.BRIGHT}{x['code']}{Style.RESET_ALL} {x['line']} {x['match']}"
175
+ )
176
+ print(f' {x["message"]}')
177
+ print()
178
+ if len(actual) == 0:
179
+ print(f"{Fore.YELLOW}No codes found.{Style.RESET_ALL}")
180
+ print()
181
+
182
+ else:
183
+ print(f"{Fore.YELLOW}{actual}{Style.RESET_ALL}")
184
+ print()
185
+
186
+
187
+ def write_to_file(the_file: str, the_text: bytes) -> None:
188
+ """Shortcode for write some bytes to a file."""
189
+ with open(the_file, mode="w+b") as open_file:
190
+ open_file.write(the_text)
191
+
192
+
193
+ def reformat(
194
+ the_file: TextIO, runner: CliRunner, the_text: bytes, profile: str = "html"
195
+ ) -> SimpleNamespace:
196
+ write_to_file(the_file.name, the_text)
197
+ result = runner.invoke(djlint, [the_file.name, "--profile", profile, "--reformat"])
198
+ return SimpleNamespace(
199
+ **{
200
+ "text": Path(the_file.name).read_text(encoding="utf8"),
201
+ "exit_code": result.exit_code,
202
+ }
203
+ )
204
+
205
+
206
+ def config_builder(args: Optional[Dict] = None) -> Config:
207
+ if args:
208
+ return Config("dummy/source.html", **args)
209
+ return Config("dummy/source.html")
210
+
211
+
212
+ @pytest.fixture()
213
+ def basic_config() -> Config:
214
+ """Return a config object with default basic options."""
215
+ return Config("dummy/source.html")
216
+
217
+
218
+ @pytest.fixture()
219
+ def django_config() -> Config:
220
+ """Return a config object with django profile."""
221
+ return Config("dummy/source.html", profile="django")
222
+
223
+
224
+ @pytest.fixture()
225
+ def jinja_config() -> Config:
226
+ """Return a config object with jinja."""
227
+ return Config("dummy/source.html", profile="jinja")
228
+
229
+
230
+ @pytest.fixture()
231
+ def handlebars_config() -> Config:
232
+ """Return a config object with handlebars."""
233
+ return Config("dummy/source.html", profile="handlebars")
234
+
235
+
236
+ @pytest.fixture()
237
+ def nunjucks_config() -> Config:
238
+ """Return a config object with nunjucks."""
239
+ return Config("dummy/source.html", profile="nunjucks")
@@ -0,0 +1,71 @@
1
+ """Test for cli inputs.
2
+
3
+ poetry run pytest tests/test_cli.py
4
+ """
5
+ from click.testing import CliRunner
6
+
7
+ from src.djlint import main as djlint
8
+
9
+
10
+ def test_cli(runner: CliRunner) -> None:
11
+ # missing options:
12
+ result = runner.invoke(
13
+ djlint,
14
+ [
15
+ "-",
16
+ "--check",
17
+ "--blank-line-after-tag",
18
+ "p",
19
+ "--blank-line-before-tag",
20
+ "p",
21
+ "--custom-blocks",
22
+ "toc",
23
+ "--custom-html",
24
+ "asdf",
25
+ "--exclude",
26
+ ".asdf",
27
+ "--extend-exclude",
28
+ ".asdf",
29
+ "--extension",
30
+ "html.dj",
31
+ "--format-attribute-template-tags",
32
+ "--format-css",
33
+ "--format-js",
34
+ "--ignore",
35
+ "H014,H015",
36
+ "--ignore-blocks",
37
+ "raw",
38
+ "--ignore-case",
39
+ "--include",
40
+ "H014",
41
+ "--indent",
42
+ "4",
43
+ "--linter-output-format",
44
+ "{code}",
45
+ "--max-attribute-length",
46
+ "9",
47
+ "--max-line-length",
48
+ "100",
49
+ "--preserve-blank-lines",
50
+ "--preserve-leading-space",
51
+ "--profile",
52
+ "django",
53
+ "--require-pragma",
54
+ "--use-gitignore",
55
+ "--per-file-ignores",
56
+ "test.html",
57
+ "H014",
58
+ "--per-file-ignores",
59
+ "test2.html",
60
+ "H015",
61
+ "--indent-css",
62
+ "4",
63
+ "--indent-js",
64
+ "4",
65
+ ],
66
+ input="<div></div>\n",
67
+ )
68
+
69
+ print(result.output)
70
+
71
+ assert result.exit_code == 0
File without changes
@@ -0,0 +1,135 @@
1
+ """Test for blank line after tag.
2
+
3
+ --blank-line-after-tag 'include,load'
4
+
5
+ poetry run pytest tests/test_config/test_blank_line_after_tag.py
6
+ """
7
+ import pytest
8
+
9
+ from src.djlint.reformat import formatter
10
+ from tests.conftest import config_builder, printer
11
+
12
+ test_data = [
13
+ pytest.param(
14
+ (
15
+ '{% extends "nothing.html" %}{% load stuff %}{% load stuff 2 %}{% include "html_two.html" %}<div></div>'
16
+ ),
17
+ (
18
+ '{% extends "nothing.html" %}\n'
19
+ "\n"
20
+ "{% load stuff %}\n"
21
+ "{% load stuff 2 %}\n"
22
+ "\n"
23
+ '{% include "html_two.html" %}\n'
24
+ "\n"
25
+ "<div></div>\n"
26
+ ),
27
+ ({"blank_line_after_tag": "include,load, extends"}),
28
+ id="one",
29
+ ),
30
+ pytest.param(
31
+ (
32
+ '<div class="tab-cnt">\n'
33
+ ' <div class="tab-dta active" id="details">\n'
34
+ ' <div class="em-grid">\n'
35
+ ' {% include "pages/task/details_source.html.j2" %}\n'
36
+ "\n"
37
+ " </div>\n"
38
+ " </div>\n"
39
+ "</div>"
40
+ ),
41
+ (
42
+ '<div class="tab-cnt">\n'
43
+ ' <div class="tab-dta active" id="details">\n'
44
+ ' <div class="em-grid">\n'
45
+ ' {% include "pages/task/details_source.html.j2" %}\n'
46
+ "\n"
47
+ " </div>\n"
48
+ " </div>\n"
49
+ "</div>\n"
50
+ ),
51
+ ({"blank_line_after_tag": "include"}),
52
+ id="blank_nested",
53
+ ),
54
+ pytest.param(
55
+ ("{% block this %}\n" "{% load i18n %}\n" "{% endblock this %}"),
56
+ ("{% block this %}\n" " {% load i18n %}\n" "\n" "{% endblock this %}\n"),
57
+ ({"blank_line_after_tag": "endblock , junk,load "}),
58
+ id="nested_indent",
59
+ ),
60
+ pytest.param(
61
+ (
62
+ "{% block include %}\n"
63
+ " {# {% include 'common/sticky-topbar-hidden-nav.html' %}#}\n"
64
+ "{% endblock %}\n"
65
+ ),
66
+ (
67
+ "{% block include %}\n"
68
+ " {# {% include 'common/sticky-topbar-hidden-nav.html' %}#}\n"
69
+ "{% endblock %}\n"
70
+ ),
71
+ ({"blank_line_after_tag": "load, extends,endblock"}),
72
+ id="test inside comment",
73
+ ),
74
+ pytest.param(
75
+ (
76
+ "{% blocktrans %}my words{% endblocktrans %}\n"
77
+ "{% block body %}\n"
78
+ " <div></div>\n"
79
+ "{% endblock body %}\n"
80
+ "\n"
81
+ "{% block js %}{% endblock %}"
82
+ ),
83
+ (
84
+ "{% blocktrans %}my words{% endblocktrans %}\n"
85
+ "{% block body %}\n"
86
+ " <div></div>\n"
87
+ "{% endblock body %}\n"
88
+ "\n"
89
+ "{% block js %}{% endblock %}\n"
90
+ ),
91
+ ({"blank_line_after_tag": "endblock"}),
92
+ id="endblock is not endblocktrans",
93
+ ),
94
+ pytest.param(
95
+ ("{% extends nothing %}\n" "\n" "<div></div>\n"),
96
+ ("{% extends nothing %}\n" "\n" "<div></div>\n"),
97
+ ({"blank_line_after_tag": "load, extends", "preserve_blank_lines": True}),
98
+ id="option should work with preserve blank lines",
99
+ ),
100
+ pytest.param(
101
+ (
102
+ "{% block %}stuff{% endblock %}\n"
103
+ "\n"
104
+ "{% block %}stuff{% endblock %}\n"
105
+ "\n"
106
+ ),
107
+ ("{% block %}stuff{% endblock %}\n" "\n" "{% block %}stuff{% endblock %}\n"),
108
+ ({"blank_line_after_tag": "load, extends, include ,endblock "}),
109
+ id="double block",
110
+ ),
111
+ pytest.param(
112
+ (
113
+ '{% include "pages/task/details/data_source.html.j2" %}\n'
114
+ '{% include "pages/task/details/query_location.html.j2" %}\n'
115
+ '{% include "pages/task/details/processing.html.j2" %}\n'
116
+ '{% include "pages/task/details/destination.html.j2" %}\n'
117
+ ),
118
+ (
119
+ '{% include "pages/task/details/data_source.html.j2" %}\n'
120
+ '{% include "pages/task/details/query_location.html.j2" %}\n'
121
+ '{% include "pages/task/details/processing.html.j2" %}\n'
122
+ '{% include "pages/task/details/destination.html.j2" %}\n'
123
+ ),
124
+ ({"blank_line_after_tag": " include ,endblock "}),
125
+ id="test multiple",
126
+ ),
127
+ ]
128
+
129
+
130
+ @pytest.mark.parametrize(("source", "expected", "args"), test_data)
131
+ def test_base(source, expected, args):
132
+ output = formatter(config_builder(args), source)
133
+
134
+ printer(expected, source, output)
135
+ assert expected == output
@@ -0,0 +1,141 @@
1
+ """Test for blank line before tag.
2
+
3
+ --blank-line-before-tag 'include,load'
4
+
5
+ poetry run pytest tests/test_config/test_blank_line_before_tag.py
6
+ """
7
+ import pytest
8
+
9
+ from src.djlint.reformat import formatter
10
+ from tests.conftest import config_builder, printer
11
+
12
+ test_data = [
13
+ pytest.param(
14
+ (
15
+ '{% extends "nothing.html" %}{% load stuff %}{% load stuff 2 %}{% include "html_two.html" %}<div></div>'
16
+ ),
17
+ (
18
+ '{% extends "nothing.html" %}\n'
19
+ "\n"
20
+ "{% load stuff %}\n"
21
+ "{% load stuff 2 %}\n"
22
+ "\n"
23
+ '{% include "html_two.html" %}\n'
24
+ "<div></div>\n"
25
+ ),
26
+ ({"blank_line_before_tag": "include,load, extends"}),
27
+ id="one",
28
+ ),
29
+ pytest.param(
30
+ (
31
+ '<div class="tab-cnt">\n'
32
+ ' <div class="tab-dta active" id="details">\n'
33
+ ' <div class="em-grid">\n'
34
+ ' {% include "pages/task/details_source.html.j2" %}\n'
35
+ "\n"
36
+ " </div>\n"
37
+ " </div>\n"
38
+ "</div>"
39
+ ),
40
+ (
41
+ '<div class="tab-cnt">\n'
42
+ ' <div class="tab-dta active" id="details">\n'
43
+ ' <div class="em-grid">\n'
44
+ "\n"
45
+ ' {% include "pages/task/details_source.html.j2" %}\n'
46
+ " </div>\n"
47
+ " </div>\n"
48
+ "</div>\n"
49
+ ),
50
+ ({"blank_line_before_tag": "include"}),
51
+ id="blank_nested",
52
+ ),
53
+ pytest.param(
54
+ ("{% block this %}\n" "{% load i18n %}\n" "{% endblock this %}"),
55
+ (
56
+ "{% block this %}\n"
57
+ "\n"
58
+ " {% load i18n %}\n"
59
+ "\n"
60
+ "{% endblock this %}\n"
61
+ ),
62
+ ({"blank_line_before_tag": "endblock , junk,load "}),
63
+ id="nested_indent",
64
+ ),
65
+ pytest.param(
66
+ (
67
+ "{% block include %}\n"
68
+ " {# {% include 'common/sticky-topbar-hidden-nav.html' %}#}\n"
69
+ "{% endblock %}\n"
70
+ ),
71
+ (
72
+ "{% block include %}\n"
73
+ " {# {% include 'common/sticky-topbar-hidden-nav.html' %}#}\n"
74
+ "\n"
75
+ "{% endblock %}\n"
76
+ ),
77
+ ({"blank_line_before_tag": "load, extends,endblock"}),
78
+ id="test inside comment",
79
+ ),
80
+ pytest.param(
81
+ (
82
+ "{% blocktrans %}my words{% endblocktrans %}\n"
83
+ "{% block body %}\n"
84
+ " <div></div>\n"
85
+ "{% endblock body %}\n"
86
+ "\n"
87
+ "{% block js %}{% endblock %}"
88
+ ),
89
+ (
90
+ "{% blocktrans %}my words{% endblocktrans %}\n"
91
+ "{% block body %}\n"
92
+ " <div></div>\n"
93
+ "\n"
94
+ "{% endblock body %}\n"
95
+ "{% block js %}{% endblock %}\n"
96
+ ),
97
+ ({"blank_line_before_tag": "endblock"}),
98
+ id="endblock is not endblocktrans",
99
+ ),
100
+ pytest.param(
101
+ ("{% extends nothing %}\n" "\n" "<div></div>\n"),
102
+ ("{% extends nothing %}\n" "\n" "<div></div>\n"),
103
+ ({"blank_line_before_tag": "load, extends", "preserve_blank_lines": True}),
104
+ id="option should work with preserve blank lines",
105
+ ),
106
+ pytest.param(
107
+ (
108
+ "{% block %}stuff{% endblock %}\n"
109
+ "\n"
110
+ "{% block %}stuff{% endblock %}\n"
111
+ "\n"
112
+ ),
113
+ ("{% block %}stuff{% endblock %}\n" "{% block %}stuff{% endblock %}\n"),
114
+ ({"blank_line_before_tag": "load, extends, include ,endblock "}),
115
+ id="double block",
116
+ ),
117
+ pytest.param(
118
+ (
119
+ '{% include "pages/task/details/data_source.html.j2" %}\n'
120
+ '{% include "pages/task/details/query_location.html.j2" %}\n'
121
+ '{% include "pages/task/details/processing.html.j2" %}\n'
122
+ '{% include "pages/task/details/destination.html.j2" %}\n'
123
+ ),
124
+ (
125
+ '{% include "pages/task/details/data_source.html.j2" %}\n'
126
+ '{% include "pages/task/details/query_location.html.j2" %}\n'
127
+ '{% include "pages/task/details/processing.html.j2" %}\n'
128
+ '{% include "pages/task/details/destination.html.j2" %}\n'
129
+ ),
130
+ ({"blank_line_before_tag": " include ,endblock "}),
131
+ id="test multiple",
132
+ ),
133
+ ]
134
+
135
+
136
+ @pytest.mark.parametrize(("source", "expected", "args"), test_data)
137
+ def test_base(source, expected, args):
138
+ output = formatter(config_builder(args), source)
139
+
140
+ printer(expected, source, output)
141
+ assert expected == output