execsql2 2.0.1__py3-none-any.whl → 2.1.2__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.
Files changed (90) hide show
  1. execsql/cli.py +322 -108
  2. execsql/config.py +134 -114
  3. execsql/db/access.py +89 -65
  4. execsql/db/base.py +97 -68
  5. execsql/db/dsn.py +45 -29
  6. execsql/db/duckdb.py +4 -5
  7. execsql/db/factory.py +27 -27
  8. execsql/db/firebird.py +30 -18
  9. execsql/db/mysql.py +38 -14
  10. execsql/db/oracle.py +58 -33
  11. execsql/db/postgres.py +68 -28
  12. execsql/db/sqlite.py +36 -27
  13. execsql/db/sqlserver.py +45 -30
  14. execsql/exceptions.py +68 -64
  15. execsql/exporters/__init__.py +1 -1
  16. execsql/exporters/base.py +42 -17
  17. execsql/exporters/delimited.py +60 -59
  18. execsql/exporters/duckdb.py +8 -12
  19. execsql/exporters/feather.py +32 -24
  20. execsql/exporters/html.py +33 -30
  21. execsql/exporters/json.py +18 -17
  22. execsql/exporters/latex.py +11 -13
  23. execsql/exporters/ods.py +50 -46
  24. execsql/exporters/parquet.py +32 -0
  25. execsql/exporters/pretty.py +16 -15
  26. execsql/exporters/raw.py +9 -11
  27. execsql/exporters/sqlite.py +38 -38
  28. execsql/exporters/templates.py +15 -72
  29. execsql/exporters/values.py +13 -12
  30. execsql/exporters/xls.py +26 -26
  31. execsql/exporters/xml.py +12 -12
  32. execsql/exporters/zip.py +0 -3
  33. execsql/gui/__init__.py +2 -2
  34. execsql/gui/console.py +0 -1
  35. execsql/gui/desktop.py +6 -7
  36. execsql/gui/tui.py +8 -14
  37. execsql/importers/base.py +6 -9
  38. execsql/importers/csv.py +10 -17
  39. execsql/importers/feather.py +16 -22
  40. execsql/importers/ods.py +3 -4
  41. execsql/importers/xls.py +5 -6
  42. execsql/metacommands/__init__.py +8 -8
  43. execsql/metacommands/conditions.py +41 -33
  44. execsql/metacommands/connect.py +113 -99
  45. execsql/metacommands/control.py +38 -26
  46. execsql/metacommands/data.py +35 -33
  47. execsql/metacommands/debug.py +13 -9
  48. execsql/metacommands/io.py +288 -229
  49. execsql/metacommands/prompt.py +179 -157
  50. execsql/metacommands/script_ext.py +11 -9
  51. execsql/metacommands/system.py +44 -25
  52. execsql/models.py +9 -16
  53. execsql/parser.py +10 -10
  54. execsql/script.py +183 -157
  55. execsql/state.py +170 -208
  56. execsql/types.py +46 -81
  57. execsql/utils/auth.py +114 -14
  58. execsql/utils/crypto.py +31 -4
  59. execsql/utils/datetime.py +7 -7
  60. execsql/utils/errors.py +34 -29
  61. execsql/utils/fileio.py +90 -55
  62. execsql/utils/gui.py +22 -23
  63. execsql/utils/mail.py +15 -17
  64. execsql/utils/numeric.py +2 -3
  65. execsql/utils/regex.py +9 -12
  66. execsql/utils/strings.py +10 -12
  67. execsql/utils/timer.py +0 -2
  68. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/execsql.conf +1 -1
  69. execsql2-2.1.2.dist-info/METADATA +300 -0
  70. execsql2-2.1.2.dist-info/RECORD +96 -0
  71. execsql2-2.0.1.dist-info/METADATA +0 -406
  72. execsql2-2.0.1.dist-info/RECORD +0 -95
  73. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/READ_ME.rst +0 -0
  74. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  75. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  76. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/make_config_db.sql +0 -0
  77. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/md_compare.sql +0 -0
  78. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/md_glossary.sql +0 -0
  79. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/md_upsert.sql +0 -0
  80. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/pg_compare.sql +0 -0
  81. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  82. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  83. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/script_template.sql +0 -0
  84. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/ss_compare.sql +0 -0
  85. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  86. {execsql2-2.0.1.data → execsql2-2.1.2.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  87. {execsql2-2.0.1.dist-info → execsql2-2.1.2.dist-info}/WHEEL +0 -0
  88. {execsql2-2.0.1.dist-info → execsql2-2.1.2.dist-info}/entry_points.txt +0 -0
  89. {execsql2-2.0.1.dist-info → execsql2-2.1.2.dist-info}/licenses/LICENSE.txt +0 -0
  90. {execsql2-2.0.1.dist-info → execsql2-2.1.2.dist-info}/licenses/NOTICE +0 -0
execsql/importers/csv.py CHANGED
@@ -9,38 +9,33 @@ a delimited file), used by the ``IMPORT`` metacommand with ``FORMAT csv``,
9
9
  ``FORMAT tsv``, and ``FORMAT txt``.
