csspin 2.0.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.
csspin/__main__.py ADDED
@@ -0,0 +1,20 @@
1
+ # -*- mode: python; coding: utf-8 -*-
2
+ #
3
+ # Copyright 2020 CONTACT Software GmbH
4
+ # https://www.contact-software.com/
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ from csspin import _main
19
+
20
+ _main()
@@ -0,0 +1,322 @@
1
+ # -*- mode: python; coding: utf-8 -*-
2
+ #
3
+ # Copyright 2020 CONTACT Software GmbH
4
+ # https://www.contact-software.com/
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ """Plugins that come with spin. These don't have to be installed
19
+ through a plugin package and are always available.
20
+ """
21
+
22
+ import sys
23
+
24
+ import click
25
+ import distro
26
+
27
+ from csspin import (
28
+ argument,
29
+ confirm,
30
+ die,
31
+ option,
32
+ parse_version,
33
+ rmtree,
34
+ run_script,
35
+ run_spin,
36
+ sh,
37
+ task,
38
+ toporun,
39
+ warn,
40
+ )
41
+ from csspin.cli import (
42
+ commands,
43
+ finalize_cfg_tree,
44
+ install_plugin_packages,
45
+ load_plugins_into_tree,
46
+ )
47
+
48
+
49
+ @task("run", add_help_option=False)
50
+ def exec_shell(ctx: click.Context, args: list[str]) -> None:
51
+ """Run a shell command in the project context."""
52
+ if not args:
53
+ die("Use of run is not possible without arguments.")
54
+ if "--help" == args[0]:
55
+ subcommand_obj = commands.get_command(ctx, "run") # type: ignore[attr-defined]
56
+ click.echo(subcommand_obj.get_help(ctx))
57
+ else:
58
+ sh(*args)
59
+
60
+
61
+ def pretty_descriptor(parent: str, name: str, descriptor, rst: bool) -> str: # type: ignore[no-untyped-def]
62
+ types = getattr(descriptor, "type", ["any"])
63
+ default = getattr(descriptor, "default", None)
64
+ if name:
65
+ if parent:
66
+ name = f"{parent}.{name}"
67
+ if rst:
68
+ joined_types = " ".join(types)
69
+ decl = f".. py:data:: {name}\n :type: '{joined_types}'\n"
70
+ if default:
71
+ decl += f" :value: '{default}'\n"
72
+ if hasattr(descriptor, "noindex") or "object" in types:
73
+ decl += " :noindex:\n"
74
+ else:
75
+ joined_types = ", ".join(types)
76
+ decl = f"{name}: [{joined_types}]"
77
+ if default:
78
+ decl += f" = '{default}'"
79
+ helptext = getattr(descriptor, "help", "")
80
+ if not helptext.endswith("\n"):
81
+ helptext += "\n"
82
+ decl += f"\n{helptext}\n"
83
+ else:
84
+ decl = "================\nSchema Reference\n================\n\n"
85
+ return decl
86
+
87
+
88
+ @task()
89
+ def schemadoc( # type: ignore[no-untyped-def]
90
+ cfg,
91
+ outfile: option( # type: ignore[valid-type]
92
+ "-o",
93
+ "outfile",
94
+ default="-", # noqa: F722
95
+ type=click.File("w"),
96
+ help="Write output into FILENAME.", # noqa: F722
97
+ ),
98
+ full: option( # type: ignore[valid-type]
99
+ "--full",
100
+ default=True,
101
+ type=click.BOOL,
102
+ help="Show schema documentation for the whole ConfigTree.", # noqa: F722
103
+ ),
104
+ rst: option( # type: ignore[valid-type]
105
+ "--rst",
106
+ is_flag=True,
107
+ default=False,
108
+ help="Print the schema documentation in rst format.", # noqa: F722
109
+ ),
110
+ select: argument( # type: ignore[valid-type]
111
+ type=click.STRING,
112
+ default="", # noqa: F722
113
+ callback=lambda ctx, param, value: value.split(".") if value else "",
114
+ ),
115
+ ) -> None:
116
+ """Print the schema definitions for spin."""
117
+
118
+ def do_docwrite(parent: str, name: str, desc, ignore=tuple()): # type: ignore[no-untyped-def]
119
+ fullname = f"{parent}.{name}" if parent else name
120
+ if fullname in ignore:
121
+ return
122
+
123
+ outfile.write(pretty_descriptor(parent, name, desc, rst))
124
+ properties = getattr(desc, "properties", {})
125
+ for prop, descr in properties.items():
126
+ do_docwrite(fullname, prop, descr, ignore)
127
+
128
+ schema = cfg.schema
129
+
130
+ ignore = []
131
+ if not full:
132
+ for import_spec in cfg.loaded:
133
+ if "csspin." in import_spec:
134
+ continue
135
+ import_spec = tuple(import_spec.split("."))
136
+ plugin_name = import_spec[-1]
137
+ ignore.append(plugin_name)
138
+
139
+ arg = ""
140
+ for arg in select:
141
+ schema = schema.properties.get(arg)
142
+ parent = "" if len(select) < 2 else ".".join(select[:-1])
143
+ do_docwrite(parent, arg, schema, ignore)
144
+
145
+
146
+ class TaskDefinition:
147
+ def __init__(self, definition: dict) -> None:
148
+ self._definition = definition
149
+
150
+ def __call__(self) -> None:
151
+ env = self._definition.get("env", None)
152
+ run_spin(self._definition.get("spin", []))
153
+ run_script(self._definition.get("script", []), env)
154
+
155
+
156
+ def configure(cfg) -> None: # type: ignore[no-untyped-def]
157
+ """
158
+ Grab explicitly defined tasks from the configuration tree and add them as
159
+ subcommands.
160
+ """
161
+ for clause_name in ("extra_tasks", "tasks"):
162
+ for task_name, task_definition in cfg.get(clause_name, {}).items():
163
+ task(task_name, help=task_definition.get("help", ""))(
164
+ TaskDefinition(task_definition)
165
+ )
166
+
167
+
168
+ def merge_dicts(a: dict, b: dict) -> None:
169
+ for k, v in b.items():
170
+ if k in a:
171
+ # We support lists and strings in system_requirements;
172
+ # this is not very robust, though.
173
+ if isinstance(v, list):
174
+ a[k].extend(v)
175
+ else:
176
+ a[k] = " ".join((a[k], v))
177
+ else:
178
+ a[k] = v
179
+
180
+
181
+ def get_distro() -> dict:
182
+ dinfo = distro.info()
183
+ if sys.platform == "win32":
184
+ dinfo["id"] = "windows"
185
+ winver = sys.getwindowsversion()
186
+ dinfo["version"] = f"{winver.major}.{winver.minor}.{winver.build}"
187
+ return dinfo # type: ignore[no-any-return]
188
+
189
+
190
+ @task("system-provision", noenv=True)
191
+ def do_system_provisioning( # type: ignore[no-untyped-def]
192
+ cfg,
193
+ distroargs: argument(nargs=-1), # type: ignore[valid-type]
194
+ ) -> None:
195
+ """Provision system dependencies for the host.
196
+
197
+ Usage:
198
+ spin system-provision [<distro> [<version>]]
199
+
200
+ This will output a script on stdout, that uses OS package managers like apt,
201
+ yum etc. to install system-level dependencies for the project. The output
202
+ can for example be piped into a sudo shell.
203
+ """
204
+ # Install the plugins and build the full config tree
205
+ install_plugin_packages(cfg)
206
+ load_plugins_into_tree(cfg)
207
+ finalize_cfg_tree(cfg)
208
+
209
+ if distroargs:
210
+ distroname = distroargs[0]
211
+ else:
212
+ dinfo = get_distro()
213
+ distroname = dinfo["id"]
214
+
215
+ # Check system requirements of individual plugins
216
+ out: dict = {}
217
+ supported = True
218
+ for pi in cfg.spin.topo_plugins:
219
+ defaults = cfg.loaded[pi].defaults
220
+ if defaults.get("requires") and defaults.requires.get("system"):
221
+ system_requirements = defaults.requires.system
222
+ if distroname not in system_requirements.keys():
223
+ warn(
224
+ f"The '{pi}' plugin does not officially support"
225
+ f" {distroname}. You can see which packages to"
226
+ " manually install by running 'spin system-provision debian'"
227
+ )
228
+ supported = False
229
+ else:
230
+ merge_dicts(out, system_requirements.get(distroname, []))
231
+
232
+ # Check system requirements defined within the configuration tree, usually
233
+ # defined the projects' spinfile.yaml.
234
+ if cfg.system_requirements.keys():
235
+ if distroname not in cfg.system_requirements.keys():
236
+ warn(
237
+ "This project does not officially support"
238
+ f" {distroname}. You can see which packages to manually"
239
+ " install by running 'spin system-provision debian'"
240
+ )
241
+ supported = False
242
+ else:
243
+ merge_dicts(out, cfg.system_requirements.get(distroname, []))
244
+
245
+ for line in out.get("before", []):
246
+ print(line)
247
+
248
+ for syscmd in ("apt", "choco"):
249
+ if (package_list := out.get(syscmd, [])) and supported:
250
+ print(f"{syscmd} install -y {' '.join(package_list)}")
251
+
252
+ for line in out.get("after", []):
253
+ print(line)
254
+
255
+
256
+ @task("distro", noenv=True)
257
+ def distro_task(cfg) -> None: # type: ignore[no-untyped-def]
258
+ """Print the distro information."""
259
+ dinfo = get_distro()
260
+ print(f"distro={repr(dinfo['id'])} version={parse_version(dinfo['version'])}")
261
+
262
+
263
+ @task("provision", noenv=True)
264
+ def provision(cfg) -> None: # type: ignore[no-untyped-def]
265
+ """
266
+ Create or update a development environment.
267
+ """
268
+ # Install the plugins and build the full config tree
269
+ install_plugin_packages(cfg)
270
+ load_plugins_into_tree(cfg)
271
+ finalize_cfg_tree(cfg)
272
+
273
+ toporun(cfg, "provision")
274
+ toporun(cfg, "finalize_provision")
275
+
276
+
277
+ @task(noenv=True)
278
+ def cleanup( # type: ignore[no-untyped-def]
279
+ cfg,
280
+ purge: option( # type: ignore[valid-type]
281
+ "--purge",
282
+ is_flag=True,
283
+ help="Removes spin plugin data.", # noqa: F722
284
+ ),
285
+ skip_confirmation: option( # type: ignore[valid-type]
286
+ "-y",
287
+ "--yes",
288
+ "skip_confirmation",
289
+ is_flag=True,
290
+ help="Skip confirmation when using --purge.", # noqa: F722
291
+ ),
292
+ ) -> None:
293
+ """
294
+ Clean up project-local resources that have been provisioned by spin, e.g.
295
+ virtual environments and {project_root}/.spin. Also deletes {spin.data}
296
+ if --purge is passed.
297
+ """
298
+ if (
299
+ purge
300
+ and not skip_confirmation
301
+ and not (
302
+ confirm(
303
+ f"You are about to delete all plugin's data in {cfg.spin.data}."
304
+ " Continue?"
305
+ )
306
+ )
307
+ ):
308
+ return
309
+
310
+ # Load the plugins as far as they are available.
311
+ load_plugins_into_tree(cfg, cleanup=True)
312
+
313
+ # Do not configure and sanitize in case of cleanup, since plugin
314
+ # packages may not be installed, causing AttributeErrors in case of
315
+ # accessing not-initialized plugins via "cfg." as well as failures due
316
+ # to interpolation against property tree keys that does not exist.
317
+
318
+ toporun(cfg, "cleanup", reverse=True)
319
+ rmtree(cfg.spin.spin_dir / "plugins")
320
+
321
+ if purge:
322
+ rmtree(cfg.spin.data)