plsqlwks 0.1.4__tar.gz → 0.1.6__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 (97) hide show
  1. plsqlwks-0.1.6/MANIFEST.in +8 -0
  2. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/PKG-INFO +234 -11
  3. plsqlwks-0.1.6/PLUGINS.md +320 -0
  4. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/README.md +231 -10
  5. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/__init__.py +1 -1
  6. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/config/__init__.py +4 -0
  7. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/config/loader.py +18 -0
  8. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/config/models.py +13 -0
  9. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/config/settings.py +42 -1
  10. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/db/execution.py +3 -9
  11. plsqlwks-0.1.6/plsqlwks/exporting.py +147 -0
  12. plsqlwks-0.1.6/plsqlwks/html_exporting.py +142 -0
  13. plsqlwks-0.1.6/plsqlwks/plugins/__init__.py +28 -0
  14. plsqlwks-0.1.6/plsqlwks/plugins/_result_export.py +162 -0
  15. plsqlwks-0.1.6/plsqlwks/plugins/api.py +122 -0
  16. plsqlwks-0.1.6/plsqlwks/plugins/csv_export.py +128 -0
  17. plsqlwks-0.1.6/plsqlwks/plugins/html_export.py +128 -0
  18. plsqlwks-0.1.6/plsqlwks/plugins/loader.py +181 -0
  19. plsqlwks-0.1.6/plsqlwks/plugins/xlsx_export.py +151 -0
  20. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/app.py +48 -8
  21. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/app_input.py +35 -3
  22. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/app_results.py +8 -0
  23. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/constants.py +3 -3
  24. plsqlwks-0.1.6/plsqlwks/ui/plugin_host.py +183 -0
  25. plsqlwks-0.1.6/plsqlwks/xlsx_exporting.py +435 -0
  26. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks.egg-info/PKG-INFO +234 -11
  27. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks.egg-info/SOURCES.txt +29 -1
  28. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks.egg-info/requires.txt +3 -0
  29. plsqlwks-0.1.6/plugin-requirements/csv-export/requirements.txt +1 -0
  30. plsqlwks-0.1.6/plugin-requirements/html-export/requirements.txt +1 -0
  31. plsqlwks-0.1.6/plugin-requirements/xlsx-export/requirements.txt +2 -0
  32. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/pyproject.toml +3 -0
  33. plsqlwks-0.1.6/pytest.ini +8 -0
  34. plsqlwks-0.1.6/rchar.py +151 -0
  35. plsqlwks-0.1.6/requirements.txt +1 -0
  36. plsqlwks-0.1.6/tests/conftest.py +149 -0
  37. plsqlwks-0.1.6/tests/fixtures/config_exports.txt +56 -0
  38. plsqlwks-0.1.6/tests/fixtures/ui_exports.txt +459 -0
  39. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/tests/test_config_workspace.py +197 -1
  40. plsqlwks-0.1.6/tests/test_csv_export_plugin.py +396 -0
  41. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/tests/test_db_helpers.py +30 -0
  42. plsqlwks-0.1.6/tests/test_exporting.py +276 -0
  43. plsqlwks-0.1.6/tests/test_html_export_plugin.py +739 -0
  44. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/tests/test_packaging.py +150 -7
  45. plsqlwks-0.1.6/tests/test_plugins.py +311 -0
  46. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/tests/test_ui_flows.py +24 -0
  47. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/tests/test_ui_keys.py +178 -1
  48. plsqlwks-0.1.6/tests/test_ui_plugins.py +907 -0
  49. plsqlwks-0.1.6/tests/test_xlsx_export_plugin.py +1139 -0
  50. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/license.txt +0 -0
  51. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/__main__.py +0 -0
  52. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/config/paths.py +0 -0
  53. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/config/session.py +0 -0
  54. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/db/__init__.py +0 -0
  55. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/db/editing.py +0 -0
  56. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/db/explain.py +0 -0
  57. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/db/health.py +0 -0
  58. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/db/metadata.py +0 -0
  59. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/db/models.py +0 -0
  60. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/db/session.py +0 -0
  61. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/db/sql_analysis.py +0 -0
  62. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/db/transactions.py +0 -0
  63. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/sqlbinds.py +0 -0
  64. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/sqlsplit.py +0 -0
  65. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/__init__.py +0 -0
  66. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/app_db.py +0 -0
  67. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/app_editor.py +0 -0
  68. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/app_files.py +0 -0
  69. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/app_render.py +0 -0
  70. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/app_tabs_browser.py +0 -0
  71. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/browser.py +0 -0
  72. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/buffer.py +0 -0
  73. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/clipboard.py +0 -0
  74. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/commands.py +0 -0
  75. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/completion.py +0 -0
  76. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/db_worker.py +0 -0
  77. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/display.py +0 -0
  78. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/errors.py +0 -0
  79. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/help.py +0 -0
  80. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/keys.py +0 -0
  81. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/menu.py +0 -0
  82. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/results.py +0 -0
  83. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/sql.py +0 -0
  84. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/state.py +0 -0
  85. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/ui/syntax.py +0 -0
  86. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks/workspace.py +0 -0
  87. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks.egg-info/dependency_links.txt +0 -0
  88. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks.egg-info/entry_points.txt +0 -0
  89. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/plsqlwks.egg-info/top_level.txt +0 -0
  90. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/setup.cfg +0 -0
  91. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/tests/test_db_editing.py +0 -0
  92. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/tests/test_db_worker.py +0 -0
  93. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/tests/test_oracle_integration.py +0 -0
  94. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/tests/test_pty_keys.py +0 -0
  95. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/tests/test_rchar.py +0 -0
  96. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/tests/test_sqlbinds.py +0 -0
  97. {plsqlwks-0.1.4 → plsqlwks-0.1.6}/tests/test_sqlsplit.py +0 -0