10
10
  """
11
11
 
12
- import codecs
13
- import csv
14
- import os
15
- import re
16
- from typing import Any, Optional
12
+ from pathlib import Path
13
+ from typing import Any
17
14
 
18
15
  from execsql.exceptions import ErrInfo
19
16
  from execsql.db.base import Database
20
- from execsql.importers.base import import_data_table
21
17
  import execsql.state as _state
18
+ from execsql.types import dbt_firebird
22
19
 
23
20
 
24
21
  def importtable(
25
22
  db: Database,
26
- schemaname: Optional[str],
23
+ schemaname: str | None,
27
24
  tablename: str,
28
25
  filename: str,
29
26
  is_new: Any,
30
27
  skip_header_line: bool = True,
31
- quotechar: Optional[str] = None,
32
- delimchar: Optional[str] = None,
33
- encoding: Optional[str] = None,
28
+ quotechar: str | None = None,
29
+ delimchar: str | None = None,
30
+ encoding: str | None = None,
34
31
  junk_header_lines: int = 0,
35
32
  ) -> None:
36
33
  from execsql.utils.errors import exception_info
37
34
 
38
35
  conf = _state.conf
39
- dbt_firebird = _state.dbt_firebird
40
-
41
- if not os.path.isfile(filename):
36
+ if not Path(filename).is_file():
42
37
  raise ErrInfo(type="error", other_msg=f"Non-existent file ({filename}) used with the IMPORT metacommand")
43
- enc = conf.import_encoding if not encoding else encoding
38
+ enc = encoding if encoding else conf.import_encoding
44
39
 
45
40
  # Lazy import of CsvFile
46
41
  from execsql.exporters.delimited import CsvFile
@@ -57,8 +52,6 @@ def importtable(
57
52
  try:
58
53
  db.drop_table(db.schema_qualified_table_name(schemaname, tablename))
59
54
  except Exception:
60
- from execsql.utils.fileio import Logger
61
-
62
55
  _state.exec_log.log_status_info(f"Could not drop existing table ({tablename}) for IMPORT metacommand")
63
56
  # Don't raise an exception; this may not be a problem because the table may not already exist.
64
57
  try:
@@ -104,7 +97,7 @@ def importtable(
104
97
 
105
98
  def importfile(
106
99
  db: Database,
107
- schemaname: Optional[str],
100
+ schemaname: str | None,
108
101
  tablename: str,
109
102
  columname: str,
110
103
  filename: str,
@@ -3,13 +3,12 @@ from __future__ import annotations
3
3
  """
4
4
  Feather and Parquet import for execsql.
5
5
 
