sqlinclude 0.1.0__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.
- sqlinclude-0.1.0/LICENSE +21 -0
- sqlinclude-0.1.0/PKG-INFO +145 -0
- sqlinclude-0.1.0/README.md +117 -0
- sqlinclude-0.1.0/pyproject.toml +46 -0
- sqlinclude-0.1.0/setup.cfg +4 -0
- sqlinclude-0.1.0/src/sqlinclude/__init__.py +7 -0
- sqlinclude-0.1.0/src/sqlinclude/__main__.py +8 -0
- sqlinclude-0.1.0/src/sqlinclude/cli.py +226 -0
- sqlinclude-0.1.0/src/sqlinclude/editor.py +179 -0
- sqlinclude-0.1.0/src/sqlinclude/preprocess.py +185 -0
- sqlinclude-0.1.0/src/sqlinclude.egg-info/PKG-INFO +145 -0
- sqlinclude-0.1.0/src/sqlinclude.egg-info/SOURCES.txt +17 -0
- sqlinclude-0.1.0/src/sqlinclude.egg-info/dependency_links.txt +1 -0
- sqlinclude-0.1.0/src/sqlinclude.egg-info/entry_points.txt +2 -0
- sqlinclude-0.1.0/src/sqlinclude.egg-info/requires.txt +4 -0
- sqlinclude-0.1.0/src/sqlinclude.egg-info/top_level.txt +1 -0
- sqlinclude-0.1.0/tests/test_cli.py +123 -0
- sqlinclude-0.1.0/tests/test_editor.py +59 -0
- sqlinclude-0.1.0/tests/test_preprocess.py +107 -0
sqlinclude-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 lukasburski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlinclude
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Tiny SQL source preprocessor: expand @include directives and @define variables.
|
|
5
|
+
Author-email: lukasburski <lukasbursky@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/lukasbursky/sqlinclude
|
|
8
|
+
Project-URL: Source, https://github.com/lukasbursky/sqlinclude
|
|
9
|
+
Project-URL: Issues, https://github.com/lukasbursky/sqlinclude/issues
|
|
10
|
+
Keywords: sql,preprocessor,include,bigquery,templating
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Database
|
|
20
|
+
Classifier: Topic :: Software Development :: Pre-processors
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
26
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# sqlinclude
|
|
30
|
+
|
|
31
|
+
Tiny SQL source preprocessor. Expands `@include` directives and `@define`
|
|
32
|
+
variables recursively. No SQL parsing, no opinion about any database. Intended
|
|
33
|
+
to be piped into a query tool:
|
|
34
|
+
|
|
35
|
+
```console
|
|
36
|
+
sqlinclude analysis.sql | bq query
|
|
37
|
+
bq query "$(sqlinclude analysis.sql)"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Written for Unix and Windows alike: it is a single dependency-free Python
|
|
41
|
+
package and installs a `sqlinclude` console command (a `sqlinclude.exe` on
|
|
42
|
+
Windows).
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```console
|
|
47
|
+
pipx install sqlinclude
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
or, into the current environment:
|
|
51
|
+
|
|
52
|
+
```console
|
|
53
|
+
pip install sqlinclude
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Requires Python 3.12+.
|
|
57
|
+
|
|
58
|
+
## Directives
|
|
59
|
+
|
|
60
|
+
Line-oriented, leading whitespace allowed:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
@include file.sql
|
|
64
|
+
@include "file.sql"
|
|
65
|
+
@define name = value (the `= ` is optional; value is the rest of the
|
|
66
|
+
line, kept verbatim including quotes)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Include paths are resolved relative to the including file. Includes may nest;
|
|
70
|
+
cycles are detected and reported as errors.
|
|
71
|
+
|
|
72
|
+
Variables form a single environment filled in expansion order: the first
|
|
73
|
+
`@define name ...` seen anywhere in the tree wins, and later definitions of the
|
|
74
|
+
same name are ignored (so a parent file's definition always overrides an
|
|
75
|
+
included fragment's default). Definitions from an included file leak back to
|
|
76
|
+
the including file. A `@name` with no definition passes through unchanged --
|
|
77
|
+
BigQuery's native `@param` syntax is never touched.
|
|
78
|
+
|
|
79
|
+
Each include block is bracketed with `-- #line N "file"` markers (with forward
|
|
80
|
+
slashes on every platform) so error messages from downstream tools point at the
|
|
81
|
+
originating source file and line. Use `-n`/`--no-markers` to suppress them.
|
|
82
|
+
|
|
83
|
+
## Example
|
|
84
|
+
|
|
85
|
+
`analysis.sql`:
|
|
86
|
+
|
|
87
|
+
```sql
|
|
88
|
+
@define start = '2024-01-01'
|
|
89
|
+
@define end = '2024-12-31'
|
|
90
|
+
|
|
91
|
+
WITH users AS (
|
|
92
|
+
@include "users.sql"
|
|
93
|
+
),
|
|
94
|
+
|
|
95
|
+
orders AS (
|
|
96
|
+
@include orders.sql
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
SELECT ... WHERE created BETWEEN @start AND @end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Run:
|
|
103
|
+
|
|
104
|
+
```console
|
|
105
|
+
sqlinclude analysis.sql | bq query
|
|
106
|
+
sqlinclude --vars start='2024-06-01' analysis.sql | bq query
|
|
107
|
+
sqlinclude --tree analysis.sql # show the include tree, not SQL
|
|
108
|
+
sqlinclude --tree --ascii analysis.sql # ASCII box characters instead of Unicode
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Options
|
|
112
|
+
|
|
113
|
+
| Option | Description |
|
|
114
|
+
| --- | --- |
|
|
115
|
+
| `file` | SQL file to preprocess (default: read stdin) |
|
|
116
|
+
| `-n`, `--no-markers` | do not emit `-- #line` markers around includes |
|
|
117
|
+
| `--vars name=value` | set a variable (repeatable); wins over `@define` |
|
|
118
|
+
| `-e`, `--edit` | open the editor even when no variable is undefined |
|
|
119
|
+
| `--no-edit` | never open the editor; leave undefined `@name`s as-is |
|
|
120
|
+
| `-t`, `--tree` | print the `@include` dependency tree instead of SQL |
|
|
121
|
+
| `--ascii` | use ASCII box characters in the include tree |
|
|
122
|
+
| `--version` | show the version |
|
|
123
|
+
|
|
124
|
+
## Editing undefined variables
|
|
125
|
+
|
|
126
|
+
When a variable is undefined, `sqlinclude` opens the editor named by
|
|
127
|
+
`$VISUAL`/`$EDITOR` on the controlling terminal, even when its output is piped.
|
|
128
|
+
Edit values, or delete a line to leave that variable undefined. Blank lines and
|
|
129
|
+
`#` comments are ignored. On Windows the same variables are honored, falling
|
|
130
|
+
back to `notepad`. With `-e`/`--edit` the buffer also shows the `@include`
|
|
131
|
+
tree. Undefined variables are always reported on stderr; pass `--no-edit` to
|
|
132
|
+
silence everything and pass undefined names through untouched.
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
```console
|
|
137
|
+
python -m venv .venv
|
|
138
|
+
. .venv/bin/activate
|
|
139
|
+
pip install -e ".[dev]"
|
|
140
|
+
pytest
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT -- see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# sqlinclude
|
|
2
|
+
|
|
3
|
+
Tiny SQL source preprocessor. Expands `@include` directives and `@define`
|
|
4
|
+
variables recursively. No SQL parsing, no opinion about any database. Intended
|
|
5
|
+
to be piped into a query tool:
|
|
6
|
+
|
|
7
|
+
```console
|
|
8
|
+
sqlinclude analysis.sql | bq query
|
|
9
|
+
bq query "$(sqlinclude analysis.sql)"
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Written for Unix and Windows alike: it is a single dependency-free Python
|
|
13
|
+
package and installs a `sqlinclude` console command (a `sqlinclude.exe` on
|
|
14
|
+
Windows).
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```console
|
|
19
|
+
pipx install sqlinclude
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
or, into the current environment:
|
|
23
|
+
|
|
24
|
+
```console
|
|
25
|
+
pip install sqlinclude
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Requires Python 3.12+.
|
|
29
|
+
|
|
30
|
+
## Directives
|
|
31
|
+
|
|
32
|
+
Line-oriented, leading whitespace allowed:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
@include file.sql
|
|
36
|
+
@include "file.sql"
|
|
37
|
+
@define name = value (the `= ` is optional; value is the rest of the
|
|
38
|
+
line, kept verbatim including quotes)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Include paths are resolved relative to the including file. Includes may nest;
|
|
42
|
+
cycles are detected and reported as errors.
|
|
43
|
+
|
|
44
|
+
Variables form a single environment filled in expansion order: the first
|
|
45
|
+
`@define name ...` seen anywhere in the tree wins, and later definitions of the
|
|
46
|
+
same name are ignored (so a parent file's definition always overrides an
|
|
47
|
+
included fragment's default). Definitions from an included file leak back to
|
|
48
|
+
the including file. A `@name` with no definition passes through unchanged --
|
|
49
|
+
BigQuery's native `@param` syntax is never touched.
|
|
50
|
+
|
|
51
|
+
Each include block is bracketed with `-- #line N "file"` markers (with forward
|
|
52
|
+
slashes on every platform) so error messages from downstream tools point at the
|
|
53
|
+
originating source file and line. Use `-n`/`--no-markers` to suppress them.
|
|
54
|
+
|
|
55
|
+
## Example
|
|
56
|
+
|
|
57
|
+
`analysis.sql`:
|
|
58
|
+
|
|
59
|
+
```sql
|
|
60
|
+
@define start = '2024-01-01'
|
|
61
|
+
@define end = '2024-12-31'
|
|
62
|
+
|
|
63
|
+
WITH users AS (
|
|
64
|
+
@include "users.sql"
|
|
65
|
+
),
|
|
66
|
+
|
|
67
|
+
orders AS (
|
|
68
|
+
@include orders.sql
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
SELECT ... WHERE created BETWEEN @start AND @end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Run:
|
|
75
|
+
|
|
76
|
+
```console
|
|
77
|
+
sqlinclude analysis.sql | bq query
|
|
78
|
+
sqlinclude --vars start='2024-06-01' analysis.sql | bq query
|
|
79
|
+
sqlinclude --tree analysis.sql # show the include tree, not SQL
|
|
80
|
+
sqlinclude --tree --ascii analysis.sql # ASCII box characters instead of Unicode
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Options
|
|
84
|
+
|
|
85
|
+
| Option | Description |
|
|
86
|
+
| --- | --- |
|
|
87
|
+
| `file` | SQL file to preprocess (default: read stdin) |
|
|
88
|
+
| `-n`, `--no-markers` | do not emit `-- #line` markers around includes |
|
|
89
|
+
| `--vars name=value` | set a variable (repeatable); wins over `@define` |
|
|
90
|
+
| `-e`, `--edit` | open the editor even when no variable is undefined |
|
|
91
|
+
| `--no-edit` | never open the editor; leave undefined `@name`s as-is |
|
|
92
|
+
| `-t`, `--tree` | print the `@include` dependency tree instead of SQL |
|
|
93
|
+
| `--ascii` | use ASCII box characters in the include tree |
|
|
94
|
+
| `--version` | show the version |
|
|
95
|
+
|
|
96
|
+
## Editing undefined variables
|
|
97
|
+
|
|
98
|
+
When a variable is undefined, `sqlinclude` opens the editor named by
|
|
99
|
+
`$VISUAL`/`$EDITOR` on the controlling terminal, even when its output is piped.
|
|
100
|
+
Edit values, or delete a line to leave that variable undefined. Blank lines and
|
|
101
|
+
`#` comments are ignored. On Windows the same variables are honored, falling
|
|
102
|
+
back to `notepad`. With `-e`/`--edit` the buffer also shows the `@include`
|
|
103
|
+
tree. Undefined variables are always reported on stderr; pass `--no-edit` to
|
|
104
|
+
silence everything and pass undefined names through untouched.
|
|
105
|
+
|
|
106
|
+
## Development
|
|
107
|
+
|
|
108
|
+
```console
|
|
109
|
+
python -m venv .venv
|
|
110
|
+
. .venv/bin/activate
|
|
111
|
+
pip install -e ".[dev]"
|
|
112
|
+
pytest
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT -- see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sqlinclude"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Tiny SQL source preprocessor: expand @include directives and @define variables."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "lukasburski", email = "lukasbursky@gmail.com" }]
|
|
14
|
+
keywords = ["sql", "preprocessor", "include", "bigquery", "templating"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Programming Language :: Python :: 3.14",
|
|
24
|
+
"Topic :: Database",
|
|
25
|
+
"Topic :: Software Development :: Pre-processors",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
sqlinclude = "sqlinclude.cli:main"
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/lukasbursky/sqlinclude"
|
|
33
|
+
Source = "https://github.com/lukasbursky/sqlinclude"
|
|
34
|
+
Issues = "https://github.com/lukasbursky/sqlinclude/issues"
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
dev = ["pytest>=8", "build>=1.2"]
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.dynamic]
|
|
40
|
+
version = { attr = "sqlinclude.__version__" }
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.packages.find]
|
|
43
|
+
where = ["src"]
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""Command-line interface for sqlinclude."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from . import __version__
|
|
11
|
+
from .editor import (
|
|
12
|
+
edit_buffer,
|
|
13
|
+
have_ctrl_tty,
|
|
14
|
+
parse_buffer,
|
|
15
|
+
render_buffer,
|
|
16
|
+
resolve_editor,
|
|
17
|
+
undefined_count,
|
|
18
|
+
)
|
|
19
|
+
from .preprocess import (
|
|
20
|
+
ORIGIN_UNDEFINED,
|
|
21
|
+
STDIN_NAME,
|
|
22
|
+
collect_tree,
|
|
23
|
+
collect_vars,
|
|
24
|
+
render_tree,
|
|
25
|
+
walk,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _stdout_can_encode(text: str) -> bool:
|
|
30
|
+
enc = getattr(sys.stdout, "encoding", None) or "ascii"
|
|
31
|
+
try:
|
|
32
|
+
text.encode(enc)
|
|
33
|
+
except (UnicodeEncodeError, LookupError):
|
|
34
|
+
return False
|
|
35
|
+
return True
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
39
|
+
parser = argparse.ArgumentParser(
|
|
40
|
+
prog="sqlinclude",
|
|
41
|
+
description="Expand @include directives and @define variables in SQL source "
|
|
42
|
+
"(no SQL parsing).",
|
|
43
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
44
|
+
epilog=(
|
|
45
|
+
"example (analysis.sql):\n"
|
|
46
|
+
"\n"
|
|
47
|
+
" @define start = '2024-01-01'\n"
|
|
48
|
+
" @define end = '2024-12-31'\n"
|
|
49
|
+
"\n"
|
|
50
|
+
" WITH users AS (\n"
|
|
51
|
+
' @include "users.sql"\n'
|
|
52
|
+
" ),\n"
|
|
53
|
+
"\n"
|
|
54
|
+
" orders AS (\n"
|
|
55
|
+
" @include orders.sql\n"
|
|
56
|
+
" )\n"
|
|
57
|
+
"\n"
|
|
58
|
+
" SELECT ... WHERE created BETWEEN @start AND @end\n"
|
|
59
|
+
"\n"
|
|
60
|
+
"run:\n"
|
|
61
|
+
"\n"
|
|
62
|
+
" sqlinclude analysis.sql | bq query\n"
|
|
63
|
+
" sqlinclude --vars start='2024-06-01' analysis.sql | bq query\n"
|
|
64
|
+
" sqlinclude --tree analysis.sql # show the include tree, not SQL\n"
|
|
65
|
+
"\n"
|
|
66
|
+
"variables:\n"
|
|
67
|
+
"\n"
|
|
68
|
+
" @define name = value set a variable in a file (first definition wins)\n"
|
|
69
|
+
" --vars name=value set a variable from the command line (repeatable)\n"
|
|
70
|
+
" @name substituted where defined; undefined names pass\n"
|
|
71
|
+
" through unchanged (BigQuery @params stay intact)\n"
|
|
72
|
+
" -e, --edit open the editor even when nothing is undefined\n"
|
|
73
|
+
" --no-edit never open the editor; no stderr notes\n"
|
|
74
|
+
"\n"
|
|
75
|
+
"editing:\n"
|
|
76
|
+
"\n"
|
|
77
|
+
" When a variable is undefined, sqlinclude opens $VISUAL/$EDITOR (or vim,\n"
|
|
78
|
+
" notepad on Windows) on the controlling terminal, even if its output is\n"
|
|
79
|
+
" piped. Edit values; delete a line to leave that variable undefined; blank\n"
|
|
80
|
+
" and # lines are ignored. With -e/--edit the buffer also shows the @include\n"
|
|
81
|
+
" tree. Undefined variables are always reported on stderr -- use\n"
|
|
82
|
+
" --no-edit to silence everything.\n"
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
parser.add_argument(
|
|
86
|
+
"file",
|
|
87
|
+
nargs="?",
|
|
88
|
+
default="-",
|
|
89
|
+
help="SQL file to preprocess (default: read stdin)",
|
|
90
|
+
)
|
|
91
|
+
parser.add_argument(
|
|
92
|
+
"-n",
|
|
93
|
+
"--no-markers",
|
|
94
|
+
action="store_true",
|
|
95
|
+
help="do not emit -- #line markers around includes",
|
|
96
|
+
)
|
|
97
|
+
parser.add_argument(
|
|
98
|
+
"--vars",
|
|
99
|
+
action="append",
|
|
100
|
+
default=[],
|
|
101
|
+
metavar="name=value",
|
|
102
|
+
help="set a variable (repeatable); wins over @define",
|
|
103
|
+
)
|
|
104
|
+
parser.add_argument(
|
|
105
|
+
"-e",
|
|
106
|
+
"--edit",
|
|
107
|
+
action="store_true",
|
|
108
|
+
help="open the editor even when no variable is undefined",
|
|
109
|
+
)
|
|
110
|
+
parser.add_argument(
|
|
111
|
+
"--no-edit",
|
|
112
|
+
action="store_true",
|
|
113
|
+
help="never open the editor; leave undefined @names as-is (no stderr notes)",
|
|
114
|
+
)
|
|
115
|
+
parser.add_argument(
|
|
116
|
+
"-t",
|
|
117
|
+
"--tree",
|
|
118
|
+
action="store_true",
|
|
119
|
+
help="print the @include dependency tree as ASCII on stdout instead of expanding SQL",
|
|
120
|
+
)
|
|
121
|
+
parser.add_argument(
|
|
122
|
+
"--ascii",
|
|
123
|
+
action="store_true",
|
|
124
|
+
help="use ASCII (not Unicode) box characters in the include tree",
|
|
125
|
+
)
|
|
126
|
+
parser.add_argument(
|
|
127
|
+
"--version",
|
|
128
|
+
action="version",
|
|
129
|
+
version=f"%(prog)s {__version__}",
|
|
130
|
+
)
|
|
131
|
+
return parser
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def main(argv: list[str] | None = None) -> int:
|
|
135
|
+
if argv is None:
|
|
136
|
+
argv = sys.argv
|
|
137
|
+
args = build_parser().parse_args(argv[1:])
|
|
138
|
+
|
|
139
|
+
cli_vars: dict[str, str] = {}
|
|
140
|
+
for pair in args.vars:
|
|
141
|
+
if "=" not in pair:
|
|
142
|
+
sys.stderr.write(f"sqlinclude: --vars expects name=value, got {pair!r}\n")
|
|
143
|
+
return 2
|
|
144
|
+
name, value = pair.split("=", 1)
|
|
145
|
+
if not re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", name):
|
|
146
|
+
sys.stderr.write(f"sqlinclude: invalid variable name in --vars: {name!r}\n")
|
|
147
|
+
return 2
|
|
148
|
+
cli_vars[name] = value
|
|
149
|
+
|
|
150
|
+
if args.file == "-":
|
|
151
|
+
source = sys.stdin.read()
|
|
152
|
+
top = None
|
|
153
|
+
else:
|
|
154
|
+
try:
|
|
155
|
+
with open(args.file, encoding="utf-8") as fh:
|
|
156
|
+
source = fh.read()
|
|
157
|
+
except OSError as exc:
|
|
158
|
+
sys.stderr.write(f"sqlinclude: {args.file}: {exc}\n")
|
|
159
|
+
return 1
|
|
160
|
+
top = args.file
|
|
161
|
+
if source and not source.endswith("\n"):
|
|
162
|
+
source += "\n"
|
|
163
|
+
|
|
164
|
+
top_disp = (
|
|
165
|
+
os.path.relpath(os.path.realpath(top), os.getcwd()).replace("\\", "/")
|
|
166
|
+
if top
|
|
167
|
+
else STDIN_NAME
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if args.tree:
|
|
171
|
+
root = collect_tree(source, top)
|
|
172
|
+
if root is None:
|
|
173
|
+
return 1
|
|
174
|
+
ascii_only = args.ascii or not _stdout_can_encode("└──")
|
|
175
|
+
sys.stdout.write(render_tree(root, ascii_only=ascii_only))
|
|
176
|
+
return 0
|
|
177
|
+
|
|
178
|
+
collected = collect_vars(source, top, cli_vars)
|
|
179
|
+
if collected is None:
|
|
180
|
+
return 1
|
|
181
|
+
order, values, sources = collected
|
|
182
|
+
undefined_present = any(values[n] is None for n in order)
|
|
183
|
+
|
|
184
|
+
if (
|
|
185
|
+
top is not None
|
|
186
|
+
and not args.no_edit
|
|
187
|
+
and (undefined_present or args.edit)
|
|
188
|
+
and have_ctrl_tty()
|
|
189
|
+
):
|
|
190
|
+
editor = resolve_editor()
|
|
191
|
+
if undefined_present:
|
|
192
|
+
sys.stderr.write(
|
|
193
|
+
f"sqlinclude: opening {editor} for {undefined_count(order, values)} "
|
|
194
|
+
"undefined variable(s)\n"
|
|
195
|
+
)
|
|
196
|
+
tree = None
|
|
197
|
+
if args.edit:
|
|
198
|
+
tree = collect_tree(source, top)
|
|
199
|
+
if tree is None:
|
|
200
|
+
return 1
|
|
201
|
+
edited = edit_buffer(render_buffer(top_disp, order, values, sources, tree), editor)
|
|
202
|
+
if edited is None:
|
|
203
|
+
return 1
|
|
204
|
+
env = parse_buffer(edited)
|
|
205
|
+
blocked = set(order) - set(env)
|
|
206
|
+
else:
|
|
207
|
+
env = dict(cli_vars)
|
|
208
|
+
blocked = set()
|
|
209
|
+
if not args.no_edit and undefined_present:
|
|
210
|
+
names = [n for n in order if values[n] is None]
|
|
211
|
+
sys.stderr.write(
|
|
212
|
+
"sqlinclude: undefined variables "
|
|
213
|
+
f"({len(names)}): {', '.join('@' + n for n in names)} "
|
|
214
|
+
"-- left as @name\n"
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
out: list[str] = []
|
|
218
|
+
stack: list[tuple[str, str]] = []
|
|
219
|
+
if walk(source, top, out, stack, args.no_markers, env, blocked):
|
|
220
|
+
return 1
|
|
221
|
+
sys.stdout.write("".join(out))
|
|
222
|
+
return 0
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
if __name__ == "__main__":
|
|
226
|
+
sys.exit(main())
|