@@ -0,0 +1,8 @@
1
+ include pytest.ini
2
+ include PLUGINS.md
3
+ include rchar.py
4
+ include requirements.txt
5
+ recursive-include plugin-requirements requirements.txt
6
+ include tests/conftest.py
7
+ include tests/fixtures/config_exports.txt
8
+ include tests/fixtures/ui_exports.txt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plsqlwks
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: An ncurses SQL and PL/SQL workspace for Oracle databases.
5
5
  License-Expression: LicenseRef-plsqlwks-Donationware
6
6
  Project-URL: Repository, https://gitlab.com/unununu/plsqlwks
@@ -13,6 +13,8 @@ Requires-Python: >=3.10
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: license.txt
15
15
  Requires-Dist: oracledb>=2.0
16
+ Provides-Extra: xlsx
17
+ Requires-Dist: openpyxl>=3.1; extra == "xlsx"
16
18
  Provides-Extra: dev
17
19
  Requires-Dist: build>=1.2; extra == "dev"
18
20
  Requires-Dist: mypy>=1.10; extra == "dev"
@@ -74,9 +76,6 @@ python3 -m pip install -e '.[dev]'
74
76
 
75
77
  `oracledb` is the only runtime dependency. Supporting functionality uses the Python standard library.
76
78
 