6
- Provides :func:`import_feather` (Apache Arrow Feather v2 via ``pyarrow``)
7
- and :func:`import_parquet` (Parquet format via ``pyarrow``), used by
8
- ``IMPORT … FORMAT feather`` and ``FORMAT parquet``.
6
+ Provides :func:`import_feather` (Apache Arrow Feather v2 / Arrow IPC via
7
+ ``polars``) and :func:`import_parquet` (Parquet format via ``polars``),
8
+ used by ``IMPORT … FORMAT feather`` and ``FORMAT parquet``.
9
9
  """
10
10
 
11
- import os
12
- from typing import Any, Optional
11
+ from typing import Any
13
12
 
14
13
  from execsql.exceptions import ErrInfo
15
14
  from execsql.db.base import Database
@@ -18,7 +17,7 @@ from execsql.importers.base import import_data_table
18
17
 
19
18
  def import_feather(
20
19
  db: Database,
21
- schemaname: Optional[str],
20
+ schemaname: str | None,
22
21
  tablename: str,
23
22
  filename: str,
24
23
  is_new: Any,
@@ -26,25 +25,22 @@ def import_feather(
26
25
  from execsql.utils.errors import exception_info
27
26
 
28
27
  try:
29
- import numpy as np
30
- import pandas as pd
31
- import pyarrow.feather
28
+ import polars as pl
32
29
  except Exception:
33
30
  raise ErrInfo(
34
31
  "exception",
35
32
  exception_msg=exception_info(),
36
- other_msg="The pandas and pyarrow Python libraries must be installed to import data from the Feather format.",
33
+ other_msg="The polars Python library must be installed to import data from the Feather format.",
37
34
  )
38
- df = pd.read_feather(filename)
39
- df = df.replace({np.nan: None})
40
- hdrs = df.columns.values.tolist()
41
- data = df.values.tolist()
35
+ df = pl.read_ipc(filename)
36
+ hdrs = df.columns
37
+ data = [list(row) for row in df.rows()]
42
38
  import_data_table(db, schemaname, tablename, is_new, hdrs, data)
43
39
 
44
40
 
45
41
  def import_parquet(
46
42
  db: Database,
47
- schemaname: Optional[str],
43
+ schemaname: str | None,
48
44
  tablename: str,
49
45
  filename: str,
50
46
  is_new: Any,
@@ -52,16 +48,14 @@ def import_parquet(
52
48
  from execsql.utils.errors import exception_info
53
49
 
54
50
  try:
55
- import numpy as np
56
- import pandas as pd
51
+ import polars as pl
57
52
  except Exception:
58
53
  raise ErrInfo(
59
54
  "exception",
60
55
  exception_msg=exception_info(),
61
- other_msg="The pandas and fastparquet or pyarrow Python libraries must be installed to import data from the Parquet format.",
56
+ other_msg="The polars Python library must be installed to import data from the Parquet format.",
62
57
  )
63
- df = pd.read_parquet(filename)
64
- df = df.replace({np.nan: None})
65
- hdrs = df.columns.values.tolist()
66
- data = df.values.tolist()
58
+ df = pl.read_parquet(filename)
59
+ hdrs = df.columns
60
+ data = [list(row) for row in df.rows()]
67
61
  import_data_table(db, schemaname, tablename, is_new, hdrs, data)
execsql/importers/ods.py CHANGED
@@ -9,8 +9,7 @@ the ``IMPORT … FORMAT ods`` metacommand. Requires ``odfpy``
9
9
  (``execsql2[ods]``).
10
10
  """
11
11
 
12
- import os
13
- from typing import Any, List, Optional
12
+ from typing import Any
14
13
 
15
14
  from execsql.exceptions import ErrInfo
16
15
  from execsql.db.base import Database
