execsql2 2.1.2__py3-none-any.whl → 2.4.0__py3-none-any.whl
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.
- execsql/cli/__init__.py +436 -0
- execsql/cli/dsn.py +86 -0
- execsql/cli/help.py +140 -0
- execsql/{cli.py → cli/run.py} +14 -589
- execsql/config.py +65 -1
- execsql/db/access.py +27 -15
- execsql/db/base.py +328 -215
- execsql/db/dsn.py +10 -5
- execsql/db/duckdb.py +6 -2
- execsql/db/factory.py +21 -0
- execsql/db/firebird.py +27 -19
- execsql/db/mysql.py +12 -7
- execsql/db/oracle.py +15 -11
- execsql/db/postgres.py +31 -16
- execsql/db/sqlite.py +15 -11
- execsql/db/sqlserver.py +16 -5
- execsql/exceptions.py +25 -7
- execsql/exporters/base.py +12 -1
- execsql/exporters/delimited.py +80 -35
- execsql/exporters/duckdb.py +6 -2
- execsql/exporters/feather.py +10 -6
- execsql/exporters/html.py +89 -69
- execsql/exporters/json.py +52 -45
- execsql/exporters/latex.py +37 -27
- execsql/exporters/ods.py +32 -11
- execsql/exporters/parquet.py +5 -2
- execsql/exporters/pretty.py +16 -9
- execsql/exporters/raw.py +22 -16
- execsql/exporters/sqlite.py +6 -2
- execsql/exporters/templates.py +39 -21
- execsql/exporters/values.py +26 -20
- execsql/exporters/xls.py +30 -11
- execsql/exporters/xml.py +31 -13
- execsql/exporters/zip.py +15 -0
- execsql/importers/base.py +6 -4
- execsql/importers/csv.py +8 -6
- execsql/importers/feather.py +6 -4
- execsql/importers/ods.py +6 -4
- execsql/importers/xls.py +6 -4
- execsql/metacommands/__init__.py +208 -1548
- execsql/metacommands/conditions.py +101 -27
- execsql/metacommands/control.py +8 -4
- execsql/metacommands/data.py +6 -6
- execsql/metacommands/debug.py +6 -2
- execsql/metacommands/dispatch.py +2011 -0
- execsql/metacommands/io.py +67 -1310
- execsql/metacommands/io_export.py +442 -0
- execsql/metacommands/io_fileops.py +287 -0
- execsql/metacommands/io_import.py +398 -0
- execsql/metacommands/io_write.py +248 -0
- execsql/metacommands/prompt.py +22 -66
- execsql/metacommands/system.py +7 -2
- execsql/models.py +7 -0
- execsql/parser.py +10 -0
- execsql/py.typed +0 -0
- execsql/script/__init__.py +95 -0
- execsql/script/control.py +162 -0
- execsql/{script.py → script/engine.py} +184 -402
- execsql/script/variables.py +281 -0
- execsql/types.py +49 -20
- execsql/utils/auth.py +2 -0
- execsql/utils/crypto.py +4 -6
- execsql/utils/datetime.py +1 -0
- execsql/utils/errors.py +11 -0
- execsql/utils/fileio.py +33 -8
- execsql/utils/gui.py +46 -0
- execsql/utils/mail.py +7 -17
- execsql/utils/numeric.py +2 -0
- execsql/utils/regex.py +9 -0
- execsql/utils/strings.py +16 -0
- execsql/utils/timer.py +2 -0
- execsql2-2.4.0.data/data/execsql2_extras/README.md +65 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/execsql.conf +1 -1
- {execsql2-2.1.2.dist-info → execsql2-2.4.0.dist-info}/METADATA +13 -6
- execsql2-2.4.0.dist-info/RECORD +108 -0
- execsql2-2.1.2.data/data/execsql2_extras/READ_ME.rst +0 -127
- execsql2-2.1.2.dist-info/RECORD +0 -96
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.4.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.4.0.dist-info}/WHEEL +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.4.0.dist-info}/entry_points.txt +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.4.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.4.0.dist-info}/licenses/NOTICE +0 -0
execsql/exporters/html.py
CHANGED
|
@@ -11,6 +11,7 @@ CSS styling.
|
|
|
11
11
|
|
|
12
12
|
import datetime
|
|
13
13
|
import getpass
|
|
14
|
+
import html as html_mod
|
|
14
15
|
import os
|
|
15
16
|
import sys
|
|
16
17
|
import tempfile
|
|
@@ -24,6 +25,8 @@ from execsql.script import current_script_line
|
|
|
24
25
|
from execsql.utils.errors import exception_desc
|
|
25
26
|
from execsql.utils.fileio import filewriter_close
|
|
26
27
|
|
|
28
|
+
__all__ = ["export_html", "export_cgi_html", "write_query_to_html", "write_query_to_cgi_html"]
|
|
29
|
+
|
|
27
30
|
|
|
28
31
|
def export_html(
|
|
29
32
|
outfile: str,
|
|
@@ -34,6 +37,7 @@ def export_html(
|
|
|
34
37
|
desc: str | None = None,
|
|
35
38
|
zipfile: str | None = None,
|
|
36
39
|
) -> None:
|
|
40
|
+
"""Write a complete HTML document containing a data table to a file or ZIP archive."""
|
|
37
41
|
conf = _state.conf
|
|
38
42
|
|
|
39
43
|
def write_table(f):
|
|
@@ -42,12 +46,12 @@ def export_html(
|
|
|
42
46
|
f.write(f"<caption>{desc}</caption>\n")
|
|
43
47
|
f.write("<thead><tr>")
|
|
44
48
|
for h in hdrs:
|
|
45
|
-
f.write(f"<th>{h}</th>")
|
|
49
|
+
f.write(f"<th>{html_mod.escape(str(h))}</th>")
|
|
46
50
|
f.write("</tr></thead>\n<tbody>\n")
|
|
47
51
|
for r in rows:
|
|
48
52
|
f.write("<tr>")
|
|
49
53
|
for v in r:
|
|
50
|
-
f.write(f"<td>{v if v else ''}</td>")
|
|
54
|
+
f.write(f"<td>{html_mod.escape(str(v)) if v else ''}</td>")
|
|
51
55
|
f.write("</tr>\n")
|
|
52
56
|
f.write("</tbody>\n</table>\n")
|
|
53
57
|
|
|
@@ -67,42 +71,47 @@ def export_html(
|
|
|
67
71
|
f = ef.open("wt")
|
|
68
72
|
else:
|
|
69
73
|
f = ZipWriter(zipfile, outfile, append)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if conf.css_styles:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
74
|
+
try:
|
|
75
|
+
f.write('<!DOCTYPE html>\n<html>\n<head>\n<meta charset="utf-8" />\n')
|
|
76
|
+
if querytext:
|
|
77
|
+
descrip = f"Source: [{querytext}] with database {_state.dbs.current().name()} in script {str(Path(script).resolve())}, line {lno}"
|
|
78
|
+
else:
|
|
79
|
+
descrip = (
|
|
80
|
+
f"From database {_state.dbs.current().name()} in script {str(Path(script).resolve())}, line {lno}"
|
|
81
|
+
)
|
|
82
|
+
f.write(f'<meta name="description" content="{descrip}" />\n')
|
|
83
|
+
datecontent = datetime.datetime.now().strftime("%Y-%m-%d")
|
|
84
|
+
f.write(f'<meta name="created" content="{datecontent}" />\n')
|
|
85
|
+
f.write(f'<meta name="revised" content="{datecontent}" />\n')
|
|
86
|
+
f.write(f'<meta name="author" content="{getpass.getuser()}" />\n')
|
|
87
|
+
f.write("<title>Data Table</title>\n")
|
|
88
|
+
if conf.css_file or conf.css_styles:
|
|
89
|
+
if conf.css_file:
|
|
90
|
+
f.write(f'<link rel="stylesheet" type="text/css" href="{conf.css_file}">')
|
|
91
|
+
if conf.css_styles:
|
|
92
|
+
f.write(f'<style type="text/css">\n{conf.css_styles}\n</style>')
|
|
93
|
+
else:
|
|
94
|
+
f.write('<style type="text/css">\n')
|
|
95
|
+
f.write(
|
|
96
|
+
'table {font-family: "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Lucida Console", "Courier New", Courier, fixed; '
|
|
97
|
+
+ "border-top: 3px solid #814324; border-bottom: 3px solid #814324; "
|
|
98
|
+
+ "border-left: 2px solid #814324; border-right: 2px solid #814324; "
|
|
99
|
+
+ "border-collapse: collapse; }\n",
|
|
100
|
+
)
|
|
101
|
+
f.write("td {text-align: left; padding 0 10px; border-right: 1px dotted #814324; }\n")
|
|
102
|
+
f.write(
|
|
103
|
+
"th {padding: 2px 10px; text-align: center; border-bottom: 1px solid #814324; border-right: 1px dotted #814324;}\n",
|
|
104
|
+
)
|
|
105
|
+
f.write("tr.hdr {font-weight: bold;}\n")
|
|
106
|
+
f.write("thead tr {border-bottom: 1px solid #814324; background-color: #F3F1E2; }\n")
|
|
107
|
+
f.write("tbody tr { border-bottom: 1px dotted #814324; }\n")
|
|
108
|
+
f.write("</style>")
|
|
109
|
+
f.write("\n</head>\n<body>\n")
|
|
110
|
+
write_table(f)
|
|
111
|
+
f.write("</body>\n</html>\n")
|
|
112
|
+
finally:
|
|
113
|
+
if outfile.lower() != "stdout":
|
|
114
|
+
f.close()
|
|
106
115
|
elif not zipfile and append:
|
|
107
116
|
if outfile.lower() == "stdout":
|
|
108
117
|
f = sys.stdout
|
|
@@ -112,8 +121,10 @@ def export_html(
|
|
|
112
121
|
|
|
113
122
|
ef = EncodedFile(outfile, conf.output_encoding)
|
|
114
123
|
f = ef.open("wt")
|
|
115
|
-
|
|
116
|
-
|
|
124
|
+
try:
|
|
125
|
+
write_table(f)
|
|
126
|
+
finally:
|
|
127
|
+
f.close()
|
|
117
128
|
else:
|
|
118
129
|
filewriter_close(outfile)
|
|
119
130
|
from execsql.utils.fileio import EncodedFile
|
|
@@ -124,23 +135,25 @@ def export_html(
|
|
|
124
135
|
os.close(tempf) # Close the fd from mkstemp; EncodedFile opens its own handle
|
|
125
136
|
tf = EncodedFile(tempfname, conf.output_encoding)
|
|
126
137
|
t = tf.open("wt")
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
138
|
+
try:
|
|
139
|
+
remainder = ""
|
|
140
|
+
for line in f:
|
|
141
|
+
bodypos = line.lower().find("</body>")
|
|
142
|
+
if bodypos > -1:
|
|
143
|
+
t.write(line[0:bodypos])
|
|
144
|
+
t.write("\n")
|
|
145
|
+
remainder = line[bodypos:]
|
|
146
|
+
break
|
|
147
|
+
else:
|
|
148
|
+
t.write(line)
|
|
149
|
+
t.write("\n")
|
|
150
|
+
write_table(t)
|
|
151
|
+
t.write(remainder)
|
|
152
|
+
for line in f:
|
|
136
153
|
t.write(line)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
for line in f:
|
|
141
|
-
t.write(line)
|
|
142
|
-
t.close()
|
|
143
|
-
f.close()
|
|
154
|
+
finally:
|
|
155
|
+
t.close()
|
|
156
|
+
f.close()
|
|
144
157
|
os.unlink(outfile)
|
|
145
158
|
os.rename(tempfname, outfile)
|
|
146
159
|
|
|
@@ -154,6 +167,7 @@ def export_cgi_html(
|
|
|
154
167
|
desc: str | None = None,
|
|
155
168
|
zipfile: str | None = None,
|
|
156
169
|
) -> None:
|
|
170
|
+
"""Write a CGI-style HTML fragment (Content-Type header + table) to a file or ZIP archive."""
|
|
157
171
|
conf = _state.conf
|
|
158
172
|
|
|
159
173
|
def write_table(f):
|
|
@@ -162,12 +176,12 @@ def export_cgi_html(
|
|
|
162
176
|
f.write(f"<caption>{desc}</caption>\n")
|
|
163
177
|
f.write("<thead><tr>")
|
|
164
178
|
for h in hdrs:
|
|
165
|
-
f.write(f"<th>{h}</th>")
|
|
179
|
+
f.write(f"<th>{html_mod.escape(str(h))}</th>")
|
|
166
180
|
f.write("</tr></thead>\n<tbody>\n")
|
|
167
181
|
for r in rows:
|
|
168
182
|
f.write("<tr>")
|
|
169
183
|
for v in r:
|
|
170
|
-
f.write(f"<td>{v if v else ''}</td>")
|
|
184
|
+
f.write(f"<td>{html_mod.escape(str(v)) if v else ''}</td>")
|
|
171
185
|
f.write("</tr>\n")
|
|
172
186
|
f.write("</tbody>\n</table>\n")
|
|
173
187
|
|
|
@@ -184,10 +198,12 @@ def export_cgi_html(
|
|
|
184
198
|
f = ef.open("wt")
|
|
185
199
|
else:
|
|
186
200
|
f = ZipWriter(zipfile, outfile, append)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
201
|
+
try:
|
|
202
|
+
f.write("Content-Type: text/html\n\n")
|
|
203
|
+
write_table(f)
|
|
204
|
+
finally:
|
|
205
|
+
if outfile.lower() != "stdout":
|
|
206
|
+
f.close()
|
|
191
207
|
else:
|
|
192
208
|
if outfile == "stdout":
|
|
193
209
|
f = sys.stdout
|
|
@@ -196,9 +212,11 @@ def export_cgi_html(
|
|
|
196
212
|
|
|
197
213
|
ef = EncodedFile(outfile, conf.output_encoding)
|
|
198
214
|
f = ef.open("a")
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
215
|
+
try:
|
|
216
|
+
write_table(f)
|
|
217
|
+
finally:
|
|
218
|
+
if outfile.lower() != "stdout":
|
|
219
|
+
f.close()
|
|
202
220
|
|
|
203
221
|
|
|
204
222
|
def write_query_to_html(
|
|
@@ -209,12 +227,13 @@ def write_query_to_html(
|
|
|
209
227
|
desc: str | None = None,
|
|
210
228
|
zipfile: str | None = None,
|
|
211
229
|
) -> None:
|
|
230
|
+
"""Execute a SELECT and write the result set as a standalone HTML document."""
|
|
212
231
|
try:
|
|
213
232
|
hdrs, rows = db.select_rowsource(select_stmt)
|
|
214
233
|
except ErrInfo:
|
|
215
234
|
raise
|
|
216
|
-
except Exception:
|
|
217
|
-
raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
|
|
235
|
+
except Exception as e:
|
|
236
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
218
237
|
export_html(outfile, hdrs, rows, append, select_stmt, desc, zipfile=zipfile)
|
|
219
238
|
|
|
220
239
|
|
|
@@ -226,10 +245,11 @@ def write_query_to_cgi_html(
|
|
|
226
245
|
desc: str | None = None,
|
|
227
246
|
zipfile: str | None = None,
|
|
228
247
|
) -> None:
|
|
248
|
+
"""Execute a SELECT and write the result set as a CGI-style HTML fragment."""
|
|
229
249
|
try:
|
|
230
250
|
hdrs, rows = db.select_rowsource(select_stmt)
|
|
231
251
|
except ErrInfo:
|
|
232
252
|
raise
|
|
233
|
-
except Exception:
|
|
234
|
-
raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
|
|
253
|
+
except Exception as e:
|
|
254
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
235
255
|
export_cgi_html(outfile, hdrs, rows, append, select_stmt, desc, zipfile=zipfile)
|
execsql/exporters/json.py
CHANGED
|
@@ -8,6 +8,7 @@ Provides :func:`write_query_to_json` (standard JSON array of objects) and
|
|
|
8
8
|
both of which serialize a query result set to a file or stream.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
import json
|
|
11
12
|
from typing import Any
|
|
12
13
|
|
|
13
14
|
import execsql.state as _state
|
|
@@ -17,6 +18,8 @@ from execsql.models import DataTable
|
|
|
17
18
|
from execsql.utils.errors import exception_desc
|
|
18
19
|
from execsql.utils.fileio import filewriter_close
|
|
19
20
|
|
|
21
|
+
__all__ = ["write_query_to_json", "write_query_to_json_ts"]
|
|
22
|
+
|
|
20
23
|
|
|
21
24
|
def write_query_to_json(
|
|
22
25
|
select_stmt: str,
|
|
@@ -26,16 +29,14 @@ def write_query_to_json(
|
|
|
26
29
|
desc: str | None = None,
|
|
27
30
|
zipfile: str | None = None,
|
|
28
31
|
) -> None:
|
|
29
|
-
|
|
30
|
-
import json
|
|
31
|
-
|
|
32
|
+
"""Execute a SELECT and write the result set as a JSON array of objects."""
|
|
32
33
|
conf = _state.conf
|
|
33
34
|
try:
|
|
34
35
|
hdrs, rows = db.select_rowsource(select_stmt)
|
|
35
36
|
except ErrInfo:
|
|
36
37
|
raise
|
|
37
|
-
except Exception:
|
|
38
|
-
raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
|
|
38
|
+
except Exception as e:
|
|
39
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
39
40
|
if zipfile is None:
|
|
40
41
|
filewriter_close(outfile)
|
|
41
42
|
from execsql.utils.fileio import EncodedFile
|
|
@@ -48,20 +49,22 @@ def write_query_to_json(
|
|
|
48
49
|
f = ef.open("wt")
|
|
49
50
|
else:
|
|
50
51
|
f = ZipWriter(zipfile, outfile, append)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
52
|
+
try:
|
|
53
|
+
f.write("[")
|
|
54
|
+
uhdrs = [str(h) for h in hdrs]
|
|
55
|
+
first = True
|
|
56
|
+
for row in rows:
|
|
57
|
+
if first:
|
|
58
|
+
f.write("\n")
|
|
59
|
+
else:
|
|
60
|
+
f.write(",\n")
|
|
61
|
+
first = False
|
|
62
|
+
dictdata = dict(zip(uhdrs, [str(v) if isinstance(v, str) else v for v in row]))
|
|
63
|
+
jsondata = json.dumps(dictdata, separators=(",", ":"), default=str)
|
|
64
|
+
f.write(str(jsondata))
|
|
65
|
+
f.write("\n]\n")
|
|
66
|
+
finally:
|
|
67
|
+
f.close()
|
|
65
68
|
|
|
66
69
|
|
|
67
70
|
def write_query_to_json_ts(
|
|
@@ -73,13 +76,14 @@ def write_query_to_json_ts(
|
|
|
73
76
|
desc: str | None = None,
|
|
74
77
|
zipfile: str | None = None,
|
|
75
78
|
) -> None:
|
|
79
|
+
"""Execute a SELECT and write the result set as a JSON object with a top-level field-type schema."""
|
|
76
80
|
conf = _state.conf
|
|
77
81
|
try:
|
|
78
82
|
hdrs, rows = db.select_rowsource(select_stmt)
|
|
79
83
|
except ErrInfo:
|
|
80
84
|
raise
|
|
81
|
-
except Exception:
|
|
82
|
-
raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
|
|
85
|
+
except Exception as e:
|
|
86
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
83
87
|
max_col_idx = len(hdrs) - 1
|
|
84
88
|
if zipfile is None:
|
|
85
89
|
filewriter_close(outfile)
|
|
@@ -93,27 +97,30 @@ def write_query_to_json_ts(
|
|
|
93
97
|
f = ef.open("wt")
|
|
94
98
|
else:
|
|
95
99
|
f = ZipWriter(zipfile, outfile, append)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
100
|
+
try:
|
|
101
|
+
f.write("{\n")
|
|
102
|
+
if desc is not None:
|
|
103
|
+
escaped_desc = json.dumps(desc)
|
|
104
|
+
f.write(f' "description": {escaped_desc},\n')
|
|
105
|
+
f.write(' "fields": [\n')
|
|
106
|
+
if write_types:
|
|
107
|
+
# Scan the data to determine data types.
|
|
108
|
+
tbl_desc = DataTable(hdrs, rows)
|
|
109
|
+
# Write the column descriptions to the header.
|
|
110
|
+
# Iterate over hdrs instead of tbl_desc.cols to preserve column order.
|
|
111
|
+
for i, h in enumerate(hdrs):
|
|
112
|
+
qcomma = "," if i < max_col_idx else ""
|
|
113
|
+
c = [col for col in tbl_desc.cols if col.name == h][0]
|
|
114
|
+
f.write(
|
|
115
|
+
f' {{\n "name": "{c.name}",\n "title": "{c.name.capitalize().replace("_", " ")}",\n "type": "{_state.to_json_type[c.dt[1]]}"\n }}{qcomma}\n',
|
|
116
|
+
)
|
|
117
|
+
else:
|
|
118
|
+
# Write the column descriptions to the header.
|
|
119
|
+
for i, h in enumerate(hdrs):
|
|
120
|
+
qcomma = "," if i < max_col_idx else ""
|
|
121
|
+
f.write(
|
|
122
|
+
f' {{\n "name": "{h}",\n "title": "{h.capitalize().replace("_", " ")}"\n }}{qcomma}\n',
|
|
123
|
+
)
|
|
124
|
+
f.write(" ]\n}\n")
|
|
125
|
+
finally:
|
|
126
|
+
f.close()
|
execsql/exporters/latex.py
CHANGED
|
@@ -17,6 +17,8 @@ from execsql.exceptions import ErrInfo
|
|
|
17
17
|
from execsql.exporters.zip import WriteableZipfile
|
|
18
18
|
import execsql.state as _state
|
|
19
19
|
|
|
20
|
+
__all__ = ["export_latex", "write_query_to_latex"]
|
|
21
|
+
|
|
20
22
|
|
|
21
23
|
def export_latex(
|
|
22
24
|
outfile: str,
|
|
@@ -27,6 +29,7 @@ def export_latex(
|
|
|
27
29
|
desc: str | None = None,
|
|
28
30
|
zipfile: Any | None = None,
|
|
29
31
|
) -> None:
|
|
32
|
+
"""Write pre-fetched rows as a LaTeX tabular environment to a file or ZIP archive."""
|
|
30
33
|
from execsql.utils.fileio import EncodedFile
|
|
31
34
|
|
|
32
35
|
def write_table(f: Any) -> None:
|
|
@@ -60,12 +63,14 @@ def export_latex(
|
|
|
60
63
|
f = ef.open("wt")
|
|
61
64
|
else:
|
|
62
65
|
f = WriteableZipfile(zipfile).open(outfile, append)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
try:
|
|
67
|
+
f.write("\\documentclass{article}\n")
|
|
68
|
+
f.write("\\begin{document}\n")
|
|
69
|
+
write_table(f)
|
|
70
|
+
f.write("\\end{document}\n")
|
|
71
|
+
finally:
|
|
72
|
+
if outfile.lower() != "stdout":
|
|
73
|
+
f.close()
|
|
69
74
|
else:
|
|
70
75
|
if outfile.lower() == "stdout" or not Path(outfile).is_file():
|
|
71
76
|
if outfile.lower() == "stdout":
|
|
@@ -75,9 +80,11 @@ def export_latex(
|
|
|
75
80
|
else:
|
|
76
81
|
ef = EncodedFile(outfile, conf.output_encoding)
|
|
77
82
|
f = ef.open("wt")
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
try:
|
|
84
|
+
write_table(f)
|
|
85
|
+
finally:
|
|
86
|
+
if outfile.lower() != "stdout":
|
|
87
|
+
f.close()
|
|
81
88
|
else:
|
|
82
89
|
ef = EncodedFile(outfile, conf.output_encoding)
|
|
83
90
|
f = ef.open("rt")
|
|
@@ -85,23 +92,25 @@ def export_latex(
|
|
|
85
92
|
os.close(tempf) # Close the fd from mkstemp; EncodedFile opens its own handle
|
|
86
93
|
tf = EncodedFile(tempfname, conf.output_encoding)
|
|
87
94
|
t = tf.open("wt")
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
try:
|
|
96
|
+
remainder = ""
|
|
97
|
+
for line in f:
|
|
98
|
+
bodypos = line.lower().find("\\end{document}")
|
|
99
|
+
if bodypos > -1:
|
|
100
|
+
t.write(line[0:bodypos])
|
|
101
|
+
t.write("\n")
|
|
102
|
+
remainder = line[bodypos:]
|
|
103
|
+
break
|
|
104
|
+
else:
|
|
105
|
+
t.write(line)
|
|
106
|
+
t.write("\n")
|
|
107
|
+
write_table(t)
|
|
108
|
+
t.write(remainder)
|
|
109
|
+
for line in f:
|
|
97
110
|
t.write(line)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
for line in f:
|
|
102
|
-
t.write(line)
|
|
103
|
-
t.close()
|
|
104
|
-
f.close()
|
|
111
|
+
finally:
|
|
112
|
+
t.close()
|
|
113
|
+
f.close()
|
|
105
114
|
os.unlink(outfile)
|
|
106
115
|
os.rename(tempfname, outfile)
|
|
107
116
|
|
|
@@ -114,12 +123,13 @@ def write_query_to_latex(
|
|
|
114
123
|
desc: str | None = None,
|
|
115
124
|
zipfile: Any | None = None,
|
|
116
125
|
) -> None:
|
|
126
|
+
"""Execute a SELECT and write the result set as a LaTeX tabular table."""
|
|
117
127
|
from execsql.utils.errors import exception_desc
|
|
118
128
|
|
|
119
129
|
try:
|
|
120
130
|
hdrs, rows = db.select_rowsource(select_stmt)
|
|
121
131
|
except ErrInfo:
|
|
122
132
|
raise
|
|
123
|
-
except Exception:
|
|
124
|
-
raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
|
|
133
|
+
except Exception as e:
|
|
134
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
125
135
|
export_latex(outfile, hdrs, rows, append, select_stmt, desc, zipfile=zipfile)
|