77
- See the [release checklist](https://gitlab.com/unununu/plsqlwks/-/blob/main/RELEASING.md)
78
- for the quality, packaging, and GitLab release process.
79
-
80
79
  For a regular local install, use:
81
80
 
82
81
  ```bash
@@ -90,11 +89,32 @@ python3 -m pytest -q
90
89
  python3 -m ruff check .
91
90
  ```
92
91
 
92
+ Plugin API and built-in plugin tests are optional and are not part of the
93
+ default core test run. Run them with either opt-in form:
94
+
95
+ ```bash
96
+ PLSQLWKS_TEST_PLUGINS=1 python3 -m pytest -q
97
+ python3 -m pytest -q -m plugin
98
+ ```
99
+
100
+ The CSV and HTML export plugins have no additional dependencies. XLSX export
101
+ uses the optional `openpyxl` package, kept out of the base PLSQLWKS runtime.
102
+ Install a distribution with XLSX support through the standard extra:
103
+
104
+ ```bash
105
+ python3 -m pip install 'plsqlwks[xlsx]'
106
+ ```
107
+
108
+ For an editable development checkout, install the development and XLSX extras
109
+ together with `python3 -m pip install -e '.[dev,xlsx]'`. The repository also
110
+ retains `plugin-requirements/xlsx-export/requirements.txt` as a compatible
111
+ fallback for existing checkout automation.
112
+
93
113
  Oracle integration tests require the connection environment variables shown
94
114
  above and an accessible password file. Enable every test group with:
95
115
 
96
116
  ```bash
97
- PLSQLWKS_TEST_ORACLE=1 PLSQLWKS_TEST_PTY=1 PLSQLWKS_TEST_SLOW=1 \
117
+ PLSQLWKS_TEST_ORACLE=1 PLSQLWKS_TEST_PLUGINS=1 PLSQLWKS_TEST_PTY=1 PLSQLWKS_TEST_SLOW=1 \
98
118
  python3 -m pytest -q -rs
99
119
  ```
100
120
 
@@ -172,9 +192,9 @@ After a successful quit, plsqlwks records the open file-backed tabs, active tab,
172
192
  | `F8` | Toggle result grid / row-detail output |
173
193
  | `F9` | Show/focus/hide schema browser |
174
194
  | `F12` | Choose autocommit or manual transaction mode |
175
- | `Ctrl-Up` / `Ctrl-Down` | Scroll the focused pane or visible DBMS_OUTPUT |
195
+ | `Ctrl-Up` / `Ctrl-Down` | Scroll the focused pane or visible DBMS_OUTPUT by one line |
176
196
  | `Ctrl-W` | Close current file tab |
177
- | `Ctrl-PageUp` / `Ctrl-PageDown` | Switch file tabs |
197
+ | `Ctrl-PageUp` / `Ctrl-PageDown` | Scroll focused results by one page; otherwise switch file tabs |
178
198
  | `Alt-1`..`Alt-9` | Jump to visible file tab |
179
199
  | `Ctrl-Q` | Quit |
180
200
  | `Ctrl-C` while running | Interrupt the active database operation |
@@ -200,7 +220,7 @@ After a successful quit, plsqlwks records the open file-backed tabs, active tab,
200
220
  | `Delete` | Delete at the cursor |
201
221
  | `Ctrl-Backspace` / `Ctrl-Delete` | Delete the previous/next word |
202
222
  | `Enter` | Insert a new line |
203
- | `Tab` | Focus the result table |
223
+ | `Tab` | Focus the results or DBMS_OUTPUT pane |
204
224
  | `Shift-Tab` | Autocomplete keywords, schema objects, and columns |
205
225
  | `F2` / `Ctrl-S` | Save buffer |
206
226
  | `F3` / `Ctrl-O` | Open file |
@@ -302,6 +322,152 @@ bytes and include a truncation marker with the full size. Truncated LOB cells
302
322
  cannot be edited safely. Schema-browser DDL is always read in full.
303
323
  `F7` cycles a grid fullscreen view that starts with the table header on the first terminal line and uses the last line for data, an editor-only fullscreen view, and a split layout with 2/3 editor and 1/3 data grid.
304
324
 
325
+ To export the active table, use one of these command paths:
326
+
327
+ - `Alt-O -> Results -> Export loaded rows to CSV`
328
+ - `Alt-O -> Results -> Export loaded rows to HTML`
329
+ - `Alt-O -> Results -> Export loaded rows to XLSX`
330
+
331
+ All three built-in Plugin API commands write the columns and exactly the rows
332
+ currently loaded in the grid; they never fetch additional result pages.
333
+ Relative names and the timestamped default name are resolved under the active
334
+ workspace's `results/` directory; absolute paths are also accepted, and an
335
+ existing file requires confirmation before replacement. Commit or cancel an
336
+ active insert draft before exporting so its temporary row cannot be included.
337
+ Each enabled export remains available in read-only mode because it does not
338
+ execute SQL or change a transaction.
339
+
340
+ The bundled exporters can be enabled independently in the active `config.ini`:
341
+
342
+ ```ini
343
+ [plugin.csv-export]
344
+ enabled = yes
345
+
346
+ [plugin.html-export]
347
+ enabled = yes
348
+
349
+ [plugin.xlsx-export]
350
+ enabled = yes
351
+ ```
352
+
353
+ Set an exporter's `enabled` value to `no` to omit its command from the
354
+ **Results** menu. A missing or malformed value defaults to enabled so existing
355
+ configurations keep all three commands. These switches affect only the bundled
356
+ exporters, not installed entry-point plugins, and take effect after restarting
357
+ PLSQLWKS.
358
+
359
+ The HTML command writes a standalone UTF-8 HTML5 document with column headings
360
+ and table rows, followed by the loaded-row count and any additional-row notice.
361
+ The result title is used only as browser-tab document metadata, not as a visible
362
+ heading. Document titles, column headers, and cell values are escaped as
363
+ untrusted text. The document contains a static embedded stylesheet, no
364
+ JavaScript, and no external resources. It reports when more rows are available
365
+ in PLSQLWKS but does not fetch them, and it does not open a browser after export.
366
+
367
+ The XLSX command writes one `Query result` worksheet with a header row and the
368
+ currently loaded display rows. The header row is frozen by default so it stays
369
+ visible while scrolling. Genuine source numbers become native Excel
370
+ numeric cells when they fit Excel's range and 15-significant-digit precision;
371
+ fixed-point scale such as `10.50` is preserved. Numeric-looking character data
372
+ and unsafe-precision numbers remain exact text and can still receive Excel's
373
+ number-as-text warning. Headers and other values, including text beginning with
374
+ `=`, `+`, `-`, or `@`, remain literal strings and are never interpreted as
375
+ spreadsheet formulas. The workbook contains no macros, external links, or
376
+ fetched continuation rows and is not opened after export. XLSX support is
377
+ optional and obtains `openpyxl>=3.1` from the standard `plsqlwks[xlsx]` extra;
378
+ the repository plugin requirements file remains a source-checkout fallback.
379
+ Each column uses the larger of its bold column name or widest data value,
380
+ estimated with Calibri 11-compatible proportional glyph widths, clamped from 3
381
+ through 60 units, and given a 17-pixel fit margin. Wrapping remains based on
382
+ logical visual length: values over 60 visual units or containing explicit line
383
+ breaks are wrapped.
384
+
385
+ The HTML plugin accepts three plugin-owned environment settings. They are
386
+ captured when PLSQLWKS loads the plugin:
387
+
388
+ ```bash
389
+ PLSQLWKS_HTML_EXPORT_NULL_VALUE="(null)"
390
+ PLSQLWKS_HTML_EXPORT_THEME="dark"
391
+ PLSQLWKS_HTML_EXPORT_DATE_FORMAT="%d.%m.%Y"
392
+ ```
393
+
394
+ `PLSQLWKS_HTML_EXPORT_NULL_VALUE` replaces the exact `<NULL>` grid display
395
+ token and defaults to empty; set it to `<NULL>` or another marker to keep NULL
396
+ values visible. `PLSQLWKS_HTML_EXPORT_THEME` is `bright` (the default) or
397
+ `dark`; both select only bundled static CSS, and printing uses a readable
398
+ bright palette. `PLSQLWKS_HTML_EXPORT_DATE_FORMAT` is empty by default and
399
+ otherwise uses Python `strftime` syntax with the same conservative ISO-display
400
+ matching described for CSV below. These settings affect only generated HTML
401
+ and do not expand Plugin API v1.
402
+
403
+ The XLSX plugin has equivalent plugin-owned environment settings, captured when
404
+ PLSQLWKS loads it:
405
+
406
+ ```bash
407
+ PLSQLWKS_XLSX_EXPORT_NULL_VALUE="(null)"
408
+ PLSQLWKS_XLSX_EXPORT_THEME="dark"
409
+ PLSQLWKS_XLSX_EXPORT_DATE_FORMAT="%d.%m.%Y"
410
+ PLSQLWKS_XLSX_EXPORT_AUTO_FILTER="no"
411
+ PLSQLWKS_XLSX_EXPORT_AUTO_WIDTH="no"
412
+ PLSQLWKS_XLSX_EXPORT_FREEZE_TOP_ROW="no"
413
+ ```
414
+
415
+ `PLSQLWKS_XLSX_EXPORT_NULL_VALUE` replaces the exact `<NULL>` display token and
416
+ defaults to empty; set it to `<NULL>` or another marker to keep NULL values
417
+ visible. `PLSQLWKS_XLSX_EXPORT_THEME` selects the bundled `bright` (default) or
418
+ `dark` cell styles. `PLSQLWKS_XLSX_EXPORT_DATE_FORMAT` is empty by default and
419
+ otherwise applies Python `strftime` directives to the same strict ISO-shaped
420
+ display strings as CSV and HTML. Formatted date and text values remain literal
421
+ spreadsheet strings rather than formulas or typed Excel dates. The
422
+ `PLSQLWKS_XLSX_EXPORT_AUTO_FILTER` setting enables Excel's column filter
423
+ controls by default. It accepts case-insensitive, whitespace-tolerant `1`,
424
+ `yes`, `true`, or `on` to enable them and `0`, `no`, `false`, or `off` to
425
+ disable them; an unset or malformed value falls back to enabled. When enabled,
426
+ the filter range spans exactly the header and currently loaded rows, without
427
+ applying filter criteria or initially hiding any rows.
428
+ `PLSQLWKS_XLSX_EXPORT_AUTO_WIDTH` controls proportional sizing from the widest
429
+ header or loaded data value and uses the same boolean syntax. When filtering is
430
+ enabled, the header candidate includes three extra character units for the filter
431
+ dropdown. Auto-width defaults to enabled; disabling it leaves Excel's default
432
+ column widths while preserving the existing multiline and over-60-unit cell
433
+ wrapping. `PLSQLWKS_XLSX_EXPORT_FREEZE_TOP_ROW` uses the same boolean syntax,
434
+ defaults to enabled, and keeps row 1 visible while scrolling; disabling it
435
+ leaves the worksheet unfrozen. Freezing is independent of filtering and
436
+ automatic widths. These settings belong to the built-in XLSX plugin and do not
437
+ expand Plugin API v1.
438
+
439
+ The built-in CSV export formatting can also be customized in the active
440
+ `config.ini`:
441
+
442
+ ```ini
443
+ [plugin.csv-export]
444
+ separator = ,
445
+ null_value =
446
+ date_format =
447
+ protect_formulas = no
448
+ ```
449
+
450
+ `separator` must be one character and defaults to a comma. `null_value`
451
+ defaults to empty, replacing the exact `<NULL>` display value with an empty CSV
452
+ field. Set it to `<NULL>` or another marker to retain a visible value.
453
+ `date_format` defaults to empty, which preserves displayed date values. When
454
+ set, it uses Python `strftime` syntax and formats only calendar-valid,
455
+ full-string ISO display values shaped as `YYYY-MM-DD` or
456
+ `YYYY-MM-DD HH:MM:SS[.digits][+/-HH:MM]`, with one to six fractional digits;
457
+ other text is exported unchanged. This deliberately strict heuristic cannot
458
+ identify non-ISO or otherwise preformatted database date text, while matching
459
+ text-column values are indistinguishable from dates and are formatted too.
460
+
461
+ `protect_formulas` defaults to `no`, preserving the exact field content and
462
+ existing CSV representation. Set it to `yes` for CSV files intended to be
463
+ opened by people in spreadsheet software. The protected mode quotes every
464
+ field and prefixes formula-triggering values with a tab, including risky ASCII
465
+ and full-width prefixes and leading control characters. This also makes
466
+ legitimate leading signed values such as `-42` text and leaves the tab in data
467
+ seen by programmatic CSV readers. OWASP notes that no CSV neutralization is
468
+ universal across all spreadsheet applications and save/re-open workflows, so
469
+ validate this opt-in mode with the applications in use.
470
+
305
471
  ### Schema Browser
306
472
 
307
473
  The `F9` schema browser groups tables, views, procedures, functions, packages, triggers, sequences, indexes, and private synonyms from the current schema. Type while the browser is focused to filter object names with a case-insensitive substring match; groups without matches are hidden and matching groups expand automatically without changing their saved expansion state.
@@ -347,8 +513,65 @@ currently be loaded from it.
347
513
  workspace/
348
514
  sql/ saved SQL files
349
515
  plsql/ saved PL/SQL files
350
- results/ reserved output directory
516
+ results/ CSV, HTML, XLSX, and other result output
517
+ ```
518
+
519
+ The first launch creates the workspace folders and starter SQL/PLSQL files.
520
+
521
+ ## Plugin API
522
+
523
+ Plugin API version 1 is a deliberately small, command-only extension point.
524
+ See [PLUGINS.md](PLUGINS.md) for the focused plugin author and testing guide.
525
+ PLSQLWKS uses that same API for its built-in loaded-row CSV, HTML, and XLSX
526
+ exporters; adding formats requires no direct App integration or public API
527
+ expansion.
528
+ Installed Python packages can add commands to the `Alt-O` menu through the
529
+ standard `plsqlwks.plugins` entry-point group:
530
+
531
+ ```toml
532
+ [project.entry-points."plsqlwks.plugins"]
533
+ example = "example_package.plugin:create_plugin"
534
+ ```
535
+
536
+ A minimal plugin factory is:
537
+
538
+ ```python
539
+ from plsqlwks.plugins import Plugin, PluginCommand, PluginContext
540
+
541
+
542
+ def show_loaded_count(context: PluginContext) -> None:
543
+ result = context.get_active_result()
544
+ count = len(result.rows) if result is not None else 0
545
+ context.set_status(f"{count} row(s) are currently loaded")
546
+
547
+
548
+ def create_plugin() -> Plugin:
549
+ return Plugin(
550
+ id="example",
551
+ name="Example commands",
552
+ commands=(
553
+ PluginCommand(
554
+ id="show-loaded-count",
555
+ section="Results",
556
+ title="Show loaded row count",
557
+ handler=show_loaded_count,
558
+ ),
559
+ ),
560
+ )
351
561
  ```
352
562
 
353
- The first launch creates the workspace folders and starter SQL/PLSQL files. The
354
- terminal UI does not currently provide a query-export command.
563
+ The entry point must resolve to a zero-argument callable returning `Plugin`.
564
+ Handlers receive only `PluginContext`: it provides an immutable snapshot of the
565
+ active tabular result, the results directory, insert-draft detection, text and
566
+ overwrite prompts, status updates, and UI error reporting. API v1 does not
567
+ provide database execution, mutable results, the application or UI state,
568
+ keyboard registration, drawing, events, lifecycle hooks, background jobs,
569
+ settings schemas, hot reload, or workspace-local executable plugins.
570
+
571
+ The `[plugin.csv-export]` section and the documented HTML/XLSX environment
572
+ variables configure only their corresponding PLSQLWKS-supplied plugins. Plugin
573
+ API v1 does not provide a generic settings schema or pass these values to
574
+ installed third-party plugins.
575
+
576
+ Installed plugins are trusted, in-process Python code. They are not sandboxed;
577
+ install plugins only from sources you trust.
@@ -0,0 +1,320 @@
1
+ # PLSQLWKS plugins
2
+
3
+ PLSQLWKS Plugin API version 1 is a small, command-only extension point. A plugin
4
+ can add commands to the Alt-O menu and use the limited `PluginContext` passed to
5
+ its command handler. Installed plugins are trusted, in-process Python code. They
6
+ are **not sandboxed** and should be installed only from sources you trust.
7
+
8
+ ## API version 1
9
+
10
+ Import supported types and constants from `plsqlwks.plugins`:
11
+
12
+ - `Plugin` describes a plugin and its commands.
13
+ - `PluginCommand` describes one searchable Alt-O menu command.
14
+ - `PluginContext` is the host interface supplied to a command handler.
15
+ - `ResultSnapshot` is an immutable, tuple-based copy of the active table result.
16
+ - `PluginHandler` and `PluginFactory` are the corresponding callable types.
17
+ - `PLUGIN_API_VERSION` and `PLUGIN_ENTRY_POINT_GROUP` identify the contract.
18
+
19
+ `PluginContext` can read the configured results directory, inspect the active
20
+ result snapshot, detect an active result-grid insert draft, open a text prompt,
21
+ request overwrite confirmation, set status text, and report an error through
22
+ the UI. A snapshot contains exactly the display rows loaded when the command
23
+ starts. Its optional `numeric_values` matrix carries only aligned source
24
+ `Decimal`, `int`, and `float` values; other cells are `None`, and legacy
25
+ snapshots may omit the matrix. No raw rows or Oracle handles cross the plugin
26
+ boundary. `has_more` reports that additional rows exist without exposing a
27
+ continuation or a way to fetch them.
28
+
29
+ API v1 does not provide database execution, mutable query results, application
30
+ or UI state, curses drawing, arbitrary shortcuts, event or lifecycle hooks,
31
+ background jobs, generic third-party plugin settings, hot reload, or
32
+ workspace-local Python-file loading.
33
+
34
+ ## Registering an installed plugin
35
+
36
+ Expose a zero-argument factory through the `plsqlwks.plugins` entry-point group:
37
+
38
+ ```toml
39
+ [project.entry-points."plsqlwks.plugins"]
40
+ example = "example_package.plugin:create_plugin"
41
+ ```
42
+
43
+ A minimal `example_package/plugin.py` is:
44
+
45
+ ```python
46
+ from plsqlwks.plugins import Plugin, PluginCommand, PluginContext
47
+
48
+
49
+ def show_results_directory(context: PluginContext) -> None:
50
+ context.set_status(f"Results directory: {context.results_dir}")
51
+
52
+
53
+ def create_plugin() -> Plugin:
54
+ return Plugin(
55
+ id="example",
56
+ name="Example commands",
57
+ commands=(
58
+ PluginCommand(
59
+ id="show-results-directory",
60
+ section="Workspace",
61
+ title="Show results directory",
62
+ handler=show_results_directory,
63
+ keywords="example results directory",
64
+ ),
65
+ ),
66
+ )
67
+ ```
68
+
69
+ Plugin IDs must match the safe pattern `^[a-z][a-z0-9_.-]*$` and are globally
70
+ unique. Command IDs must be nonempty and unique within a plugin. The API version
71
+ must match the host version. Built-in plugins load first, followed by installed
72
+ entry points in deterministic name order. A broken or incompatible installed
73
+ plugin is skipped and reported as a startup warning rather than preventing
74
+ PLSQLWKS from starting. An ordinary exception raised by a command is reported
75
+ through the UI instead of terminating the application.
76
+
77
+ ## Built-in result exports
78
+
79
+ The bundled CSV, HTML, and XLSX commands are normal built-in `Plugin` objects
80
+ using the same API v1 context available to installed plugins. When enabled,
81
+ they are registered in that order in the **Results** section and have no global
82
+ keyboard shortcuts. None is an App method, and adding export formats requires
83
+ no public API expansion.
84
+
85
+ ### Exporter availability
86
+
87
+ Each bundled exporter has an independent switch in the active `config.ini`:
88
+
89
+ ```ini
90
+ [plugin.csv-export]
91
+ enabled = yes
92
+
93
+ [plugin.html-export]
94
+ enabled = yes
95
+
96
+ [plugin.xlsx-export]
97
+ enabled = yes
98
+ ```
99
+
100
+ Set `enabled = no` to omit that exporter's command from the **Results** menu.
101
+ A missing or malformed value defaults to enabled. These switches control only
102
+ the bundled exporters; installed entry-point plugins are still discovered and
103
+ loaded normally. Restart PLSQLWKS after changing an availability switch.
104
+
105
+ ### CSV export
106
+
107
+ Choose **Alt-O -> Results -> Export loaded rows to CSV** to export the active
108
+ table snapshot. The command proposes a timestamped name in the workspace
109
+ `results/` directory, accepts relative or absolute paths, and asks before
110
+ replacing an existing file. It writes UTF-8 CSV with a header and exactly the
111
+ currently loaded display rows; it never fetches a continuation page. Export is
112
+ available in read-only mode. An active insert draft must first be committed or
113
+ cancelled so its temporary row cannot be exported.
114
+
115
+ The shared CSV writer writes a temporary sibling file and atomically replaces
116
+ the destination after a successful close. A failed write therefore does not
117
+ turn an existing destination into an apparently successful partial export.
118
+
119
+ ### HTML export
120
+
121
+ Choose **Alt-O -> Results -> Export loaded rows to HTML** to write a complete,
122
+ standalone UTF-8 HTML5 document for the active table snapshot. The command
123
+ proposes a timestamped `.html` name in the workspace `results/` directory,
124
+ accepts relative or absolute paths, and asks before replacing an existing file.
125
+ It writes the headers and exactly the currently loaded display rows, followed
126
+ by the loaded-row count and a notice when additional rows remain available; the
127
+ plugin never fetches them. The result title is retained only as browser-tab
128
+ document metadata and is not repeated as a visible heading.
129
+
130
+ The result-derived document title, column names, and cell values are escaped as
131
+ untrusted text. The document has a small static embedded stylesheet and no
132
+ JavaScript, event handlers, forms, frames, images, remote URLs, or external
133
+ resources. Cell whitespace remains readable, and wide tables scroll
134
+ horizontally. The command does not launch a browser.
135
+
136
+ Like CSV export, HTML export is available in read-only mode and rejects an
137
+ active insert draft before reading the result or opening a filename prompt. It
138
+ uses the existing context methods for draft detection, immutable snapshot
139
+ access, the results directory, text and overwrite prompts, status updates, and
140
+ error reporting. Atomic replacement keeps a previous destination intact when
141
+ rendering, writing, or replacement fails.
142
+
143
+ ### HTML export configuration
144
+
145
+ The built-in HTML plugin captures these optional environment variables when
146
+ PLSQLWKS loads it:
147
+
148
+ ```bash
149
+ PLSQLWKS_HTML_EXPORT_NULL_VALUE="(null)"
150
+ PLSQLWKS_HTML_EXPORT_THEME="dark"
151
+ PLSQLWKS_HTML_EXPORT_DATE_FORMAT="%d.%m.%Y"
152
+ ```
153
+
154
+ - `PLSQLWKS_HTML_EXPORT_NULL_VALUE` replaces values exactly equal to the grid's
155
+ `<NULL>` display token. It defaults to empty and may be set to `<NULL>` or
156
+ another marker.
157
+ - `PLSQLWKS_HTML_EXPORT_THEME` selects the bundled static `bright` or `dark`
158
+ stylesheet. It defaults to `bright`; print rules remain bright and readable.
159
+ - `PLSQLWKS_HTML_EXPORT_DATE_FORMAT` accepts Python `strftime` directives. It
160
+ defaults to empty, preserving displayed values.
161
+
162
+ The HTML date option uses the same strict ISO-display matching documented for
163
+ CSV: snapshots have display strings, not Oracle type metadata, so invalid or
164
+ nonmatching text is preserved and a text value having the recognized shape is
165
+ indistinguishable from a date. These are settings of the bundled plugin, not a
166
+ generic Plugin API v1 configuration facility. Code embedding the built-in
167
+ factory may instead pass an immutable `HtmlExportOptions` instance explicitly;
168
+ installed third-party entry-point factories remain zero-argument callables.
169
+
170
+ ### XLSX export
171
+
172
+ Choose **Alt-O -> Results -> Export loaded rows to XLSX** to write one `Query
173
+ result` worksheet containing a header and exactly the loaded result snapshot.
174
+ The command proposes a timestamped `.xlsx` name in the workspace `results/`
175
+ directory, accepts relative or absolute paths, and asks before replacing an
176
+ existing file. It does not fetch continuation rows, access the database, start
177
+ a subprocess, or open the resulting workbook. It remains available in
178
+ read-only mode and rejects an active insert draft before reading the snapshot
179
+ or prompting.
180
+
181
+ Headers and text cells are explicitly stored as strings. Genuine source
182
+ numbers become native Excel numeric cells only when they are finite, within
183
+ Excel's supported range, and contain at most 15 significant digits. Fixed-point
184
+ formats preserve visible scale such as `10.50`; excess-precision values and
185
+ numeric-looking character data remain exact text and may retain Excel's
186
+ number-as-text warning. Values beginning with `=`, `+`, `-`, or `@` remain
187
+ literal result text and are never interpreted as spreadsheet formulas. The
188
+ workbook contains no macros or external links. Its snapshot title is stored
189
+ only as document metadata; result data remains confined to the single
190
+ worksheet. Each column uses the larger of its bold column name or widest data
191
+ value, estimated with Calibri 11-compatible proportional glyph widths and
192
+ clamped from 3 through 60 units. A 17-pixel fit margin prevents spreadsheet
193
+ font rendering from clipping the last characters. Wrapping remains based on
194
+ logical visual length: values over 60 visual units or containing explicit line
195
+ breaks are wrapped.
196
+
197
+ XLSX generation uses the optional `openpyxl>=3.1` dependency. It is not a base
198
+ PLSQLWKS runtime dependency. Install a distribution with XLSX support through
199
+ the standard extra:
200
+
201
+ ```bash
202
+ python3 -m pip install 'plsqlwks[xlsx]'
203
+ ```
204
+
205
+ For an editable checkout, use `python3 -m pip install -e '.[xlsx]'`. The
206
+ repository keeps `plugin-requirements/xlsx-export/requirements.txt` as a
207
+ compatible fallback for existing source-checkout automation.
208
+
209
+ If the dependency is unavailable, invoking the command reports a concise XLSX
210
+ export error through the normal Plugin API UI boundary instead of preventing
211
+ PLSQLWKS from starting.
212
+
213
+ ### XLSX export configuration
214
+
215
+ The built-in XLSX plugin captures these optional environment variables when
216
+ PLSQLWKS loads it:
217
+
218
+ ```bash
219
+ PLSQLWKS_XLSX_EXPORT_NULL_VALUE="(null)"
220
+ PLSQLWKS_XLSX_EXPORT_THEME="dark"
221
+ PLSQLWKS_XLSX_EXPORT_DATE_FORMAT="%d.%m.%Y"
222
+ PLSQLWKS_XLSX_EXPORT_AUTO_FILTER="no"
223
+ PLSQLWKS_XLSX_EXPORT_AUTO_WIDTH="no"
224
+ PLSQLWKS_XLSX_EXPORT_FREEZE_TOP_ROW="no"
225
+ ```
226
+
227
+ - `PLSQLWKS_XLSX_EXPORT_NULL_VALUE` replaces values exactly equal to the grid's
228
+ `<NULL>` display token. It defaults to empty and may be set to `<NULL>` or
229
+ another marker.
230
+ - `PLSQLWKS_XLSX_EXPORT_THEME` selects the bundled `bright` or `dark` cell
231
+ styles and defaults to `bright`.
232
+ - `PLSQLWKS_XLSX_EXPORT_DATE_FORMAT` accepts Python `strftime` directives and
233
+ defaults to empty, preserving displayed values.
234
+ - `PLSQLWKS_XLSX_EXPORT_AUTO_FILTER` controls Excel's column filter controls.
235
+ It defaults to enabled and accepts case-insensitive, whitespace-tolerant
236
+ `1`, `yes`, `true`, or `on` to enable them and `0`, `no`, `false`, or `off`
237
+ to disable them. An unset or malformed value falls back to enabled.
238
+ - `PLSQLWKS_XLSX_EXPORT_AUTO_WIDTH` controls proportional column sizing from
239
+ the widest header or loaded data value. With filtering enabled, the header
240
+ candidate includes three extra character units for the filter dropdown. It
241
+ accepts the same boolean values, defaults to enabled, and leaves Excel's
242
+ default column widths when disabled.
243
+ - `PLSQLWKS_XLSX_EXPORT_FREEZE_TOP_ROW` keeps the first-row header visible
244
+ while scrolling. It accepts the same boolean values, defaults to enabled,
245
+ and leaves the worksheet unfrozen when disabled.
246
+
247
+ When enabled, the auto-filter covers exactly the header row and currently
248
+ loaded rows. It applies no filter criteria, so no data rows are initially
249
+ hidden. Disabling automatic widths does not change cell wrapping: multiline
250
+ values and values above the 60-character-unit wrapping threshold still wrap.
251
+ Freezing is independent of the auto-filter and automatic-width settings.
252
+
253
+ NULL and date transformations follow the same display-string limitations as
254
+ CSV and HTML and remain literal spreadsheet strings. Numeric provenance is
255
+ independent of these settings. These variables configure only the bundled XLSX
256
+ plugin and are not a generic Plugin API v1 settings mechanism. Code embedding
257
+ its factory may pass an immutable `XlsxExportOptions` value; installed
258
+ entry-point factories remain zero-argument callables.
259
+
260
+ ### CSV export configuration
261
+
262
+ The built-in CSV plugin reads these host-owned formatting settings from the
263
+ active `config.ini`:
264
+
265
+ ```ini
266
+ [plugin.csv-export]
267
+ separator = ,
268
+ null_value =
269
+ date_format =
270
+ protect_formulas = no
271
+ ```
272
+
273
+ - `separator` is one character and defaults to `,`.
274
+ - `null_value` is written for values that exactly equal the result grid's
275
+ `<NULL>` display token. It defaults to empty and may be set to `<NULL>` or
276
+ another marker.
277
+ - `date_format` uses Python `strftime` directives. It defaults to empty, which
278
+ preserves the displayed value.
279
+ - `protect_formulas` accepts normal INI boolean values and defaults to `no`.
280
+ When enabled, the plugin protects both headers and formatted data fields
281
+ that begin with spreadsheet formula-triggering ASCII, control, or full-width
282
+ characters. It prefixes those values with a tab and quotes every field so
283
+ the tab remains inside its CSV cell. Missing or malformed values fall back
284
+ to `no`.
285
+
286
+ Date formatting is deliberately conservative because Plugin API snapshots
287
+ contain display strings rather than Oracle type metadata. It is applied only
288
+ to valid, full-string ISO display values shaped as `YYYY-MM-DD` or
289
+ `YYYY-MM-DD HH:MM:SS[.digits][+/-HH:MM]`, with one to six fractional digits.
290
+ Invalid dates, ISO values using other shapes, and all nonmatching text remain
291
+ unchanged. A character value with the same valid shape is indistinguishable
292
+ from a date and is formatted too. The directives supported by `strftime` can
293
+ vary slightly by platform.
294
+
295
+ Formula protection is intended for human spreadsheet viewing. It deliberately
296
+ turns leading signed values such as `-42` into text and exposes the added tab
297
+ to programmatic CSV readers. There is no universal neutralization across all
298
+ spreadsheet applications or save/re-open workflows, so use this opt-in mode
299
+ for the target spreadsheet workflow rather than as a lossless round trip.
300
+
301
+ This section configures only the PLSQLWKS-supplied plugin. Plugin API v1 has no
302
+ generic configuration schema and does not expose these settings to installed
303
+ third-party plugins.
304
+
305
+ ## Optional plugin tests
306
+
307
+ Plugin-specific tests are excluded from the default core test run. Run them
308
+ explicitly with either:
309
+
310
+ ```bash
311
+ python3 -m pytest -q -m plugin
312
+ PLSQLWKS_TEST_PLUGINS=1 python3 -m pytest -q
313
+ ```
314
+
315
+ Compatibility manifests for repository plugins remain under
316
+ `plugin-requirements/<plugin-id>/requirements.txt`. The built-in `csv-export`
317
+ and `html-export` plugins have no extra dependencies beyond PLSQLWKS and the
318
+ Python standard library. The built-in `xlsx-export` plugin uses the standard
319
+ `xlsx` extra for `openpyxl>=3.1`; its manifest is retained as a synchronized
320
+ source-checkout fallback.