@@ -39,7 +38,7 @@ def ods_data(
39
38
  except Exception:
40
39
  raise ErrInfo(type="cmd", other_msg=f"{sheetname} is not a worksheet in {filename}.")
41
40
  colhdrs = alldata[0]
42
- if any([x is None or len(x.strip()) == 0 for x in colhdrs]):
41
+ if any(x is None or len(x.strip()) == 0 for x in colhdrs):
43
42
  if conf.del_empty_cols:
44
43
  blanks = [i for i in range(len(colhdrs)) if colhdrs[i] is None or len(colhdrs[i].strip()) == 0]
45
44
  while len(blanks) > 0:
@@ -70,7 +69,7 @@ def ods_data(
70
69
 
71
70
  def importods(
72
71
  db: Database,
73
- schemaname: Optional[str],
72
+ schemaname: str | None,
74
73
  tablename: str,
75
74
  is_new: Any,
76
75
  filename: str,
execsql/importers/xls.py CHANGED
@@ -9,8 +9,7 @@ reader) and the XLSX equivalent using ``openpyxl``. Used by
9
9
  ``execsql2[excel]``.
10
10
  """
11
11
 
12
- import os
13
- from typing import Any, List, Optional
12
+ from typing import Any
14
13
 
15
14
  from execsql.exceptions import ErrInfo
16
15
  from execsql.db.base import Database
@@ -22,7 +21,7 @@ def xls_data(
22
21
  filename: str,
23
22
  sheetname: str,
24
23
  junk_header_rows: int,
25
- encoding: Optional[str] = None,
24
+ encoding: str | None = None,
26
25
  ) -> tuple:
27
26
  """Returns the data from the specified worksheet as a list of headers and a list of lists of rows."""
28
27
  from execsql.utils.strings import clean_words, trim_words, fold_words, dedup_words
@@ -59,7 +58,7 @@ def xls_data(
59
58
  if len(alldata) == 1:
60
59
  return alldata[0], []
61
60
  colhdrs = alldata[0]
62
- if any([x is None or (isinstance(x, str) and len(x.strip()) == 0) for x in colhdrs]):
61
+ if any(x is None or (isinstance(x, str) and len(x.strip()) == 0) for x in colhdrs):
63
62
  if conf.del_empty_cols:
64
63
  blanks = [i for i in range(len(colhdrs)) if colhdrs[i] is None or len(colhdrs[i].strip()) == 0]
65
64
  while len(blanks) > 0:
@@ -90,13 +89,13 @@ def xls_data(
90
89
 
91
90
  def importxls(
92
91
  db: Database,
93
- schemaname: Optional[str],
92
+ schemaname: str | None,
94
93
  tablename: str,
95
94
  is_new: Any,
96
95
  filename: str,
97
96
  sheetname: str,
98
97
  junk_header_rows: int,
99
- encoding: Optional[str],
98
+ encoding: str | None,
100
99
  ) -> None:
101
100
  hdrs, data = xls_data(filename, sheetname, junk_header_rows, encoding)
102
101
  import_data_table(db, schemaname, tablename, is_new, hdrs, data)
@@ -12,7 +12,7 @@ from __future__ import annotations
12
12
 
13
13
  import re
14
14
 
15
- import execsql.state as _state
15
+ import execsql.state as _state # noqa: F401
16
16
 
17
17
  # Handler imports — grouped by module for readability.
18
18
  from execsql.metacommands.connect import (
@@ -29,7 +29,7 @@ from execsql.metacommands.connect import (
29
29
  x_connect_user_ora,
30
30
  x_connect_duckdb,
31
31
  x_connect_sqlite,
32
- x_connect_dsn,
32
+ x_connect_dsn, # noqa: F401
33
33
  x_use,
34
34
  x_disconnect,
35
35
  x_autocommit_on,
@@ -188,13 +188,13 @@ from execsql.metacommands.system import (
188
188
  x_cancel_halt_write,
189
189
  x_cancel_halt_email_clear,
190
190
  x_cancel_halt_email,
191
- x_cancel_halt_exec,
191
+ x_cancel_halt_exec, # noqa: F401
192
192
  x_cancel_halt_exec_clear,
193
193
  x_error_halt_write_clear,
194
194
  x_error_halt_write,
195
195
  x_error_halt_email_clear,
196
196
  x_error_halt_email,
197
- x_error_halt_exec,
197
+ x_error_halt_exec, # noqa: F401
198
198
  x_error_halt_exec_clear,
199
199
  x_write_warnings,
200
200
  x_gui_level,
@@ -204,17 +204,18 @@ from execsql.metacommands.system import (
204
204
  # Regex helper functions (from utils/regex.py)
205
205
  from execsql.utils.regex import (
206
206
  ins_rxs,
207
- ins_quoted_rx,
207
+ ins_quoted_rx, # noqa: F401
208
208
  ins_schema_rxs,
209
209
  ins_table_rxs,
210
210
  ins_table_list_rxs,
211
211
  ins_fn_rxs,
212
212
  )
213
+ from execsql.script import MetaCommandList
213
214
 
214
215
 
215
- def build_dispatch_table() -> _state.MetaCommandList:
216
+ def build_dispatch_table() -> MetaCommandList:
216
217
  """Construct and return the complete metacommand dispatch table."""
217
- mcl = _state.MetaCommandList()
218
+ mcl = MetaCommandList()
218
219
 
219
220
  # ------------------------------------------------------------------
220
221
  # DEBUG metacommands
@@ -1040,7 +1041,6 @@ def build_dispatch_table() -> _state.MetaCommandList:
1040
1041
  x_halt,
1041
1042
  )
1042
1043
  for errmsg_delim in (r"\[", r"\#", r"\`", r"\'", r"\~", r'"'):
1043
- close = errmsg_delim.replace("\\", "") if errmsg_delim.startswith("\\") else errmsg_delim
1044
1044
  # Use the same open/close bracket pair for the errmsg capture
1045
1045
  open_c = errmsg_delim if not errmsg_delim.startswith("\\") else errmsg_delim[1:]
1046
1046
  close_c = "]" if open_c == "[" else open_c
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+ from execsql.exceptions import ErrInfo
2
3
 
3
4
  """
4
5
  Conditional test handler functions for execsql.
@@ -15,10 +16,19 @@ at registration time.
15
16
 
16
17
  import os
17
18
  import time
18
- from typing import Any, Callable
19
+ from pathlib import Path
20
+ from typing import Any
21
+ from collections.abc import Callable
19
22
 
20
23
  import execsql.state as _state
21
- from execsql.utils.regex import ins_fn_rxs, ins_rxs
24
+ from execsql.utils.regex import ins_fn_rxs
25
+ from execsql.parser import CondParser
26
+ from execsql.script import MetaCommandList
27
+ from execsql.types import DT_Boolean, DT_Date, DT_Timestamp, DT_TimestampTZ
28
+ from execsql.utils.datetime import parse_datetime
29
+ from execsql.utils.errors import exception_desc
30
+ from execsql.utils.gui import gui_console_isrunning
31
+ from execsql.utils.strings import unquoted
22
32
 
23
33
 
24
34
  def xf_contains(**kwargs: Any) -> bool:
@@ -53,10 +63,10 @@ def xf_hasrows(**kwargs: Any) -> bool:
53
63
  sql = f"select count(*) from {queryname};"
54
64
  try:
55
65
  hdrs, rec = _state.dbs.current().select_data(sql)
56
- except _state.ErrInfo:
66
+ except ErrInfo:
57
67
  raise
58
68
  except Exception:
59
- raise _state.ErrInfo("db", sql, exception_msg=_state.exception_desc())
69
+ raise ErrInfo("db", sql, exception_msg=exception_desc())
60
70
  nrows = rec[0][0]
61
71
  return nrows > 0
62
72
 
@@ -71,12 +81,12 @@ def xf_dialogcanceled(**kwargs: Any) -> bool:
71
81
 
72
82
  def xf_fileexists(**kwargs: Any) -> bool:
73
83
  filename = kwargs["filename"]
74
- return os.path.isfile(filename.strip())
84
+ return Path(filename.strip()).is_file()
75
85
 
76
86
 
77
87
  def xf_direxists(**kwargs: Any) -> bool:
78
88
  dirname = kwargs["dirname"]
79
- return os.path.isdir(dirname.strip())
89
+ return Path(dirname.strip()).is_dir()
80
90
 
81
91
 
82
92
  def xf_schemaexists(**kwargs: Any) -> bool:
@@ -115,7 +125,7 @@ def xf_sub_empty(**kwargs: Any) -> bool:
115
125
  else:
116
126
  subvarset = _state.commandliststack[-1].paramvals
117
127
  if not subvarset.sub_exists(varname):
118
- raise _state.ErrInfo(
128
+ raise ErrInfo(
119
129
  type="cmd",
120
130
  command_text=kwargs["metacommandline"],
121
131
  other_msg=f"Unrecognized substitution variable name: {varname}",
@@ -136,10 +146,10 @@ def xf_equals(**kwargs: Any) -> bool:
136
146
  converters = (
137
147
  int,
138
148
  float,
139
- _state.DT_Timestamp().from_data,
140
- _state.DT_TimestampTZ().from_data,
141
- _state.DT_Date().from_data,
142
- _state.DT_Boolean().from_data,
149
+ DT_Timestamp().from_data,
150
+ DT_TimestampTZ().from_data,
151
+ DT_Date().from_data,
152
+ DT_Boolean().from_data,
143
153
  )
144
154
  for convf in converters:
145
155
  try:
@@ -171,7 +181,7 @@ def xf_iszero(**kwargs: Any) -> bool:
171
181
  try:
172
182
  v = float(val)
173
183
  except Exception:
174
- raise _state.ErrInfo(
184
+ raise ErrInfo(
175
185
  type="cmd",
176
186
  command_text=kwargs["metacommandline"],
177
187
  other_msg=f"The value {{{val}}} is not numeric.",
@@ -186,7 +196,7 @@ def xf_isgt(**kwargs: Any) -> bool:
186
196
  v1 = float(val1)
187
197
  v2 = float(val2)
188
198
  except Exception:
189
- raise _state.ErrInfo(
199
+ raise ErrInfo(
190
200
  type="cmd",
191
201
  command_text=kwargs["metacommandline"],
192
202
  other_msg=f"Values {{{val1}}} and {{{val2}}} are not both numeric.",
@@ -201,7 +211,7 @@ def xf_isgte(**kwargs: Any) -> bool:
201
211
  v1 = float(val1)
202
212
  v2 = float(val2)
203
213
  except Exception:
204
- raise _state.ErrInfo(
214
+ raise ErrInfo(
205
215
  type="cmd",
206
216
  command_text=kwargs["metacommandline"],
207
217
  other_msg=f"Values {{{val1}}} and {{{val2}}} are not both numeric.",
@@ -210,11 +220,11 @@ def xf_isgte(**kwargs: Any) -> bool:
210
220
 
211
221
 
212
222
  def xf_boolliteral(**kwargs: Any) -> bool:
213
- return _state.unquoted(kwargs["value"].strip()).lower() in ("true", "yes", "1")
223
+ return unquoted(kwargs["value"].strip()).lower() in ("true", "yes", "1")
214
224
 
215
225
 
216
226
  def xf_istrue(**kwargs: Any) -> bool:
217
- return _state.unquoted(kwargs["value"].strip()).lower() in ("yes", "y", "true", "t", "1")
227
+ return unquoted(kwargs["value"].strip()).lower() in ("yes", "y", "true", "t", "1")
218
228
 
219
229
 
220
230
  def xf_dbms(**kwargs: Any) -> bool:
@@ -249,33 +259,33 @@ def xf_metacommanderror(**kwargs: Any) -> bool:
249
259
 
250
260
 
251
261
  def xf_console(**kwargs: Any) -> bool:
252
- return _state.gui_console_isrunning()
262
+ return gui_console_isrunning()
253
263
 
254
264
 
255
265
  def xf_newer_file(**kwargs: Any) -> bool:
256
266
  file1 = kwargs["file1"]
257
267
  file2 = kwargs["file2"]
258
- if not os.path.exists(file1):
259
- raise _state.ErrInfo(type="cmd", other_msg=f"File {file1} does not exist.")
260
- if not os.path.exists(file2):
261
- raise _state.ErrInfo(type="cmd", other_msg=f"File {file2} does not exist.")
268
+ if not Path(file1).exists():
269
+ raise ErrInfo(type="cmd", other_msg=f"File {file1} does not exist.")
270
+ if not Path(file2).exists():
271
+ raise ErrInfo(type="cmd", other_msg=f"File {file2} does not exist.")
262
272
  return os.stat(file1).st_mtime > os.stat(file2).st_mtime
263
273
 
264
274
 
265
275
  def xf_newer_date(**kwargs: Any) -> bool:
266
276
  file1 = kwargs["file1"]
267
- datestr = _state.unquoted(kwargs["datestr"])
268
- if not os.path.exists(file1):
269
- raise _state.ErrInfo(type="cmd", other_msg=f"File {file1} does not exist.")
270
- dt_value = _state.parse_datetime(datestr)
277
+ datestr = unquoted(kwargs["datestr"])
278
+ if not Path(file1).exists():
279
+ raise ErrInfo(type="cmd", other_msg=f"File {file1} does not exist.")
280
+ dt_value = parse_datetime(datestr)
271
281
  if not dt_value:
272
- raise _state.ErrInfo(type="cmd", other_msg=f"{datestr} can't be interpreted as a date/time.")
282
+ raise ErrInfo(type="cmd", other_msg=f"{datestr} can't be interpreted as a date/time.")
273
283
  return os.stat(file1).st_mtime > time.mktime(dt_value.timetuple())
274
284
 
275
285
 
276
286
  def build_conditional_table() -> Any:
277
287
  """Construct and return the conditional predicate dispatch table."""
278
- mcl = _state.MetaCommandList()
288
+ mcl = MetaCommandList()
279
289
 
280
290
  # CONTAINS
281
291
  mcl.add(
@@ -607,16 +617,16 @@ CONDITIONAL_TABLE = build_conditional_table()
607
617
 
608
618
 
609
619
  def xcmd_test(teststr: str) -> bool:
610
- result = _state.CondParser(teststr).parse().eval()
620
+ result = CondParser(teststr).parse().eval()
611
621
  if result is not None:
612
622
  return result
613
623
  else:
614
- raise _state.ErrInfo(type="cmd", command_text=teststr, other_msg="Unrecognized conditional")
624
+ raise ErrInfo(type="cmd", command_text=teststr, other_msg="Unrecognized conditional")
615
625
 
616
626
 
617
627
  def file_size_date(filename: str) -> tuple[int, str]:
618
628
  """Returns the file size and date (as string) of the given file."""
619
- s_file = os.path.abspath(filename)
629
+ s_file = str(Path(filename).resolve())
620
630
  f_stat = os.stat(s_file)
621
631
  return f_stat.st_size, time.strftime("%Y-%m-%d %H:%M", time.gmtime(f_stat.st_mtime))
622
632
 
@@ -632,8 +642,6 @@ def chainfuncs(*funcs: Callable) -> Callable:
632
642
 
633
643
 
634
644
  def as_none(item: Any) -> Any:
635
- if isinstance(item, str) and len(item) == 0:
636
- return None
637
- elif isinstance(item, int) and item == 0:
645
+ if isinstance(item, str) and len(item) == 0 or isinstance(item, int) and item == 0:
638
646
  return None
639
647
  return item