docker-stack 0.2.4__tar.gz → 0.2.5__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.
- {docker-stack-0.2.4 → docker-stack-0.2.5}/PKG-INFO +2 -2
- {docker-stack-0.2.4 → docker-stack-0.2.5}/docker_stack/cli.py +17 -8
- docker-stack-0.2.5/docker_stack/envsubst.py +206 -0
- {docker-stack-0.2.4 → docker-stack-0.2.5}/docker_stack/envsubst_merge.py +17 -4
- {docker-stack-0.2.4 → docker-stack-0.2.5}/docker_stack.egg-info/PKG-INFO +2 -2
- {docker-stack-0.2.4 → docker-stack-0.2.5}/pyproject.toml +3 -0
- {docker-stack-0.2.4 → docker-stack-0.2.5}/setup.py +2 -2
- docker-stack-0.2.4/docker_stack/envsubst.py +0 -109
- {docker-stack-0.2.4 → docker-stack-0.2.5}/README.md +0 -0
- {docker-stack-0.2.4 → docker-stack-0.2.5}/docker_stack/__init__.py +0 -0
- {docker-stack-0.2.4 → docker-stack-0.2.5}/docker_stack/compose.py +0 -0
- {docker-stack-0.2.4 → docker-stack-0.2.5}/docker_stack/docker_objects.py +0 -0
- {docker-stack-0.2.4 → docker-stack-0.2.5}/docker_stack/helpers.py +0 -0
- {docker-stack-0.2.4 → docker-stack-0.2.5}/docker_stack/merge_conf.py +0 -0
- {docker-stack-0.2.4 → docker-stack-0.2.5}/docker_stack/registry.py +0 -0
- {docker-stack-0.2.4 → docker-stack-0.2.5}/docker_stack/url_parser.py +0 -0
- {docker-stack-0.2.4 → docker-stack-0.2.5}/docker_stack.egg-info/SOURCES.txt +0 -0
- {docker-stack-0.2.4 → docker-stack-0.2.5}/docker_stack.egg-info/dependency_links.txt +0 -0
- {docker-stack-0.2.4 → docker-stack-0.2.5}/docker_stack.egg-info/entry_points.txt +0 -0
- {docker-stack-0.2.4 → docker-stack-0.2.5}/docker_stack.egg-info/requires.txt +0 -0
- {docker-stack-0.2.4 → docker-stack-0.2.5}/docker_stack.egg-info/top_level.txt +0 -0
- {docker-stack-0.2.4 → docker-stack-0.2.5}/setup.cfg +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: docker-stack
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: CLI for deploying and managing Docker stacks.
|
|
5
|
-
Home-page: https://github.com/
|
|
5
|
+
Home-page: https://github.com/mesudip/docker-stack
|
|
6
6
|
Author: Sudip Bhattarai
|
|
7
7
|
Author-email: sudip@bhattarai.me
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -77,7 +77,10 @@ class DockerStack:
|
|
|
77
77
|
compose_data["configs"] = self._process_x_content(compose_data["configs"], self.docker.config,base_dir=base_dir,stack=stack)
|
|
78
78
|
if "secrets" in compose_data:
|
|
79
79
|
compose_data["secrets"] = self._process_x_content(compose_data["secrets"], self.docker.secret,base_dir=base_dir,stack=stack)
|
|
80
|
-
|
|
80
|
+
|
|
81
|
+
# Define the replacements for '$' to '$$' for env variables in compose files
|
|
82
|
+
replacements_map = {"$": "$$"}
|
|
83
|
+
return envsubst(yaml.dump(compose_data), replacements=replacements_map)
|
|
81
84
|
|
|
82
85
|
def decode_yaml(self,data:str)->dict:
|
|
83
86
|
return yaml.safe_load(data)
|
|
@@ -94,10 +97,6 @@ class DockerStack:
|
|
|
94
97
|
rendered_filename = Path(compose_file).with_name(
|
|
95
98
|
f"{Path(compose_file).stem}-rendered{Path(compose_file).suffix}"
|
|
96
99
|
)
|
|
97
|
-
with open(rendered_filename, "w") as f:
|
|
98
|
-
f.write(rendered_content)
|
|
99
|
-
with open(rendered_filename.as_posix()+".json","w") as f:
|
|
100
|
-
f.write(json.dumps(rendered_content,indent=2))
|
|
101
100
|
return (rendered_filename,rendered_content)
|
|
102
101
|
|
|
103
102
|
|
|
@@ -366,9 +365,9 @@ def main(args:List[str]=None):
|
|
|
366
365
|
# Ls command
|
|
367
366
|
subparsers.add_parser("ls",help="List docker-stacks")
|
|
368
367
|
|
|
369
|
-
cat_parser = subparsers.add_parser("cat",help="Print the docker compose of specific version")
|
|
368
|
+
cat_parser = subparsers.add_parser("cat",help="Print the docker compose of specific version. Defaults to latest version if not specified.")
|
|
370
369
|
cat_parser.add_argument("stack_name", help="Name of the stack")
|
|
371
|
-
cat_parser.add_argument("version", help="Stack version to cat")
|
|
370
|
+
cat_parser.add_argument("version", nargs='?', help="Stack version to cat. Defaults to latest if omitted.")
|
|
372
371
|
|
|
373
372
|
checkout_parser = subparsers.add_parser("checkout",help="Deploy specific version of the stack")
|
|
374
373
|
checkout_parser.add_argument("stack_name", help="Name of the stack")
|
|
@@ -403,7 +402,17 @@ def main(args:List[str]=None):
|
|
|
403
402
|
elif args.command == "rm":
|
|
404
403
|
docker.stack.rm(args.stack_name)
|
|
405
404
|
elif args.command == 'cat':
|
|
406
|
-
|
|
405
|
+
version_to_cat = args.version
|
|
406
|
+
if version_to_cat is None:
|
|
407
|
+
versions_list = docker.stack.versions(args.stack_name)
|
|
408
|
+
if versions_list:
|
|
409
|
+
# Assuming versions are integers, find the maximum
|
|
410
|
+
latest_version = max(int(v[0]) for v in versions_list if v[0].isdigit())
|
|
411
|
+
version_to_cat = str(latest_version)
|
|
412
|
+
else:
|
|
413
|
+
print(f"No versions found for stack '{args.stack_name}'.")
|
|
414
|
+
sys.exit(1)
|
|
415
|
+
print(docker.stack.cat(args.stack_name, version_to_cat))
|
|
407
416
|
elif args.command == 'checkout':
|
|
408
417
|
docker.stack.checkout(args.stack_name,args.version)
|
|
409
418
|
elif args.command == 'versions' or args.command == "version":
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
"""
|
|
3
|
+
NAME
|
|
4
|
+
envsubst.py - substitutes environment variables in bash format strings
|
|
5
|
+
|
|
6
|
+
DESCRIPTION
|
|
7
|
+
envsubst.py is an upgrade of the POSIX command `envsubst`
|
|
8
|
+
|
|
9
|
+
supported syntax:
|
|
10
|
+
normal - ${VARIABLE1} or $VARIABLE1
|
|
11
|
+
with default - ${VARIABLE1:-somevalue}
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
from typing import Dict, List, Literal, Optional
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class LineCheckResult:
|
|
23
|
+
line_no: int
|
|
24
|
+
line_content: str
|
|
25
|
+
variable_name: Optional[str] = None # None means no error
|
|
26
|
+
start_index: Optional[int] = None # Start index of the variable in the line_content
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def has_error(self) -> bool:
|
|
30
|
+
return self.variable_name is not None
|
|
31
|
+
|
|
32
|
+
def __str__(self):
|
|
33
|
+
if self.has_error:
|
|
34
|
+
return (f"ERROR :: Missing variable: '{self.variable_name}' "
|
|
35
|
+
f"on line {self.line_no}: '{self.line_content}'")
|
|
36
|
+
return f"OK :: Line {self.line_no}: '{self.line_content}'"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SubstitutionError(Exception):
|
|
40
|
+
"""Custom exception to collect multiple substitution errors with detailed information."""
|
|
41
|
+
def __init__(self, results: List[LineCheckResult], template_str: str, underline_char: str = '\u0333'):
|
|
42
|
+
self.results = results
|
|
43
|
+
self.template_lines = template_str.splitlines(keepends=False)
|
|
44
|
+
self.underline_char = underline_char
|
|
45
|
+
super().__init__(self._format_messages(results))
|
|
46
|
+
|
|
47
|
+
def _format_messages(self, results: List[LineCheckResult]) -> str:
|
|
48
|
+
formatted_messages = []
|
|
49
|
+
|
|
50
|
+
# Group results by line number
|
|
51
|
+
errors_by_line = {}
|
|
52
|
+
for result in results:
|
|
53
|
+
if result.has_error:
|
|
54
|
+
if result.line_no not in errors_by_line:
|
|
55
|
+
errors_by_line[result.line_no] = {
|
|
56
|
+
'line_content': result.line_content,
|
|
57
|
+
'variables': []
|
|
58
|
+
}
|
|
59
|
+
errors_by_line[result.line_no]['variables'].append({
|
|
60
|
+
'name': result.variable_name,
|
|
61
|
+
'start_index': result.start_index
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
# Sort line numbers
|
|
65
|
+
sorted_line_nos = sorted(errors_by_line.keys())
|
|
66
|
+
|
|
67
|
+
last_printed_line = 0
|
|
68
|
+
|
|
69
|
+
for line_no in sorted_line_nos:
|
|
70
|
+
# Add separator if there's a gap from the last context block
|
|
71
|
+
if last_printed_line > 0 and (line_no - 2) > (last_printed_line + 1):
|
|
72
|
+
formatted_messages.append("")
|
|
73
|
+
|
|
74
|
+
# Determine context lines to display
|
|
75
|
+
start_context_line = max(last_printed_line + 1, line_no - 2)
|
|
76
|
+
end_context_line = min(len(self.template_lines), line_no + 2)
|
|
77
|
+
|
|
78
|
+
for current_ln in range(start_context_line, end_context_line + 1):
|
|
79
|
+
line_text = self.template_lines[current_ln - 1]
|
|
80
|
+
|
|
81
|
+
# If this is an error line, apply the underlining
|
|
82
|
+
if current_ln in errors_by_line:
|
|
83
|
+
current_line_errors = errors_by_line[current_ln]['variables']
|
|
84
|
+
|
|
85
|
+
# Sort errors by start_index in reverse to avoid index shifting issues
|
|
86
|
+
current_line_errors.sort(key=lambda x: x['start_index'], reverse=True)
|
|
87
|
+
|
|
88
|
+
modified_line_text_chars = list(line_text) # Convert to list for mutability
|
|
89
|
+
|
|
90
|
+
for var_info in current_line_errors:
|
|
91
|
+
var_name = var_info['name']
|
|
92
|
+
start_idx = var_info['start_index']
|
|
93
|
+
|
|
94
|
+
# Insert underline_char after each character of the variable name
|
|
95
|
+
for k in range(len(var_name) - 1, -1, -1):
|
|
96
|
+
insert_pos = start_idx + k + 1 # Position after the character
|
|
97
|
+
modified_line_text_chars.insert(insert_pos, self.underline_char)
|
|
98
|
+
|
|
99
|
+
line_text = "".join(modified_line_text_chars)
|
|
100
|
+
|
|
101
|
+
formatted_messages.append(f"{current_ln:3d} {line_text}")
|
|
102
|
+
|
|
103
|
+
last_printed_line = end_context_line
|
|
104
|
+
|
|
105
|
+
return "\n".join(formatted_messages)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def envsubst(template_str, env=os.environ, replacements: Dict[str, str] = None, on_error: Literal['exit','throw'] = 'exit'):
|
|
110
|
+
"""Substitute environment variables in the template string, supporting default values."""
|
|
111
|
+
|
|
112
|
+
# Combined regex for ${VAR:-default} and $VAR, and also $$
|
|
113
|
+
pattern = re.compile(r"\$\_ESCAPED_DOLLAR_|\$\{([^}:\s]+)(?::-(.*?))?\}|\$([a-zA-Z_][a-zA-Z0-9_]*)")
|
|
114
|
+
|
|
115
|
+
# Handle escaped dollars
|
|
116
|
+
template_str = template_str.replace("$$", "$_ESCAPED_DOLLAR_")
|
|
117
|
+
|
|
118
|
+
lines = template_str.splitlines(True) # keepends=True
|
|
119
|
+
processed_lines = []
|
|
120
|
+
error_results: List[LineCheckResult] = []
|
|
121
|
+
|
|
122
|
+
for i, original_line in enumerate(lines):
|
|
123
|
+
line_no = i + 1
|
|
124
|
+
|
|
125
|
+
line_errors_raw = [] # Store (var_name, start_index) tuples
|
|
126
|
+
|
|
127
|
+
def replacer(match: re.Match[str]):
|
|
128
|
+
if match.group(0) == '$_ESCAPED_DOLLAR_':
|
|
129
|
+
return '$$'
|
|
130
|
+
# Group 1, 2 for ${VAR:-default}
|
|
131
|
+
if match.group(1) is not None:
|
|
132
|
+
var = match.group(1)
|
|
133
|
+
default_value = match.group(2) if match.group(2) is not None else None
|
|
134
|
+
result = env.get(var, default_value)
|
|
135
|
+
if result is None:
|
|
136
|
+
line_errors_raw.append((var, match.start(1))) # Use match.start(1) for ${VAR}
|
|
137
|
+
return match.group(0) # Keep original if variable not found
|
|
138
|
+
# Group 3 for $VAR
|
|
139
|
+
else:
|
|
140
|
+
var = match.group(3)
|
|
141
|
+
result = env.get(var, None)
|
|
142
|
+
if result is None:
|
|
143
|
+
line_errors_raw.append((var, match.start(3))) # Use match.start(3) for $VAR
|
|
144
|
+
return match.group(0) # Keep original if variable not found
|
|
145
|
+
|
|
146
|
+
if replacements:
|
|
147
|
+
for old, new in replacements.items():
|
|
148
|
+
result = result.replace(old, new)
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
processed_line = pattern.sub(replacer, original_line)
|
|
152
|
+
|
|
153
|
+
processed_lines.append(processed_line)
|
|
154
|
+
|
|
155
|
+
if line_errors_raw:
|
|
156
|
+
# The original line content for error reporting should not have the escaped dollar placeholder.
|
|
157
|
+
# It should also retain its leading/trailing whitespace for accurate caret positioning.
|
|
158
|
+
error_line_content_for_report = original_line.replace("$_ESCAPED_DOLLAR_", "$$").rstrip('\n') # Remove only trailing newline
|
|
159
|
+
|
|
160
|
+
# Use a set of tuples to store unique (var_name, start_index) pairs for this line
|
|
161
|
+
unique_errors_on_line = set()
|
|
162
|
+
for var_name, start_index in line_errors_raw:
|
|
163
|
+
unique_errors_on_line.add((var_name, start_index))
|
|
164
|
+
|
|
165
|
+
for var_name, start_index in sorted(list(unique_errors_on_line), key=lambda x: x[1]): # Sort by start_index
|
|
166
|
+
error_results.append(LineCheckResult(line_no=line_no, line_content=error_line_content_for_report, variable_name=var_name, start_index=start_index))
|
|
167
|
+
|
|
168
|
+
if error_results:
|
|
169
|
+
# Sort errors by line number, then by start_index
|
|
170
|
+
error_results.sort(key=lambda x: (x.line_no, x.start_index))
|
|
171
|
+
if on_error == 'exit':
|
|
172
|
+
error_output = SubstitutionError(error_results, template_str)._format_messages(error_results) # Pass template_str
|
|
173
|
+
print(error_output, file=sys.stderr)
|
|
174
|
+
exit(1)
|
|
175
|
+
elif on_error == 'throw':
|
|
176
|
+
raise SubstitutionError(error_results, template_str) # Pass template_str
|
|
177
|
+
|
|
178
|
+
result_str = "".join(processed_lines)
|
|
179
|
+
# Restore escaped dollars
|
|
180
|
+
result_str = result_str.replace("$_ESCAPED_DOLLAR_", "$$")
|
|
181
|
+
|
|
182
|
+
return result_str
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def envsubst_load_file(template_file, env=os.environ, replacements: Dict[str, str] = None, on_error: str = 'exit'):
|
|
186
|
+
with open(template_file) as file:
|
|
187
|
+
return envsubst(file.read(), env, replacements, on_error)
|
|
188
|
+
|
|
189
|
+
def main():
|
|
190
|
+
if len(sys.argv) > 2:
|
|
191
|
+
print("Usage: python envsubst.py [template_file]")
|
|
192
|
+
sys.exit(1)
|
|
193
|
+
|
|
194
|
+
if len(sys.argv) == 2:
|
|
195
|
+
template_file = sys.argv[1]
|
|
196
|
+
with open(template_file, "r") as file:
|
|
197
|
+
template_str = file.read()
|
|
198
|
+
else:
|
|
199
|
+
template_str = sys.stdin.read()
|
|
200
|
+
|
|
201
|
+
result = envsubst(template_str)
|
|
202
|
+
print(result)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
if __name__ == "__main__":
|
|
206
|
+
main()
|
|
@@ -11,9 +11,11 @@ DESCRIPTION
|
|
|
11
11
|
import os
|
|
12
12
|
import re
|
|
13
13
|
import sys
|
|
14
|
+
from typing import Dict
|
|
15
|
+
from .envsubst import SubstitutionError, envsubst as base_envsubst
|
|
14
16
|
|
|
15
17
|
|
|
16
|
-
def envsubst(template_str, env=os.environ):
|
|
18
|
+
def envsubst(template_str, env=os.environ, replacements: Dict[str, str] = None, on_error: str = 'exit'):
|
|
17
19
|
"""Substitute environment variables in the template string, supporting default values."""
|
|
18
20
|
|
|
19
21
|
# Regex for ${VARIABLE} with optional default
|
|
@@ -29,6 +31,10 @@ def envsubst(template_str, env=os.environ):
|
|
|
29
31
|
if result is None:
|
|
30
32
|
print(f"Missing template variable with default: {var}", file=sys.stderr)
|
|
31
33
|
exit(1)
|
|
34
|
+
|
|
35
|
+
if replacements:
|
|
36
|
+
for old, new in replacements.items():
|
|
37
|
+
result = result.replace(old, new)
|
|
32
38
|
return result
|
|
33
39
|
|
|
34
40
|
def replace_without_default(match):
|
|
@@ -37,6 +43,10 @@ def envsubst(template_str, env=os.environ):
|
|
|
37
43
|
if result is None:
|
|
38
44
|
print(f"Missing template variable: {var}", file=sys.stderr)
|
|
39
45
|
exit(1)
|
|
46
|
+
|
|
47
|
+
if replacements:
|
|
48
|
+
for old, new in replacements.items():
|
|
49
|
+
result = result.replace(old, new)
|
|
40
50
|
return result
|
|
41
51
|
|
|
42
52
|
# Substitute variables with default values
|
|
@@ -48,7 +58,7 @@ def envsubst(template_str, env=os.environ):
|
|
|
48
58
|
return template_str
|
|
49
59
|
|
|
50
60
|
|
|
51
|
-
def merge_files_from_directories(directories, file_extension=None):
|
|
61
|
+
def merge_files_from_directories(directories, file_extension=None, on_error: str = 'exit'):
|
|
52
62
|
merged_content = []
|
|
53
63
|
|
|
54
64
|
for path in directories:
|
|
@@ -85,8 +95,11 @@ def merge_files_from_directories(directories, file_extension=None):
|
|
|
85
95
|
# Strip extra empty lines from the beginning and end
|
|
86
96
|
result = result.strip()
|
|
87
97
|
|
|
88
|
-
#
|
|
89
|
-
|
|
98
|
+
# Define the replacements for '$' to '$$'
|
|
99
|
+
replacements_map = {"$": "$$"}
|
|
100
|
+
|
|
101
|
+
# Perform environment variable substitution on the final result with replacements
|
|
102
|
+
return base_envsubst(result, replacements=replacements_map, on_error=on_error)
|
|
90
103
|
|
|
91
104
|
|
|
92
105
|
def main():
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: docker-stack
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: CLI for deploying and managing Docker stacks.
|
|
5
|
-
Home-page: https://github.com/
|
|
5
|
+
Home-page: https://github.com/mesudip/docker-stack
|
|
6
6
|
Author: Sudip Bhattarai
|
|
7
7
|
Author-email: sudip@bhattarai.me
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -2,13 +2,13 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="docker-stack",
|
|
5
|
-
version="0.2.
|
|
5
|
+
version="0.2.5",
|
|
6
6
|
description="CLI for deploying and managing Docker stacks.",
|
|
7
7
|
long_description=open("README.md").read(), # You can include a README file to describe your package
|
|
8
8
|
long_description_content_type="text/markdown",
|
|
9
9
|
author="Sudip Bhattarai",
|
|
10
10
|
author_email="sudip@bhattarai.me",
|
|
11
|
-
url="https://github.com/
|
|
11
|
+
url="https://github.com/mesudip/docker-stack", # Replace with your project URL
|
|
12
12
|
packages=find_packages(),
|
|
13
13
|
install_requires=[
|
|
14
14
|
"PyYAML"
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/python3
|
|
2
|
-
"""
|
|
3
|
-
NAME
|
|
4
|
-
envsubst.py - substitutes environment variables in bash format strings
|
|
5
|
-
|
|
6
|
-
DESCRIPTION
|
|
7
|
-
envsubst.py is an upgrade of the POSIX command `envsubst`
|
|
8
|
-
|
|
9
|
-
supported syntax:
|
|
10
|
-
normal - ${VARIABLE1} or $VARIABLE1
|
|
11
|
-
with default - ${VARIABLE1:-somevalue}
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
import os
|
|
15
|
-
import re
|
|
16
|
-
import sys
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def envsubst(template_str, env=os.environ):
|
|
20
|
-
"""Substitute environment variables in the template string, supporting default values."""
|
|
21
|
-
|
|
22
|
-
# Regex for ${VARIABLE} with optional default
|
|
23
|
-
pattern_with_default = re.compile(r"\$\{([^}:\s]+)(?::-(.*?))?\}")
|
|
24
|
-
|
|
25
|
-
# Regex for $VARIABLE without default
|
|
26
|
-
pattern_without_default = re.compile(r"\$([a-zA-Z_][a-zA-Z0-9_]*)")
|
|
27
|
-
|
|
28
|
-
template_str = template_str.replace("$$", "__ESCAPED_DOLLAR__")
|
|
29
|
-
def print_error_line(template_str, match_span):
|
|
30
|
-
"""Helper function to print the error context."""
|
|
31
|
-
lines = template_str.splitlines()
|
|
32
|
-
|
|
33
|
-
# Determine the start position and line
|
|
34
|
-
start_pos = match_span[0]
|
|
35
|
-
end_pos = match_span[1]
|
|
36
|
-
|
|
37
|
-
# Calculate line numbers based on character positions
|
|
38
|
-
char_count = 0
|
|
39
|
-
start_line = end_line = None
|
|
40
|
-
for i, line in enumerate(lines):
|
|
41
|
-
char_count += len(line) + 1 # +1 for the newline character
|
|
42
|
-
if start_line is None and char_count > start_pos:
|
|
43
|
-
start_line = i
|
|
44
|
-
if char_count >= end_pos:
|
|
45
|
-
end_line = i
|
|
46
|
-
break
|
|
47
|
-
|
|
48
|
-
# Display lines before, the error line, and after (with line numbers)
|
|
49
|
-
start = max(start_line - 1, 0)
|
|
50
|
-
end = min(end_line + 1, len(lines) - 1)
|
|
51
|
-
|
|
52
|
-
for i in range(start, end + 1):
|
|
53
|
-
print(f"{i + 1}: {lines[i]}",file=sys.stderr)
|
|
54
|
-
|
|
55
|
-
def replace_with_default(match: re.Match[str]):
|
|
56
|
-
var = match.group(1)
|
|
57
|
-
default_value = match.group(2) if match.group(2) is not None else None
|
|
58
|
-
result = env.get(var, default_value)
|
|
59
|
-
if result is None:
|
|
60
|
-
print_error_line(template_str, match.span())
|
|
61
|
-
print(f"ERROR :: Missing template variable with default: {var}", file=sys.stderr)
|
|
62
|
-
|
|
63
|
-
exit(1)
|
|
64
|
-
return result
|
|
65
|
-
|
|
66
|
-
def replace_without_default(match: re.Match[str]):
|
|
67
|
-
var = match.group(1)
|
|
68
|
-
result = env.get(var, None)
|
|
69
|
-
if result is None:
|
|
70
|
-
print_error_line(template_str, match.span())
|
|
71
|
-
print(f"ERROR :: Missing template variable: {var}", file=sys.stderr)
|
|
72
|
-
exit(1)
|
|
73
|
-
return result
|
|
74
|
-
|
|
75
|
-
# Substitute variables with default values
|
|
76
|
-
template_str = pattern_with_default.sub(replace_with_default, template_str)
|
|
77
|
-
|
|
78
|
-
# Substitute variables without default values
|
|
79
|
-
template_str = pattern_without_default.sub(replace_without_default, template_str)
|
|
80
|
-
|
|
81
|
-
template_str = template_str.replace("__ESCAPED_DOLLAR__", "$")
|
|
82
|
-
|
|
83
|
-
return template_str
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def envsubst_load_file(template_file,env=os.environ):
|
|
87
|
-
with open(template_file) as file:
|
|
88
|
-
return envsubst(file.read(),env)
|
|
89
|
-
|
|
90
|
-
def main():
|
|
91
|
-
if len(sys.argv) > 2:
|
|
92
|
-
print("Usage: python envsubst.py [template_file]")
|
|
93
|
-
sys.exit(1)
|
|
94
|
-
|
|
95
|
-
if len(sys.argv) == 2:
|
|
96
|
-
template_file = sys.argv[1]
|
|
97
|
-
with open(template_file, "r") as file:
|
|
98
|
-
template_str = file.read()
|
|
99
|
-
else:
|
|
100
|
-
template_str = sys.stdin.read()
|
|
101
|
-
|
|
102
|
-
result = envsubst(template_str)
|
|
103
|
-
|
|
104
|
-
print(result)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if __name__ == "__main__":
|
|
108
|
-
main()
|
|
109
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|