execsql2 2.18.0__tar.gz → 2.18.1__tar.gz

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 (375) hide show
  1. {execsql2-2.18.0 → execsql2-2.18.1}/.github/workflows/ci-cd.yml +1 -1
  2. {execsql2-2.18.0 → execsql2-2.18.1}/CHANGELOG.md +12 -0
  3. {execsql2-2.18.0 → execsql2-2.18.1}/PKG-INFO +42 -40
  4. {execsql2-2.18.0 → execsql2-2.18.1}/README.md +41 -39
  5. {execsql2-2.18.0 → execsql2-2.18.1}/docs/about/divergence.md +7 -6
  6. {execsql2-2.18.0 → execsql2-2.18.1}/docs/dev/adding_metacommands.md +1 -3
  7. {execsql2-2.18.0 → execsql2-2.18.1}/docs/dev/architecture.md +4 -4
  8. {execsql2-2.18.0 → execsql2-2.18.1}/extras/vscode-execsql/README.md +4 -4
  9. {execsql2-2.18.0 → execsql2-2.18.1}/pyproject.toml +2 -2
  10. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/cli/__init__.py +3 -5
  11. execsql2-2.18.0/src/execsql/cli/lint_ast.py → execsql2-2.18.1/src/execsql/cli/lint.py +113 -46
  12. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/metacommands/dispatch.py +5 -10
  13. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/metacommands/script_ext.py +8 -7
  14. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/script/engine.py +1 -12
  15. execsql2-2.18.0/tests/cli/test_lint_ast.py → execsql2-2.18.1/tests/cli/test_lint.py +130 -17
  16. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_assert.py +0 -6
  17. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_conditions_extra.py +3 -3
  18. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_prompt.py +1 -1
  19. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_engine.py +1 -13
  20. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_executor.py +4 -4
  21. {execsql2-2.18.0 → execsql2-2.18.1}/uv.lock +1 -1
  22. execsql2-2.18.0/src/execsql/cli/lint.py +0 -91
  23. execsql2-2.18.0/tests/cli/test_lint.py +0 -98
  24. {execsql2-2.18.0 → execsql2-2.18.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  25. {execsql2-2.18.0 → execsql2-2.18.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  26. {execsql2-2.18.0 → execsql2-2.18.1}/.github/dependabot.yml +0 -0
  27. {execsql2-2.18.0 → execsql2-2.18.1}/.gitignore +0 -0
  28. {execsql2-2.18.0 → execsql2-2.18.1}/.pre-commit-config.yaml +0 -0
  29. {execsql2-2.18.0 → execsql2-2.18.1}/.pre-commit-hooks.yaml +0 -0
  30. {execsql2-2.18.0 → execsql2-2.18.1}/.python-version +0 -0
  31. {execsql2-2.18.0 → execsql2-2.18.1}/.readthedocs.yaml +0 -0
  32. {execsql2-2.18.0 → execsql2-2.18.1}/CONTRIBUTING.md +0 -0
  33. {execsql2-2.18.0 → execsql2-2.18.1}/LICENSE.txt +0 -0
  34. {execsql2-2.18.0 → execsql2-2.18.1}/NOTICE +0 -0
  35. {execsql2-2.18.0 → execsql2-2.18.1}/SECURITY.md +0 -0
  36. {execsql2-2.18.0 → execsql2-2.18.1}/docs/about/contributors.md +0 -0
  37. {execsql2-2.18.0 → execsql2-2.18.1}/docs/about/copyright.md +0 -0
  38. {execsql2-2.18.0 → execsql2-2.18.1}/docs/api/cli.md +0 -0
  39. {execsql2-2.18.0 → execsql2-2.18.1}/docs/api/db.md +0 -0
  40. {execsql2-2.18.0 → execsql2-2.18.1}/docs/api/exporters.md +0 -0
  41. {execsql2-2.18.0 → execsql2-2.18.1}/docs/api/importers.md +0 -0
  42. {execsql2-2.18.0 → execsql2-2.18.1}/docs/api/index.md +0 -0
  43. {execsql2-2.18.0 → execsql2-2.18.1}/docs/api/metacommands.md +0 -0
  44. {execsql2-2.18.0 → execsql2-2.18.1}/docs/dev/adding_db_adapters.md +0 -0
  45. {execsql2-2.18.0 → execsql2-2.18.1}/docs/dev/adding_exporters.md +0 -0
  46. {execsql2-2.18.0 → execsql2-2.18.1}/docs/dev/adding_importers.md +0 -0
  47. {execsql2-2.18.0 → execsql2-2.18.1}/docs/getting-started/installation.md +0 -0
  48. {execsql2-2.18.0 → execsql2-2.18.1}/docs/getting-started/requirements.md +0 -0
  49. {execsql2-2.18.0 → execsql2-2.18.1}/docs/getting-started/syntax.md +0 -0
  50. {execsql2-2.18.0 → execsql2-2.18.1}/docs/guides/debugging.md +0 -0
  51. {execsql2-2.18.0 → execsql2-2.18.1}/docs/guides/documentation.md +0 -0
  52. {execsql2-2.18.0 → execsql2-2.18.1}/docs/guides/encoding.md +0 -0
  53. {execsql2-2.18.0 → execsql2-2.18.1}/docs/guides/examples.md +0 -0
  54. {execsql2-2.18.0 → execsql2-2.18.1}/docs/guides/formatter.md +0 -0
  55. {execsql2-2.18.0 → execsql2-2.18.1}/docs/guides/logging.md +0 -0
  56. {execsql2-2.18.0 → execsql2-2.18.1}/docs/guides/sql_syntax.md +0 -0
  57. {execsql2-2.18.0 → execsql2-2.18.1}/docs/guides/usage.md +0 -0
  58. {execsql2-2.18.0 → execsql2-2.18.1}/docs/guides/using_scripts.md +0 -0
  59. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/Compare_planets.png +0 -0
  60. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/actions.png +0 -0
  61. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/actions2.png +0 -0
  62. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/checkboxes.png +0 -0
  63. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/connect.b64 +0 -0
  64. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/connect.png +0 -0
  65. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/create_conf.png +0 -0
  66. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/data_error1_screenshot.jpg +0 -0
  67. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/entry_form.png +0 -0
  68. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/execsql_console.png +0 -0
  69. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/execsql_logo_01.png +0 -0
  70. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/fatals.png +0 -0
  71. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/logo_small.png +0 -0
  72. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/pause_terminal.png +0 -0
  73. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/pause_terminal_sm.b64 +0 -0
  74. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/pause_terminal_sm.png +0 -0
  75. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/prompt_compare.png +0 -0
  76. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/set_build_commands.jpg +0 -0
  77. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/unit_conversions.b64 +0 -0
  78. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/unit_conversions_029.png +0 -0
  79. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/unmatched.png +0 -0
  80. {execsql2-2.18.0 → execsql2-2.18.1}/docs/images/vim_execsql_highlight.png +0 -0
  81. {execsql2-2.18.0 → execsql2-2.18.1}/docs/index.md +0 -0
  82. {execsql2-2.18.0 → execsql2-2.18.1}/docs/reference/configuration.md +0 -0
  83. {execsql2-2.18.0 → execsql2-2.18.1}/docs/reference/metacommands.md +0 -0
  84. {execsql2-2.18.0 → execsql2-2.18.1}/docs/reference/security.md +0 -0
  85. {execsql2-2.18.0 → execsql2-2.18.1}/docs/reference/substitution_vars.md +0 -0
  86. {execsql2-2.18.0 → execsql2-2.18.1}/extras/plugin-template/README.md +0 -0
  87. {execsql2-2.18.0 → execsql2-2.18.1}/extras/plugin-template/pyproject.toml +0 -0
  88. {execsql2-2.18.0 → execsql2-2.18.1}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
  89. {execsql2-2.18.0 → execsql2-2.18.1}/extras/plugin-template/tests/test_plugin.py.example +0 -0
  90. {execsql2-2.18.0 → execsql2-2.18.1}/extras/vscode-execsql/package.json +0 -0
  91. {execsql2-2.18.0 → execsql2-2.18.1}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
  92. {execsql2-2.18.0 → execsql2-2.18.1}/justfile +0 -0
  93. {execsql2-2.18.0 → execsql2-2.18.1}/scripts/generate_vscode_grammar.py +0 -0
  94. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/__init__.py +0 -0
  95. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/__main__.py +0 -0
  96. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/api.py +0 -0
  97. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/cli/dsn.py +0 -0
  98. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/cli/help.py +0 -0
  99. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/cli/run.py +0 -0
  100. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/config.py +0 -0
  101. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/data/__init__.py +0 -0
  102. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/data/execsql.conf.template +0 -0
  103. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/db/__init__.py +0 -0
  104. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/db/access.py +0 -0
  105. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/db/base.py +0 -0
  106. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/db/dsn.py +0 -0
  107. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/db/duckdb.py +0 -0
  108. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/db/factory.py +0 -0
  109. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/db/firebird.py +0 -0
  110. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/db/mysql.py +0 -0
  111. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/db/oracle.py +0 -0
  112. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/db/postgres.py +0 -0
  113. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/db/sqlite.py +0 -0
  114. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/db/sqlserver.py +0 -0
  115. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/debug/__init__.py +0 -0
  116. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/debug/repl.py +0 -0
  117. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exceptions.py +0 -0
  118. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/__init__.py +0 -0
  119. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/base.py +0 -0
  120. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/delimited.py +0 -0
  121. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/duckdb.py +0 -0
  122. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/feather.py +0 -0
  123. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/html.py +0 -0
  124. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/json.py +0 -0
  125. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/latex.py +0 -0
  126. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/markdown.py +0 -0
  127. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/ods.py +0 -0
  128. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/parquet.py +0 -0
  129. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/pretty.py +0 -0
  130. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/protocol.py +0 -0
  131. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/raw.py +0 -0
  132. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/sqlite.py +0 -0
  133. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/templates.py +0 -0
  134. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/values.py +0 -0
  135. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/xls.py +0 -0
  136. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/xlsx.py +0 -0
  137. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/xml.py +0 -0
  138. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/yaml.py +0 -0
  139. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/exporters/zip.py +0 -0
  140. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/format.py +0 -0
  141. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/gui/__init__.py +0 -0
  142. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/gui/base.py +0 -0
  143. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/gui/console.py +0 -0
  144. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/gui/desktop.py +0 -0
  145. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/gui/tui.py +0 -0
  146. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/importers/__init__.py +0 -0
  147. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/importers/base.py +0 -0
  148. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/importers/csv.py +0 -0
  149. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/importers/feather.py +0 -0
  150. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/importers/json.py +0 -0
  151. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/importers/ods.py +0 -0
  152. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/importers/xls.py +0 -0
  153. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/metacommands/__init__.py +0 -0
  154. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/metacommands/conditions.py +0 -0
  155. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/metacommands/connect.py +0 -0
  156. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/metacommands/control.py +0 -0
  157. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/metacommands/data.py +0 -0
  158. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/metacommands/debug.py +0 -0
  159. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/metacommands/io.py +0 -0
  160. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/metacommands/io_export.py +0 -0
  161. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/metacommands/io_fileops.py +0 -0
  162. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/metacommands/io_import.py +0 -0
  163. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/metacommands/io_write.py +0 -0
  164. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/metacommands/prompt.py +0 -0
  165. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/metacommands/system.py +0 -0
  166. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/metacommands/upsert.py +0 -0
  167. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/models.py +0 -0
  168. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/parser.py +0 -0
  169. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/plugins.py +0 -0
  170. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/py.typed +0 -0
  171. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/script/__init__.py +0 -0
  172. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/script/ast.py +0 -0
  173. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/script/control.py +0 -0
  174. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/script/executor.py +0 -0
  175. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/script/parser.py +0 -0
  176. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/script/variables.py +0 -0
  177. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/state.py +0 -0
  178. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/types.py +0 -0
  179. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/utils/__init__.py +0 -0
  180. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/utils/auth.py +0 -0
  181. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/utils/crypto.py +0 -0
  182. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/utils/datetime.py +0 -0
  183. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/utils/errors.py +0 -0
  184. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/utils/fileio.py +0 -0
  185. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/utils/gui.py +0 -0
  186. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/utils/mail.py +0 -0
  187. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/utils/numeric.py +0 -0
  188. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/utils/regex.py +0 -0
  189. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/utils/strings.py +0 -0
  190. {execsql2-2.18.0 → execsql2-2.18.1}/src/execsql/utils/timer.py +0 -0
  191. {execsql2-2.18.0 → execsql2-2.18.1}/templates/README.md +0 -0
  192. {execsql2-2.18.0 → execsql2-2.18.1}/templates/config_settings.sqlite +0 -0
  193. {execsql2-2.18.0 → execsql2-2.18.1}/templates/example_config_prompt.sql +0 -0
  194. {execsql2-2.18.0 → execsql2-2.18.1}/templates/execsql.conf +0 -0
  195. {execsql2-2.18.0 → execsql2-2.18.1}/templates/make_config_db.sql +0 -0
  196. {execsql2-2.18.0 → execsql2-2.18.1}/templates/md_compare.sql +0 -0
  197. {execsql2-2.18.0 → execsql2-2.18.1}/templates/md_glossary.sql +0 -0
  198. {execsql2-2.18.0 → execsql2-2.18.1}/templates/md_upsert.sql +0 -0
  199. {execsql2-2.18.0 → execsql2-2.18.1}/templates/pg_compare.sql +0 -0
  200. {execsql2-2.18.0 → execsql2-2.18.1}/templates/pg_glossary.sql +0 -0
  201. {execsql2-2.18.0 → execsql2-2.18.1}/templates/pg_upsert.sql +0 -0
  202. {execsql2-2.18.0 → execsql2-2.18.1}/templates/script_template.sql +0 -0
  203. {execsql2-2.18.0 → execsql2-2.18.1}/templates/ss_compare.sql +0 -0
  204. {execsql2-2.18.0 → execsql2-2.18.1}/templates/ss_glossary.sql +0 -0
  205. {execsql2-2.18.0 → execsql2-2.18.1}/templates/ss_upsert.sql +0 -0
  206. {execsql2-2.18.0 → execsql2-2.18.1}/tests/__init__.py +0 -0
  207. {execsql2-2.18.0 → execsql2-2.18.1}/tests/cli/__init__.py +0 -0
  208. {execsql2-2.18.0 → execsql2-2.18.1}/tests/cli/test_cli.py +0 -0
  209. {execsql2-2.18.0 → execsql2-2.18.1}/tests/cli/test_cli_e2e.py +0 -0
  210. {execsql2-2.18.0 → execsql2-2.18.1}/tests/cli/test_cli_run.py +0 -0
  211. {execsql2-2.18.0 → execsql2-2.18.1}/tests/cli/test_ping.py +0 -0
  212. {execsql2-2.18.0 → execsql2-2.18.1}/tests/cli/test_profile.py +0 -0
  213. {execsql2-2.18.0 → execsql2-2.18.1}/tests/conftest.py +0 -0
  214. {execsql2-2.18.0 → execsql2-2.18.1}/tests/db/__init__.py +0 -0
  215. {execsql2-2.18.0 → execsql2-2.18.1}/tests/db/test_access_windows.py +0 -0
  216. {execsql2-2.18.0 → execsql2-2.18.1}/tests/db/test_base.py +0 -0
  217. {execsql2-2.18.0 → execsql2-2.18.1}/tests/db/test_db_adapters_mocked.py +0 -0
  218. {execsql2-2.18.0 → execsql2-2.18.1}/tests/db/test_db_adapters_mocked_extra.py +0 -0
  219. {execsql2-2.18.0 → execsql2-2.18.1}/tests/db/test_dsn.py +0 -0
  220. {execsql2-2.18.0 → execsql2-2.18.1}/tests/db/test_duckdb.py +0 -0
  221. {execsql2-2.18.0 → execsql2-2.18.1}/tests/db/test_factory.py +0 -0
  222. {execsql2-2.18.0 → execsql2-2.18.1}/tests/db/test_mysql_case_folding.py +0 -0
  223. {execsql2-2.18.0 → execsql2-2.18.1}/tests/db/test_mysql_inprocess.py +0 -0
  224. {execsql2-2.18.0 → execsql2-2.18.1}/tests/db/test_postgres.py +0 -0
  225. {execsql2-2.18.0 → execsql2-2.18.1}/tests/db/test_postgres_inprocess.py +0 -0
  226. {execsql2-2.18.0 → execsql2-2.18.1}/tests/db/test_sqlite.py +0 -0
  227. {execsql2-2.18.0 → execsql2-2.18.1}/tests/db/test_sqlite_extra.py +0 -0
  228. {execsql2-2.18.0 → execsql2-2.18.1}/tests/db/test_sqlserver_inprocess.py +0 -0
  229. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/__init__.py +0 -0
  230. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_base.py +0 -0
  231. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_db.py +0 -0
  232. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_delimited.py +0 -0
  233. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_duckdb_exporter.py +0 -0
  234. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_exporters.py +0 -0
  235. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_feather.py +0 -0
  236. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_html_extended.py +0 -0
  237. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_html_latex.py +0 -0
  238. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_json.py +0 -0
  239. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_json_extended.py +0 -0
  240. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_latex_extended.py +0 -0
  241. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_markdown.py +0 -0
  242. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_ods.py +0 -0
  243. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_ods_export.py +0 -0
  244. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_parquet.py +0 -0
  245. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_pretty_extended.py +0 -0
  246. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_raw_extended.py +0 -0
  247. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_sqlite_exporter.py +0 -0
  248. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_templates.py +0 -0
  249. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_templates_extended.py +0 -0
  250. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_values_extended.py +0 -0
  251. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_xls_xlsx.py +0 -0
  252. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_xlsx.py +0 -0
  253. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_xml.py +0 -0
  254. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_yaml.py +0 -0
  255. {execsql2-2.18.0 → execsql2-2.18.1}/tests/exporters/test_zip.py +0 -0
  256. {execsql2-2.18.0 → execsql2-2.18.1}/tests/gui/__init__.py +0 -0
  257. {execsql2-2.18.0 → execsql2-2.18.1}/tests/gui/test_backends.py +0 -0
  258. {execsql2-2.18.0 → execsql2-2.18.1}/tests/gui/test_backends_extended.py +0 -0
  259. {execsql2-2.18.0 → execsql2-2.18.1}/tests/gui/test_compare_stats.py +0 -0
  260. {execsql2-2.18.0 → execsql2-2.18.1}/tests/gui/test_compute_row_diffs.py +0 -0
  261. {execsql2-2.18.0 → execsql2-2.18.1}/tests/gui/test_desktop_dialogs.py +0 -0
  262. {execsql2-2.18.0 → execsql2-2.18.1}/tests/gui/test_tui_pilot.py +0 -0
  263. {execsql2-2.18.0 → execsql2-2.18.1}/tests/gui/test_tui_pilot_complex.py +0 -0
  264. {execsql2-2.18.0 → execsql2-2.18.1}/tests/gui/test_utils_gui_extended.py +0 -0
  265. {execsql2-2.18.0 → execsql2-2.18.1}/tests/importers/__init__.py +0 -0
  266. {execsql2-2.18.0 → execsql2-2.18.1}/tests/importers/test_base_extended.py +0 -0
  267. {execsql2-2.18.0 → execsql2-2.18.1}/tests/importers/test_csv_edge_cases.py +0 -0
  268. {execsql2-2.18.0 → execsql2-2.18.1}/tests/importers/test_csv_importer.py +0 -0
  269. {execsql2-2.18.0 → execsql2-2.18.1}/tests/importers/test_feather_importer.py +0 -0
  270. {execsql2-2.18.0 → execsql2-2.18.1}/tests/importers/test_json_importer.py +0 -0
  271. {execsql2-2.18.0 → execsql2-2.18.1}/tests/importers/test_ods_importer.py +0 -0
  272. {execsql2-2.18.0 → execsql2-2.18.1}/tests/importers/test_xls_importer.py +0 -0
  273. {execsql2-2.18.0 → execsql2-2.18.1}/tests/integration/__init__.py +0 -0
  274. {execsql2-2.18.0 → execsql2-2.18.1}/tests/integration/conftest.py +0 -0
  275. {execsql2-2.18.0 → execsql2-2.18.1}/tests/integration/test_dsn.py +0 -0
  276. {execsql2-2.18.0 → execsql2-2.18.1}/tests/integration/test_duckdb.py +0 -0
  277. {execsql2-2.18.0 → execsql2-2.18.1}/tests/integration/test_mysql.py +0 -0
  278. {execsql2-2.18.0 → execsql2-2.18.1}/tests/integration/test_postgres.py +0 -0
  279. {execsql2-2.18.0 → execsql2-2.18.1}/tests/integration/test_sqlite.py +0 -0
  280. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/__init__.py +0 -0
  281. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_breakpoint.py +0 -0
  282. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_connect.py +0 -0
  283. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_io_export.py +0 -0
  284. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_io_import.py +0 -0
  285. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_metacommands.py +0 -0
  286. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_metacommands_connect.py +0 -0
  287. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_metacommands_data.py +0 -0
  288. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_metacommands_extended.py +0 -0
  289. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  290. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_metacommands_io.py +0 -0
  291. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  292. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  293. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_metacommands_system.py +0 -0
  294. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  295. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_pg_upsert.py +0 -0
  296. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_row_count.py +0 -0
  297. {execsql2-2.18.0 → execsql2-2.18.1}/tests/metacommands/test_show_scripts.py +0 -0
  298. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/__init__.py +0 -0
  299. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/audit_lint_bad.sql +0 -0
  300. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/audit_smoke/sample.json +0 -0
  301. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/audit_smoke/sample.jsonl +0 -0
  302. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/audit_smoke.sql +0 -0
  303. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/config_runtime.sql +0 -0
  304. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/control_flow.sql +0 -0
  305. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/counters_and_locals.sql +0 -0
  306. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/error_handling.sql +0 -0
  307. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/import_options/has_comments.csv +0 -0
  308. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/import_options.sql +0 -0
  309. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/includes/helper.sql +0 -0
  310. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/includes.sql +0 -0
  311. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/io_formats.sql +0 -0
  312. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
  313. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/multi_db.sql +0 -0
  314. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/parquet_feather_roundtrip.sql +0 -0
  315. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
  316. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/predicates.sql +0 -0
  317. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/scripts.sql +0 -0
  318. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/smoke.sql +0 -0
  319. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/subroutine_loops.sql +0 -0
  320. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/subvars_advanced.sql +0 -0
  321. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/template_export/greeting.tmpl +0 -0
  322. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/template_export.sql +0 -0
  323. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/timer.sql +0 -0
  324. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/transactions.sql +0 -0
  325. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/write_create_table.sql +0 -0
  326. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/fixtures/xlsx_ods_roundtrip.sql +0 -0
  327. {execsql2-2.18.0 → execsql2-2.18.1}/tests/scripts/test_sql_scripts.py +0 -0
  328. {execsql2-2.18.0 → execsql2-2.18.1}/tests/security/__init__.py +0 -0
  329. {execsql2-2.18.0 → execsql2-2.18.1}/tests/security/test_env_detection.py +0 -0
  330. {execsql2-2.18.0 → execsql2-2.18.1}/tests/security/test_expansion_bomb.py +0 -0
  331. {execsql2-2.18.0 → execsql2-2.18.1}/tests/security/test_import_regex_hardening.py +0 -0
  332. {execsql2-2.18.0 → execsql2-2.18.1}/tests/security/test_log_redaction.py +0 -0
  333. {execsql2-2.18.0 → execsql2-2.18.1}/tests/security/test_odbc_injection.py +0 -0
  334. {execsql2-2.18.0 → execsql2-2.18.1}/tests/security/test_path_containment.py +0 -0
  335. {execsql2-2.18.0 → execsql2-2.18.1}/tests/security/test_substitution_injection.py +0 -0
  336. {execsql2-2.18.0 → execsql2-2.18.1}/tests/security/test_zip_bomb.py +0 -0
  337. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_api.py +0 -0
  338. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_api_extended.py +0 -0
  339. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_ast.py +0 -0
  340. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_ast_parser.py +0 -0
  341. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_bundled_templates.py +0 -0
  342. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_config.py +0 -0
  343. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_config_data.py +0 -0
  344. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_config_extended.py +0 -0
  345. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_debug_repl.py +0 -0
  346. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_error_messages.py +0 -0
  347. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_exceptions.py +0 -0
  348. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_executor_inprocess.py +0 -0
  349. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_format.py +0 -0
  350. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_mail.py +0 -0
  351. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_models.py +0 -0
  352. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_package.py +0 -0
  353. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_parser.py +0 -0
  354. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_parser_params.py +0 -0
  355. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_plugins.py +0 -0
  356. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_registry.py +0 -0
  357. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_script.py +0 -0
  358. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_state.py +0 -0
  359. {execsql2-2.18.0 → execsql2-2.18.1}/tests/test_types.py +0 -0
  360. {execsql2-2.18.0 → execsql2-2.18.1}/tests/utils/__init__.py +0 -0
  361. {execsql2-2.18.0 → execsql2-2.18.1}/tests/utils/test_auth.py +0 -0
  362. {execsql2-2.18.0 → execsql2-2.18.1}/tests/utils/test_auth_extra.py +0 -0
  363. {execsql2-2.18.0 → execsql2-2.18.1}/tests/utils/test_crypto.py +0 -0
  364. {execsql2-2.18.0 → execsql2-2.18.1}/tests/utils/test_datetime.py +0 -0
  365. {execsql2-2.18.0 → execsql2-2.18.1}/tests/utils/test_encodedfile_context.py +0 -0
  366. {execsql2-2.18.0 → execsql2-2.18.1}/tests/utils/test_errors.py +0 -0
  367. {execsql2-2.18.0 → execsql2-2.18.1}/tests/utils/test_errors_extra.py +0 -0
  368. {execsql2-2.18.0 → execsql2-2.18.1}/tests/utils/test_fileio.py +0 -0
  369. {execsql2-2.18.0 → execsql2-2.18.1}/tests/utils/test_fileio_extra.py +0 -0
  370. {execsql2-2.18.0 → execsql2-2.18.1}/tests/utils/test_numeric.py +0 -0
  371. {execsql2-2.18.0 → execsql2-2.18.1}/tests/utils/test_regex.py +0 -0
  372. {execsql2-2.18.0 → execsql2-2.18.1}/tests/utils/test_strings.py +0 -0
  373. {execsql2-2.18.0 → execsql2-2.18.1}/tests/utils/test_timer.py +0 -0
  374. {execsql2-2.18.0 → execsql2-2.18.1}/tests/utils/test_timer_extra.py +0 -0
  375. {execsql2-2.18.0 → execsql2-2.18.1}/zensical.toml +0 -0
@@ -283,7 +283,7 @@ jobs:
283
283
  name: ${{ github.event.repository.name }}
284
284
  path: dist/
285
285
  - name: Create release
286
- uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
286
+ uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
287
287
  with:
288
288
  draft: false
289
289
  prerelease: false
@@ -13,6 +13,18 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.18.1] - 2026-05-28
17
+
18
+ ### Changed
19
+
20
+ - Internal: `execsql.cli.lint_ast` has been folded into `execsql.cli.lint`; the AST walker entry point is now `execsql.cli.lint.lint()`. Code that imported `execsql.cli.lint_ast.lint_ast` should import `execsql.cli.lint.lint` instead.
21
+
22
+ ### Removed
23
+
24
+ - Internal: the unused `run_when_false` and `run_in_batch` flags on `MetaCommand` (and the matching keyword arguments on `MetaCommandList.add()`) — neither has been consulted since v2.16.0. Code that still passes either kwarg to `mcl.add()` will raise `TypeError`.
25
+
26
+ ______________________________________________________________________
27
+
16
28
  ## [2.18.0] - 2026-05-27
17
29
 
18
30
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.18.0
3
+ Version: 2.18.1
4
4
  Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
5
5
  Project-URL: Homepage, https://execsql2.readthedocs.io
6
6
  Project-URL: Repository, https://github.com/geocoug/execsql
@@ -228,44 +228,46 @@ execsql script.sql # read connection from config file
228
228
 
229
229
  ## Options
230
230
 
231
- | Flag | Description |
232
- | ------------------------------------- | ----------------------------------------------------------------- |
233
- | `-t {p,m,s,l,k,a,f,o,d}` | Database type |
234
- | `-u USER` | Database username |
235
- | `-p PORT` | Server port |
236
- | `-a VALUE` | Set substitution variable `$ARG_x` |
237
- | `-b` / `--boolean-int` | Treat integers 0 and 1 as boolean values |
238
- | `-c SCRIPT` | Execute inline SQL or metacommand string |
239
- | `-d` | Auto-create export directories |
240
- | `-e ENCODING` / `--database-encoding` | Character encoding used in the database |
241
- | `-f ENCODING` | Script file encoding (default: UTF-8) |
242
- | `-g ENCODING` / `--output-encoding` | Encoding for WRITE and EXPORT output |
243
- | `-i ENCODING` / `--import-encoding` | Encoding for data files used with IMPORT |
244
- | `-l` | Write run log to `~/execsql.log` |
245
- | `-m` | List metacommands and exit |
246
- | `-n` | Create a new SQLite or PostgreSQL database if it does not exist |
247
- | `-o` / `--online-help` | Open the online documentation in the default browser |
248
- | `-s N` / `--scan-lines` | Lines to scan for IMPORT format detection (0 = scan entire file) |
249
- | `-v {0,1,2,3}` | GUI level (0=none, 1=password, 2=selection, 3=full) |
250
- | `-w` | Skip password prompt when a username is supplied |
251
- | `-y` / `--encodings` | List available encoding names and exit |
252
- | `-z KB` / `--import-buffer` | Import buffer size in KB (default: 32) |
253
- | `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
254
- | `--output-dir DIR` | Default base directory for EXPORT output files |
255
- | `--dry-run` | Parse the script and report commands without executing |
256
- | `--lint` | Static analysis: check structure and warn on issues (no DB) |
257
- | `--parse-tree` | Print the script's AST structure and exit (no DB) |
258
- | `--list-plugins` | List discovered plugins and exit |
259
- | `--ping` | Test database connectivity and exit |
260
- | `--profile` | Show per-statement timing summary after execution |
261
- | `--profile-limit N` | Top N statements to display in `--profile` summary (default: 20) |
262
- | `--progress` | Show a progress bar for long-running IMPORT operations |
263
- | `--config FILE` | Load an explicit config file (highest priority after CLI args) |
264
- | `--no-system-cmd` | Disable the `SYSTEM_CMD` metacommand (safer for CI / shared envs) |
265
- | `--init-config` | Print a default `execsql.conf` template to stdout and exit |
266
- | `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
267
- | `--dump-keywords` | Print metacommand keywords as JSON and exit |
268
- | `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
231
+ | Flag | Description |
232
+ | ------------------------------------- | ------------------------------------------------------------------ |
233
+ | `-t {p,m,s,l,k,a,f,o,d}` | Database type |
234
+ | `-u USER` | Database username |
235
+ | `-p PORT` | Server port |
236
+ | `-a VALUE` | Set substitution variable `$ARG_x` |
237
+ | `-b` / `--boolean-int` | Treat integers 0 and 1 as boolean values |
238
+ | `-c SCRIPT` | Execute inline SQL or metacommand string |
239
+ | `-d` | Auto-create export directories |
240
+ | `-e ENCODING` / `--database-encoding` | Character encoding used in the database |
241
+ | `-f ENCODING` | Script file encoding (default: UTF-8) |
242
+ | `-g ENCODING` / `--output-encoding` | Encoding for WRITE and EXPORT output |
243
+ | `-i ENCODING` / `--import-encoding` | Encoding for data files used with IMPORT |
244
+ | `-l` | Write run log to `~/execsql.log` |
245
+ | `-m` | List metacommands and exit |
246
+ | `-n` | Create a new SQLite or PostgreSQL database if it does not exist |
247
+ | `-o` / `--online-help` | Open the online documentation in the default browser |
248
+ | `-s N` / `--scan-lines` | Lines to scan for IMPORT format detection (0 = scan entire file) |
249
+ | `-v {0,1,2,3}` | GUI level (0=none, 1=password, 2=selection, 3=full) |
250
+ | `-w` | Skip password prompt when a username is supplied |
251
+ | `-y` / `--encodings` | List available encoding names and exit |
252
+ | `-z KB` / `--import-buffer` | Import buffer size in KB (default: 32) |
253
+ | `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
254
+ | `--output-dir DIR` | Default base directory for EXPORT output files |
255
+ | `--dry-run` | Parse the script and report commands without executing |
256
+ | `--lint` | Static analysis: check structure and warn on issues (no DB) |
257
+ | `--parse-tree` | Print the script's AST structure and exit (no DB) |
258
+ | `--list-plugins` | List discovered plugins and exit |
259
+ | `--ping` | Test database connectivity and exit |
260
+ | `--profile` | Show per-statement timing summary after execution |
261
+ | `--profile-limit N` | Top N statements to display in `--profile` summary (default: 20) |
262
+ | `--progress` | Show a progress bar for long-running IMPORT operations |
263
+ | `--config FILE` | Load an explicit config file (highest priority after CLI args) |
264
+ | `--no-system-cmd` | Disable the `SYSTEM_CMD` metacommand (safer for CI / shared envs) |
265
+ | `--no-rm-file` | Disable the `RM_FILE` metacommand (no script-driven file deletion) |
266
+ | `--no-serve` | Disable the `SERVE` metacommand (no script-driven file streaming) |
267
+ | `--init-config` | Print a default `execsql.conf` template to stdout and exit |
268
+ | `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
269
+ | `--dump-keywords` | Print metacommand keywords as JSON and exit |
270
+ | `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
269
271
 
270
272
  Run `execsql --help` for the full option list, or `execsql -m` to list all metacommands.
271
273
 
@@ -393,7 +395,7 @@ execsql-format --check scripts/
393
395
  ```yaml
394
396
  repos:
395
397
  - repo: https://github.com/geocoug/execsql
396
- rev: v2.11.0
398
+ rev: v2.18.0
397
399
  hooks:
398
400
  - id: execsql-format
399
401
  args: [--in-place]
@@ -99,44 +99,46 @@ execsql script.sql # read connection from config file
99
99
 
100
100
  ## Options
101
101
 
102
- | Flag | Description |
103
- | ------------------------------------- | ----------------------------------------------------------------- |
104
- | `-t {p,m,s,l,k,a,f,o,d}` | Database type |
105
- | `-u USER` | Database username |
106
- | `-p PORT` | Server port |
107
- | `-a VALUE` | Set substitution variable `$ARG_x` |
108
- | `-b` / `--boolean-int` | Treat integers 0 and 1 as boolean values |
109
- | `-c SCRIPT` | Execute inline SQL or metacommand string |
110
- | `-d` | Auto-create export directories |
111
- | `-e ENCODING` / `--database-encoding` | Character encoding used in the database |
112
- | `-f ENCODING` | Script file encoding (default: UTF-8) |
113
- | `-g ENCODING` / `--output-encoding` | Encoding for WRITE and EXPORT output |
114
- | `-i ENCODING` / `--import-encoding` | Encoding for data files used with IMPORT |
115
- | `-l` | Write run log to `~/execsql.log` |
116
- | `-m` | List metacommands and exit |
117
- | `-n` | Create a new SQLite or PostgreSQL database if it does not exist |
118
- | `-o` / `--online-help` | Open the online documentation in the default browser |
119
- | `-s N` / `--scan-lines` | Lines to scan for IMPORT format detection (0 = scan entire file) |
120
- | `-v {0,1,2,3}` | GUI level (0=none, 1=password, 2=selection, 3=full) |
121
- | `-w` | Skip password prompt when a username is supplied |
122
- | `-y` / `--encodings` | List available encoding names and exit |
123
- | `-z KB` / `--import-buffer` | Import buffer size in KB (default: 32) |
124
- | `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
125
- | `--output-dir DIR` | Default base directory for EXPORT output files |
126
- | `--dry-run` | Parse the script and report commands without executing |
127
- | `--lint` | Static analysis: check structure and warn on issues (no DB) |
128
- | `--parse-tree` | Print the script's AST structure and exit (no DB) |
129
- | `--list-plugins` | List discovered plugins and exit |
130
- | `--ping` | Test database connectivity and exit |
131
- | `--profile` | Show per-statement timing summary after execution |
132
- | `--profile-limit N` | Top N statements to display in `--profile` summary (default: 20) |
133
- | `--progress` | Show a progress bar for long-running IMPORT operations |
134
- | `--config FILE` | Load an explicit config file (highest priority after CLI args) |
135
- | `--no-system-cmd` | Disable the `SYSTEM_CMD` metacommand (safer for CI / shared envs) |
136
- | `--init-config` | Print a default `execsql.conf` template to stdout and exit |
137
- | `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
138
- | `--dump-keywords` | Print metacommand keywords as JSON and exit |
139
- | `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
102
+ | Flag | Description |
103
+ | ------------------------------------- | ------------------------------------------------------------------ |
104
+ | `-t {p,m,s,l,k,a,f,o,d}` | Database type |
105
+ | `-u USER` | Database username |
106
+ | `-p PORT` | Server port |
107
+ | `-a VALUE` | Set substitution variable `$ARG_x` |
108
+ | `-b` / `--boolean-int` | Treat integers 0 and 1 as boolean values |
109
+ | `-c SCRIPT` | Execute inline SQL or metacommand string |
110
+ | `-d` | Auto-create export directories |
111
+ | `-e ENCODING` / `--database-encoding` | Character encoding used in the database |
112
+ | `-f ENCODING` | Script file encoding (default: UTF-8) |
113
+ | `-g ENCODING` / `--output-encoding` | Encoding for WRITE and EXPORT output |
114
+ | `-i ENCODING` / `--import-encoding` | Encoding for data files used with IMPORT |
115
+ | `-l` | Write run log to `~/execsql.log` |
116
+ | `-m` | List metacommands and exit |
117
+ | `-n` | Create a new SQLite or PostgreSQL database if it does not exist |
118
+ | `-o` / `--online-help` | Open the online documentation in the default browser |
119
+ | `-s N` / `--scan-lines` | Lines to scan for IMPORT format detection (0 = scan entire file) |
120
+ | `-v {0,1,2,3}` | GUI level (0=none, 1=password, 2=selection, 3=full) |
121
+ | `-w` | Skip password prompt when a username is supplied |
122
+ | `-y` / `--encodings` | List available encoding names and exit |
123
+ | `-z KB` / `--import-buffer` | Import buffer size in KB (default: 32) |
124
+ | `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
125
+ | `--output-dir DIR` | Default base directory for EXPORT output files |
126
+ | `--dry-run` | Parse the script and report commands without executing |
127
+ | `--lint` | Static analysis: check structure and warn on issues (no DB) |
128
+ | `--parse-tree` | Print the script's AST structure and exit (no DB) |
129
+ | `--list-plugins` | List discovered plugins and exit |
130
+ | `--ping` | Test database connectivity and exit |
131
+ | `--profile` | Show per-statement timing summary after execution |
132
+ | `--profile-limit N` | Top N statements to display in `--profile` summary (default: 20) |
133
+ | `--progress` | Show a progress bar for long-running IMPORT operations |
134
+ | `--config FILE` | Load an explicit config file (highest priority after CLI args) |
135
+ | `--no-system-cmd` | Disable the `SYSTEM_CMD` metacommand (safer for CI / shared envs) |
136
+ | `--no-rm-file` | Disable the `RM_FILE` metacommand (no script-driven file deletion) |
137
+ | `--no-serve` | Disable the `SERVE` metacommand (no script-driven file streaming) |
138
+ | `--init-config` | Print a default `execsql.conf` template to stdout and exit |
139
+ | `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
140
+ | `--dump-keywords` | Print metacommand keywords as JSON and exit |
141
+ | `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
140
142
 
141
143
  Run `execsql --help` for the full option list, or `execsql -m` to list all metacommands.
142
144
 
@@ -264,7 +266,7 @@ execsql-format --check scripts/
264
266
  ```yaml
265
267
  repos:
266
268
  - repo: https://github.com/geocoug/execsql
267
- rev: v2.11.0
269
+ rev: v2.18.0
268
270
  hooks:
269
271
  - id: execsql-format
270
272
  args: [--in-place]
@@ -296,9 +296,10 @@ ______________________________________________________________________
296
296
 
297
297
  ## Removed Features
298
298
 
299
- | Feature | Reason |
300
- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
301
- | Airspeed template processor | The `airspeed` library (Velocity clone) is unmaintained since ~2018. Use `FORMAT jinja` instead. The `airspeed` value for `template_processor` in `execsql.conf` is no longer accepted. |
302
- | Python 2 compatibility | All Python 2 constructs (`stringtypes`, `u""` literals, `optparse`, etc.) have been removed. execsql2 requires Python 3.10+. |
303
- | `FREE` keyword on `PROMPT DISPLAY` | The non-blocking display behavior was only implemented in the console backend; the Textual and Tkinter GUI backends ignored it. Removed rather than partially supported. |
304
- | Legacy command-list execution engine | The `CommandList` data class, `IfLevels` / `IfItem` classes, and the `.run()` methods on `SqlStmt` / `MetacommandStmt` / `ScriptCmd` were the flat command-list engine inherited from the monolith. The AST executor has been the sole engine since v2.16.0; everything dependent on the flat-engine bookkeeping (`runscripts`, `check_iflevels`, `endloop`, and the `commandliststack` / `loopcommandstack` / `compiling_loop` / `loop_nest_level` / `if_stack` / `savedscripts` slots on `RuntimeContext`) has been removed. Variable scoping migrated to the unified `ast_exec_stack` via the new `ExecFrame` data class. Advanced consumers that read `_state.commandliststack` directly should use `_state.current_localvars()` / `current_paramvals()` / `outer_script_scopes()` instead. |
299
+ | Feature | Reason |
300
+ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
301
+ | Airspeed template processor | The `airspeed` library (Velocity clone) is unmaintained since ~2018. Use `FORMAT jinja` instead. The `airspeed` value for `template_processor` in `execsql.conf` is no longer accepted. |
302
+ | Python 2 compatibility | All Python 2 constructs (`stringtypes`, `u""` literals, `optparse`, etc.) have been removed. execsql2 requires Python 3.10+. |
303
+ | `FREE` keyword on `PROMPT DISPLAY` | The non-blocking display behavior was only implemented in the console backend; the Textual and Tkinter GUI backends ignored it. Removed rather than partially supported. |
304
+ | Legacy command-list execution engine | The `CommandList` data class, `IfLevels` / `IfItem` classes, and the `.run()` methods on `SqlStmt` / `MetacommandStmt` / `ScriptCmd` were the flat command-list engine inherited from the monolith. The AST executor has been the sole engine since v2.16.0; everything dependent on the flat-engine bookkeeping (`runscripts`, `check_iflevels`, `endloop`, and the `commandliststack` / `loopcommandstack` / `compiling_loop` / `loop_nest_level` / `if_stack` / `savedscripts` slots on `RuntimeContext`) has been removed. Variable scoping migrated to the unified `ast_exec_stack` via the new `ExecFrame` data class. Advanced consumers that read `_state.commandliststack` directly should use `_state.current_localvars()` / `current_paramvals()` / `outer_script_scopes()` instead. |
305
+ | `run_when_false` / `run_in_batch` flags on `MetaCommand` | The two boolean flags on `MetaCommand` (and the matching keyword arguments on `MetaCommandList.add()`) were the flat-engine's dispatch-time gates: `run_when_false` let `ELSE` / `ENDIF` handlers fire even when the IF stack was false, and `run_in_batch` permitted `END BATCH` / `ROLLBACK` to run inside a `BEGIN BATCH` block. Under the AST executor IF branching is structural, so handlers only execute on the chosen branch; the live `BEGIN BATCH` gate is `BatchLevels.in_batch()`. Neither flag was read by any live code path; both were removed in v2.18.1 along with the two tests that pinned their presence on every dispatch entry. Code that passed these kwargs to `mcl.add()` will raise `TypeError`. |
@@ -26,7 +26,7 @@ ______________________________________________________________________
26
26
  1. It extracts the leading keyword and narrows candidates via the keyword index, falling back to the full list if no keyword matches.
27
27
  1. It calls the handler, passing all named regex groups plus `"metacommandline"` as keyword arguments.
28
28
 
29
- Conditional branching (`IF` / `ELSEIF` / `ELSE` / `ENDIF`) is **structural** under the AST executor — the parser turns those metacommands into `IfBlock` AST nodes and the executor only invokes handlers for the branch it actually entered. Handlers therefore do not need to consult any IF state, and `run_when_false` is no longer honoured.
29
+ Conditional branching (`IF` / `ELSEIF` / `ELSE` / `ENDIF`) is **structural** under the AST executor — the parser turns those metacommands into `IfBlock` AST nodes and the executor only invokes handlers for the branch it actually entered. Handlers therefore do not need to consult any IF state; the `run_when_false` and `run_in_batch` flags that used to gate dispatch under the flat command-list engine were removed in v2.18.1.
30
30
 
31
31
  ### Handler naming conventions
32
32
 
@@ -114,8 +114,6 @@ mcl.add(
114
114
  | `matching_regexes` | `str` or `tuple[str, ...]` | required | One regex string, or a tuple of strings all mapped to the same handler |
115
115
  | `exec_func` | callable | required | The handler function |
116
116
  | `description` | `str \| None` | `None` | Human-readable keyword name (shown in `DEBUG WRITE METACOMMANDLIST` and `--dump-keywords`) |
117
- | `run_in_batch` | `bool` | `False` | Allow execution inside an open `BEGIN_BATCH`/`END_BATCH` block |
118
- | `run_when_false` | `bool` | `False` | Execute even when the `IF`-stack condition is false (needed for `ELSE`, `ENDIF`, etc.) |
119
117
  | `set_error_flag` | `bool` | `True` | Update `_state.status.metacommand_error` on success/failure |
120
118
  | `category` | `str \| None` | `None` | Keyword category for `--dump-keywords` and VS Code grammar generation (e.g., `"action"`, `"control"`, `"config"`) |
121
119
 
@@ -85,7 +85,7 @@ flowchart LR
85
85
  | `api.py` | Public `execsql.run()` Python entry point for notebooks, pipelines, and library use |
86
86
  | `config.py` | `ConfigData` (INI merging), `StatObj` (runtime flags), `WriteHooks` (stdout/stderr redirection) |
87
87
  | `state.py` | Thread-local runtime store — all shared mutable state lives here, isolated per-thread |
88
- | `script/` | AST parser/executor, `CommandList`, `MetaCommandList`, `SubVarSet`, `ScriptFile` |
88
+ | `script/` | AST node types, parser, `MetaCommandList`, `SubVarSet`, `BatchLevels`, `ScriptExecSpec`, `set_system_vars()` |
89
89
  | `metacommands/` | `build_dispatch_table()`, all `x_*` handlers, `build_conditional_table()`, all `xf_*` predicates |
90
90
  | `db/` | `Database` ABC, `DatabasePool`, 9 adapter modules (postgres, sqlite, duckdb, mysql, sqlserver, oracle, firebird, access, dsn) |
91
91
  | `exporters/` | `ExportRecord`, `ExportMetadata`, `WriteSpec`, 20+ format writers (CSV, JSON, XML, HTML, etc.) |
@@ -110,7 +110,7 @@ execsql2 parses scripts into an AST and walks the tree to execute. There is no s
110
110
  - **`script/ast.py`** — defines 9 `Node` subclasses (`SqlStatement`, `MetaCommandStatement`, `Comment`, `IfBlock`, `LoopBlock`, `BatchBlock`, `ScriptBlock`, `SqlBlock`, `IncludeDirective`) plus the `Script` root and supporting types (`SourceSpan` for source locations, `ConditionModifier` for ANDIF/ORIF, `ElseIfClause`, `ParamDef`).
111
111
  - **`script/parser.py`** — `parse_script(path)` / `parse_string(text)` produce a `Script` tree. All block structures (IF/LOOP/BATCH/SCRIPT/SQL) are resolved at parse time into nested nodes; the parser also reports structural errors (unmatched blocks).
112
112
  - **`script/executor.py`** — `execute(tree, ctx=)` walks the tree via `_execute_nodes()` / `_execute_node()`. SQL and metacommands delegate to the existing dispatch tables. INCLUDE'd files are parsed and recursed into natively; circular INCLUDE references are detected via `ctx.include_chain` and reported. Named SCRIPT blocks register in `ctx.ast_scripts` (instance-scoped) for later `EXECUTE SCRIPT` lookup. `ON ERROR_HALT` / `ON CANCEL_HALT EXECUTE SCRIPT` deferred scripts also run through the AST executor.
113
- - **`cli/lint_ast.py`** — AST-based linter for variable and INCLUDE checks; structural validation lives in the parser.
113
+ - **`cli/lint.py`** — AST-based linter for variable and INCLUDE checks plus the Rich result printer; structural validation lives in the parser.
114
114
 
115
115
  Use `--parse-tree` to print the AST without executing.
116
116
 
@@ -139,7 +139,7 @@ Metacommands are lines in SQL scripts prefixed with `-- !x!`. At import time, `m
139
139
 
140
140
  ### How dispatch works
141
141
 
142
- `MetacommandStmt.run()` delegates to `_state.metacommandlist.eval(cmd_str)`. The dispatcher extracts the leading keyword, narrows ~225 entries to a small candidate set via a keyword index, then tests each candidate's compiled regex against the full command. The matched handler is called with the regex's named groups as keyword arguments plus `metacommandline` (the original unmodified line), and its hit counter is incremented.
142
+ For each `MetaCommandStatement` AST node, the executor calls `_exec_metacommand()` in `script/executor.py`, which delegates to `_state.metacommandlist.eval(cmd_str)`. The dispatcher extracts the leading keyword, narrows ~225 entries to a small candidate set via a keyword index, then tests each candidate's compiled regex against the full command. The matched handler is called with the regex's named groups as keyword arguments plus `metacommandline` (the original unmodified line), and its hit counter is incremented.
143
143
 
144
144
  ### Handler conventions
145
145
 
@@ -160,7 +160,7 @@ The `IF`/`ELSEIF`/`ELSE`/`ENDIF` metacommands control conditional execution stru
160
160
 
161
161
  `_execute_if()` evaluates the IF condition (and any ANDIF/ORIF modifiers) via `CondParser`, then walks the chosen branch (the IF body, one of the ELSEIF clauses, or the ELSE body). For tracking and REPL introspection it pushes a non-scope `ExecFrame` (`kind="if"`/`"elseif"`/`"else"`) onto `ctx.ast_exec_stack` whose `scope_ref` points at the enclosing SCRIPT/main scope; the frame is popped when the branch finishes.
162
162
 
163
- Because branching is structural, the legacy `_state.if_stack` / `IfLevels` machinery and the `run_when_false` flag on metacommand handlers are no longer in use; the dispatch handlers for `IF` / `ELSEIF` / `ELSE` / `ENDIF` / `ANDIF` / `ORIF` / `BREAK` / `BEGIN BATCH` / `END BATCH` remain registered as `ErrInfo` stubs so a parser regression that bypassed the AST would fail loudly instead of silently.
163
+ Because branching is structural, the executor never consults dispatch-time flags to decide whether an IF/ELSE/ELSEIF/ENDIF/ANDIF/ORIF handler should fire the parser has already chosen the live branch. The corresponding dispatch entries (and the `BREAK` / `BEGIN BATCH` / `END BATCH` entries) remain registered as `ErrInfo` stubs so that a parser regression which let one of those metacommands fall through to the dispatch table would fail loudly rather than silently. (The pre-AST flat-command-list engine relied on a `_state.if_stack` + `IfLevels` state machine and matching `run_when_false` / `run_in_batch` flags on `MetaCommand`; both were removed once the AST became the sole execution engine.)
164
164
 
165
165
  ### CondParser
166
166
 
@@ -43,13 +43,13 @@ ______________________________________________________________________
43
43
  Create a symlink from the VSCode extensions directory to this folder:
44
44
 
45
45
  ```sh
46
- ln -s /path/to/execsql/vscode-execsql ~/.vscode/extensions/execsql-syntax
46
+ ln -s /path/to/execsql/extras/vscode-execsql ~/.vscode/extensions/execsql-syntax
47
47
  ```
48
48
 
49
49
  For example, if you cloned the repo to `./execsql`:
50
50
 
51
51
  ```sh
52
- ln -s ./execsql/vscode-execsql ~/.vscode/extensions/execsql-syntax
52
+ ln -s "$(pwd)/execsql/extras/vscode-execsql" ~/.vscode/extensions/execsql-syntax
53
53
  ```
54
54
 
55
55
  ### Windows
@@ -57,13 +57,13 @@ ln -s ./execsql/vscode-execsql ~/.vscode/extensions/execsql-syntax
57
57
  Create a directory junction using Command Prompt **as Administrator**:
58
58
 
59
59
  ```cmd
60
- mklink /J "%USERPROFILE%\.vscode\extensions\execsql-syntax" "C:\path\to\execsql\vscode-execsql"
60
+ mklink /J "%USERPROFILE%\.vscode\extensions\execsql-syntax" "C:\path\to\execsql\extras\vscode-execsql"
61
61
  ```
62
62
 
63
63
  Or using PowerShell **as Administrator**:
64
64
 
65
65
  ```powershell
66
- New-Item -ItemType Junction -Path "$env:USERPROFILE\.vscode\extensions\execsql-syntax" -Target "C:\path\to\execsql\vscode-execsql"
66
+ New-Item -ItemType Junction -Path "$env:USERPROFILE\.vscode\extensions\execsql-syntax" -Target "C:\path\to\execsql\extras\vscode-execsql"
67
67
  ```
68
68
 
69
69
  ### After installing
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.18.0"
7
+ version = "2.18.1"
8
8
  description = "Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = { file = "LICENSE.txt" }
@@ -168,7 +168,7 @@ skip-magic-trailing-comma = false
168
168
  line-ending = "auto"
169
169
 
170
170
  [tool.bumpversion]
171
- current_version = "2.18.0"
171
+ current_version = "2.18.1"
172
172
  commit = true
173
173
  tag = true
174
174
  tag_name = "v{new_version}"
@@ -8,8 +8,7 @@ Submodules:
8
8
  - :mod:`execsql.cli.help` — Rich-formatted help output & console objects
9
9
  - :mod:`execsql.cli.dsn` — Connection-string (DSN URL) parser
10
10
  - :mod:`execsql.cli.run` — Core execution logic (``_run``, ``_connect_initial_db``, ``_ping_db``, ``_print_dry_run``, ``_print_profile``)
11
- - :mod:`execsql.cli.lint_ast` — AST-based ``--lint`` static analyser
12
- - :mod:`execsql.cli.lint` — Shared lint result printing (``_print_lint_results``) used by the AST linter
11
+ - :mod:`execsql.cli.lint` — AST-based ``--lint`` static analyser and Rich result printer
13
12
  """
14
13
 
15
14
  from __future__ import annotations
@@ -554,8 +553,7 @@ def main(
554
553
  # Lint: AST-based static analysis (no DB connection needed)
555
554
  # ------------------------------------------------------------------
556
555
  if lint:
557
- from execsql.cli.lint import _print_lint_results
558
- from execsql.cli.lint_ast import lint_ast
556
+ from execsql.cli.lint import _print_lint_results, lint as _lint_script
559
557
  from execsql.script.parser import parse_script, parse_string
560
558
 
561
559
  label = script_name or "<inline>"
@@ -576,7 +574,7 @@ def main(
576
574
  exit_code = _print_lint_results(issues, label)
577
575
  raise typer.Exit(code=exit_code) from exc
578
576
 
579
- issues = lint_ast(tree, script_path=script_name)
577
+ issues = _lint_script(tree, script_path=script_name)
580
578
  exit_code = _print_lint_results(issues, label)
581
579
  raise typer.Exit(code=exit_code)
582
580
 
@@ -1,29 +1,37 @@
1
- """AST-based static analysis (lint) for execsql scripts.
2
-
3
- Performs the same checks as :mod:`execsql.cli.lint` but operates on the
4
- :class:`~execsql.script.ast.Script` tree instead of a flat
5
- :class:`~execsql.script.engine.CommandList`.
6
-
7
- Advantages over the flat linter:
8
-
9
- - **No runtime state required** — works with the AST parser alone, so it
10
- can run as an early exit in the CLI without initialising ``_state``.
11
- - **Structural validation is free** the AST parser already rejects
12
- unmatched IF/LOOP/BATCH/SCRIPT blocks at parse time with precise source
13
- spans. This linter only needs to report variable and INCLUDE issues.
14
- - **Script blocks are in the tree** ``EXECUTE SCRIPT`` targets are
15
- resolved by finding :class:`ScriptBlock` nodes, not by looking up
16
- ``_state.savedscripts``.
17
-
18
- Checks performed
19
- ----------------
20
- 1. **Parse errors** — the AST parser rejects unmatched blocks, so any
21
- parse failure is reported as an error with the parser's message.
22
- 2. **Potentially undefined variables** — same heuristic as the flat linter.
23
- 3. **EXECUTE SCRIPT target resolution** warns when a target name does
24
- not correspond to a :class:`ScriptBlock` in the same file.
25
- 4. **Missing INCLUDE files** — warns when the file does not exist on disk.
26
- 5. **Empty script** — warns when no nodes were parsed.
1
+ """AST-based static analysis (``--lint``) for execsql scripts.
2
+
3
+ Operates on the :class:`~execsql.script.ast.Script` tree produced by
4
+ :func:`execsql.script.parser.parse_script` / ``parse_string``. Runs as
5
+ an early CLI exit — no DB connection and no ``_state`` initialisation
6
+ required.
7
+
8
+ Checks performed:
9
+
10
+ 1. **Parse errors** the AST parser rejects unmatched IF / LOOP /
11
+ BATCH / SCRIPT blocks at parse time with precise source spans;
12
+ ``cli/__init__.py`` reports any parse failure as a lint error before
13
+ :func:`lint` is even called.
14
+ 2. **Empty scripts** warns when no nodes were parsed.
15
+ 3. **Potentially undefined variables** flags ``!!$VAR!!`` references
16
+ with no preceding ``SUB``-family definition, ignoring built-in
17
+ ``$VAR`` names discovered from the package source and the
18
+ non-``$`` sigils (``~``, ``#``, ``+``, ``@``, ``&``) that resolve at
19
+ runtime.
20
+ 4. **Missing INCLUDE files** — warns when the resolved target does not
21
+ exist on disk (skipped when ``IF EXISTS`` is present).
22
+ 5. **EXECUTE SCRIPT target resolution** — warns when a target name does
23
+ not correspond to a :class:`ScriptBlock` in the same file (skipped
24
+ when ``IF EXISTS`` is present).
25
+
26
+ Public surface:
27
+
28
+ - :func:`lint` — entry point; returns a list of
29
+ ``(severity, source, line_no, message)`` tuples.
30
+ - :func:`_print_lint_results` — Rich console formatter for those
31
+ tuples; returns the ``--lint`` process exit code (``1`` when any
32
+ error-severity issue is present, ``0`` otherwise).
33
+ - :data:`_Issue`, :func:`_error`, :func:`_warning` — tuple type alias
34
+ and constructors used by the walker and the formatter.
27
35
  """
28
36
 
29
37
  from __future__ import annotations
@@ -44,11 +52,27 @@ from execsql.script.ast import (
44
52
  SqlStatement,
45
53
  )
46
54
 
47
- __all__ = ["lint_ast"]
55
+ __all__ = ["_Issue", "_error", "_print_lint_results", "_warning", "lint"]
48
56
 
49
57
 
50
58
  # ---------------------------------------------------------------------------
51
- # Variable-related patterns (shared with the flat linter)
59
+ # Issue tuple type and constructors
60
+ # ---------------------------------------------------------------------------
61
+
62
+
63
+ _Issue = tuple[str, str, int, str] # (severity, source, line_no, message)
64
+
65
+
66
+ def _error(source: str, line_no: int, message: str) -> _Issue:
67
+ return ("error", source, line_no, message)
68
+
69
+
70
+ def _warning(source: str, line_no: int, message: str) -> _Issue:
71
+ return ("warning", source, line_no, message)
72
+
73
+
74
+ # ---------------------------------------------------------------------------
75
+ # Variable-related patterns
52
76
  # ---------------------------------------------------------------------------
53
77
 
54
78
  _RX_SUB = re.compile(r"^\s*SUB\s+(?P<name>[+~]?\w+)\s+", re.I)
@@ -72,22 +96,7 @@ _RX_VAR_REF = re.compile(r"!!([$@&~#+]?\w+)!!", re.I)
72
96
 
73
97
 
74
98
  # ---------------------------------------------------------------------------
75
- # Issue tuple helpers
76
- # ---------------------------------------------------------------------------
77
-
78
- _Issue = tuple[str, str, int, str] # (severity, source, line_no, message)
79
-
80
-
81
- def _error(source: str, line_no: int, message: str) -> _Issue:
82
- return ("error", source, line_no, message)
83
-
84
-
85
- def _warning(source: str, line_no: int, message: str) -> _Issue:
86
- return ("warning", source, line_no, message)
87
-
88
-
89
- # ---------------------------------------------------------------------------
90
- # Built-in variable discovery (reuse from flat linter)
99
+ # Built-in variable discovery
91
100
  # ---------------------------------------------------------------------------
92
101
 
93
102
 
@@ -396,11 +405,11 @@ def _lint_nodes(
396
405
 
397
406
 
398
407
  # ---------------------------------------------------------------------------
399
- # Public API
408
+ # Public entry point
400
409
  # ---------------------------------------------------------------------------
401
410
 
402
411
 
403
- def lint_ast(
412
+ def lint(
404
413
  script: Script,
405
414
  script_path: str | None = None,
406
415
  ) -> list[_Issue]:
@@ -437,3 +446,61 @@ def lint_ast(
437
446
  )
438
447
 
439
448
  return issues
449
+
450
+
451
+ # ---------------------------------------------------------------------------
452
+ # Result printing
453
+ # ---------------------------------------------------------------------------
454
+
455
+
456
+ def _print_lint_results(issues: list[_Issue], script_label: str) -> int:
457
+ """Print lint issues to the console using Rich formatting.
458
+
459
+ Args:
460
+ issues: List of ``(severity, source, line_no, message)`` tuples.
461
+ script_label: Human-readable label for the script (file path or
462
+ ``<inline>``), shown in the summary line.
463
+
464
+ Returns:
465
+ ``1`` if any errors were found, ``0`` if only warnings or nothing.
466
+ """
467
+ from execsql.cli.help import _console
468
+
469
+ n_errors = sum(1 for sev, *_ in issues if sev == "error")
470
+ n_warnings = sum(1 for sev, *_ in issues if sev == "warning")
471
+
472
+ _console.print(f"\n[bold cyan]Lint:[/bold cyan] {script_label}")
473
+ _console.print()
474
+
475
+ if not issues:
476
+ _console.print("[bold green]No issues found.[/bold green]")
477
+ _console.print()
478
+ return 0
479
+
480
+ # Sort: errors first, then warnings; within each group sort by line number.
481
+ _sev_order = {"error": 0, "warning": 1}
482
+ sorted_issues = sorted(issues, key=lambda i: (_sev_order.get(i[0], 9), i[2]))
483
+
484
+ # Compute the widest location string so columns align.
485
+ locs: list[str] = []
486
+ for _, source, line_no, _ in sorted_issues:
487
+ locs.append(f"{source}:{line_no}" if line_no else source)
488
+ loc_width = max(len(loc) for loc in locs) if locs else 0
489
+
490
+ for (severity, _source, _line_no, message), loc in zip(sorted_issues, locs):
491
+ pad = " " * (loc_width - len(loc))
492
+ if severity == "error":
493
+ _console.print(f" [bold red]ERROR [/bold red] [dim]{loc}[/dim]{pad} {message}")
494
+ else:
495
+ _console.print(f" [bold yellow]WARNING[/bold yellow] [dim]{loc}[/dim]{pad} {message}")
496
+
497
+ _console.print()
498
+ parts = []
499
+ if n_errors:
500
+ parts.append(f"[bold red]{n_errors} error{'s' if n_errors != 1 else ''}[/bold red]")
501
+ if n_warnings:
502
+ parts.append(f"[bold yellow]{n_warnings} warning{'s' if n_warnings != 1 else ''}[/bold yellow]")
503
+ _console.print(" " + ", ".join(parts))
504
+ _console.print()
505
+
506
+ return 1 if n_errors > 0 else 0