|
1 | | -#!/usr/bin/python3 |
2 | | - |
3 | | -import argparse |
4 | | -import os |
5 | | -import pathlib |
6 | | -import re |
7 | | -import subprocess |
8 | | -import sys |
9 | | - |
10 | | - |
11 | | -BINDIR = pathlib.Path(__file__).parent |
12 | | -FEATURES = [ |
13 | | - 'mongo', |
14 | | - 'postgresql', |
15 | | - 'grpc', |
16 | | -] |
17 | | -TEMPLATE_ON_REGEX = re.compile(r'.* ([a-z]+) template on.*') |
18 | | -TEMPLATE_CURRENT_REGEX = re.compile(r'(.*) (//|#) ([a-z]+) template current$') |
19 | | - |
20 | | - |
21 | | -def is_system_installed() -> bool: |
22 | | - return BINDIR.name == 'bin' |
23 | | - |
24 | | - |
25 | | -def template_path() -> pathlib.Path: |
26 | | - if is_system_installed(): |
27 | | - return BINDIR / '..' / 'share' / 'userver' / 'service_template' |
28 | | - return BINDIR / '..' / 'service_template' |
29 | | - |
30 | | - |
31 | | -def version_file_path() -> pathlib.Path: |
32 | | - if is_system_installed(): |
33 | | - return BINDIR / '..' / 'share' / 'userver' / 'version.txt' |
34 | | - return BINDIR / '..' / 'version.txt' |
35 | | - |
36 | | - |
37 | | -def get_userver_version() -> str: |
38 | | - version_path = version_file_path() |
39 | | - return version_path.read_text().strip() |
40 | | - |
41 | | - |
42 | | -def get_devcontainer_version(userver_version: str) -> str: |
43 | | - if userver_version.endswith('-rc'): |
44 | | - return 'latest' |
45 | | - return f'v{userver_version}' |
46 | | - |
47 | | - |
48 | | -def patch_devcontainer_version(service_path: pathlib.Path) -> None: |
49 | | - devcontainer_path = service_path / '.devcontainer' / 'devcontainer.json' |
50 | | - |
51 | | - userver_version = get_userver_version() |
52 | | - devcontainer_version = get_devcontainer_version(userver_version) |
53 | | - |
54 | | - content = devcontainer_path.read_text() |
55 | | - content = re.sub(r'"(ghcr\.io/[^"]+)"', rf'"\1:{devcontainer_version}"', content) |
56 | | - devcontainer_path.write_text(content) |
57 | | - |
58 | | - |
59 | | -def parse_args(): |
60 | | - parser = argparse.ArgumentParser( |
61 | | - description='Create new C++ userver-based service') |
62 | | - for feature in FEATURES: |
63 | | - parser.add_argument(f'--{feature}', action='store_true') |
64 | | - parser.add_argument('service_path', type=pathlib.Path) |
65 | | - return parser.parse_args() |
66 | | - |
67 | | - |
68 | | -def service_name(service_path: pathlib.Path) -> str: |
69 | | - return service_path.resolve().name.replace('-', '_') |
70 | | - |
71 | | - |
72 | | -def handle_file(src: pathlib.Path, dst: pathlib.Path, args) -> bool: |
73 | | - orig_text = src.read_text() |
74 | | - text = [] |
75 | | - skip = False |
76 | | - for line in orig_text.splitlines(): |
77 | | - line = line.replace('service_template', service_name(args.service_path)) |
78 | | - match_on = TEMPLATE_ON_REGEX.match(line) |
79 | | - match_current = TEMPLATE_CURRENT_REGEX.match(line) |
80 | | - if match_on: |
81 | | - feature = match_on.group(1) |
82 | | - assert feature in FEATURES, f'{feature} not in {FEATURES}' |
83 | | - skip = not getattr(args, feature) |
84 | | - elif 'template off' in line: |
85 | | - skip = False |
86 | | - elif match_current: |
87 | | - feature = match_current.group(3) |
88 | | - assert feature in FEATURES, f'{feature} not in {FEATURES}' |
89 | | - if getattr(args, feature): |
90 | | - text.append(match_current.group(1)) |
91 | | - else: |
92 | | - if not skip: |
93 | | - text.append(line) |
94 | | - if text: |
95 | | - dst.write_text('\n'.join(text)) |
96 | | - dst.chmod(src.lstat().st_mode) |
97 | | - return True |
98 | | - return False |
99 | | - |
100 | | - |
101 | | -def copy_tree(src: pathlib.Path, dst: pathlib.Path, args) -> None: |
102 | | - for root, _, files in os.walk(src): |
103 | | - dst_root = dst / pathlib.Path(root).relative_to(src) |
104 | | - dst_root.mkdir(parents=True, exist_ok=True) |
105 | | - dst_root_is_empty = True |
106 | | - |
107 | | - for file in files: |
108 | | - src_filepath = pathlib.Path(root) / file |
109 | | - dst_filepath = dst_root / file |
110 | | - if handle_file(src_filepath, dst_filepath, args): |
111 | | - dst_root_is_empty = False |
112 | | - |
113 | | - if dst_root_is_empty: |
114 | | - dst_root.rmdir() |
115 | | - |
116 | | - |
117 | | -def run_ruff(src: pathlib.Path) -> None: |
118 | | - try: |
119 | | - subprocess.check_call(['ruff', 'format', str(src)]) |
120 | | - except BaseException as exc: |
121 | | - print(f'Warning: Failed to run ruff ({exc}), skipping.', file=sys.stderr) |
122 | | - |
123 | | - |
124 | | -def check_dst_non_existing(service_path: pathlib.Path): |
125 | | - if service_path.exists(): |
126 | | - print(f'Error: {service_path} directory already exists.', file=sys.stderr) |
127 | | - sys.exit(1) |
128 | | - |
129 | | - |
130 | | -def main() -> None: |
131 | | - args = parse_args() |
132 | | - check_dst_non_existing(args.service_path) |
133 | | - copy_tree(template_path(), args.service_path, args) |
134 | | - patch_devcontainer_version(args.service_path) |
135 | | - run_ruff(args.service_path) |
136 | | - |
137 | | - |
138 | | -if __name__ == '__main__': |
139 | | - main() |
| 1 | +#!/bin/sh |
| 2 | +./userver-create-service.py "$@" |
0 commit comments