dk-tasklib 3.0.9__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.
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: dk-tasklib
3
+ Version: 3.0.9
4
+ Summary: dk-tasklib - pyinvoke task library
5
+ Home-page: https://github.com/datakortet/dk-tasklib
6
+ Author: Bjorn Pettersen
7
+ Author-email: bp@datakortet.no
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Requires-Dist: invoke
17
+ Requires-Dist: PyYAML
18
+ Requires-Dist: dkfileutils>=1.4.2
19
+ Requires-Dist: yamldirs>=1.1.8
20
+ Dynamic: author
21
+ Dynamic: author-email
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: home-page
25
+ Dynamic: requires-dist
26
+ Dynamic: summary
27
+
28
+
29
+ .. image:: https://travis-ci.org/datakortet/dk-tasklib.svg?branch=master
30
+ :target: https://travis-ci.org/datakortet/dk-tasklib
31
+
32
+ .. image:: https://coveralls.io/repos/github/datakortet/dk-tasklib/badge.svg?branch=master
33
+ :target: https://coveralls.io/github/datakortet/dk-tasklib?branch=master
34
+
35
+ dk-tasklib - pyinvoke task library
36
+ ====================================
37
+
38
+
39
+ Installing from PyPI
40
+ --------------------
41
+
42
+ This is what you want if you just want to use dk-tasklib:
43
+
44
+ pip install dk-tasklib
45
+
46
+
47
+ Creating a default tasks.py file
48
+ --------------------------------
49
+ You can create a default ``tasks.py`` file. From the root of your package::
50
+
51
+ dk-tasklib install
52
+
53
+ You'll need to specify ``--force`` to overwrite an existing file.
54
+
55
+ As a source package
56
+ -------------------
57
+ This is what you want if you are developing dk-tasklib or want
58
+ to make local changes to the source code.
59
+
60
+ pip install -e <path>
61
+
62
+
63
+ See docs/ folder for documentation.
64
+
65
+
66
+ Development
67
+ -----------
68
+
69
+ Run the authoritative package checks from the repository root::
70
+
71
+ dk testpackage
72
+
73
+ The suite enforces branch-aware coverage of at least 83 percent. Build the
74
+ documentation with::
75
+
76
+ dk docs
77
+
78
+ The documentation is maintained against current Sphinx 8 and 9 releases.
@@ -0,0 +1,35 @@
1
+ dktasklib/__init__.py,sha256=XEy0mO984KPXEXIbohLYDjl_7UeV2tiuM58BVfyIKlQ,129
2
+ dktasklib/_version.py,sha256=GgzlXELdHTkTAUXqLnCAGFr2E0nwjjBPe-3RxfSkl6c,22
3
+ dktasklib/clean.py,sha256=m4IF-XI6J5KJz7WvWDYoA7Sr65OI7K3GR9IcJsKAQ2Y,212
4
+ dktasklib/commands.py,sha256=bh8aF8RYBfCgOwhWmGsRpGeyeZl5EXbOHUL11OOTNc4,3165
5
+ dktasklib/concat.py,sha256=GfAqc94dS8Lki25RIMaCx4_A8iVsKN6mTJjQZwW-rk4,1942
6
+ dktasklib/docs.py,sha256=1iIOUE4mKTksTbRLcDZ2vNz3PltgdNlbANii2_iGRKc,6463
7
+ dktasklib/environment.py,sha256=3eX8e-z4C94tcim6vw5mWOiD6952PWG0EaW1-bWin40,113
8
+ dktasklib/executables.py,sha256=1puqMZ1ni87V0a0sQsF5iGNvXtmaF7MCD25Gst9P6no,6621
9
+ dktasklib/help.py,sha256=FNKuVusc-Dev8ihEk98mmxY9WNUEwi-6F7IikHW6EQo,359
10
+ dktasklib/jstools.py,sha256=W4kBh7xPJsiZb2U4r2HPrKCufsx9Zya7lyXA3PIg_OQ,10535
11
+ dktasklib/lessc.py,sha256=Gx0jUHz02QXnxevQGDSdy045VpLSA2PGNITa6LJqzRo,3375
12
+ dktasklib/manage.py,sha256=0S1PAjAfJctjT6RCNLw4pvBst83rP_TaeUk-QLXP4VU,2631
13
+ dktasklib/npm.py,sha256=6W2AmYptF6sWNAdrgefDGHDcYAUDBVF1pIprLNDyb4Q,518
14
+ dktasklib/pset.py,sha256=L656kmzR4OXIsHni2OPQAVm6ctw-O0_YScOnZCVYV-s,3304
15
+ dktasklib/publish.py,sha256=j8rZ2VXnDd4ak3GXvmy4zDpDnzsRqbJAgB7dvKijyec,1361
16
+ dktasklib/rule.py,sha256=6D85tS3tIgiK9ll_0leaGH-rUsFqTA0oZqqW8W-Dg8o,1572
17
+ dktasklib/runners.py,sha256=OvGY-3ljyX-SYxf7rib9CWMLa0Whaoxvgc_tyfO2URw,990
18
+ dktasklib/upversion.py,sha256=CppaS2nIx9KhjB-wb03LWIkNfzkFdm7xNhLYBa2w6aI,5166
19
+ dktasklib/urlinliner.py,sha256=vSBUOVD9mqfSJkMCiPYjWwU62RVtG346rIkJKc6qMi4,2505
20
+ dktasklib/utils.py,sha256=tqP0VCYi4ulP63deJhsg3XO3e7FEl0cMzkXj2LKPb4s,6845
21
+ dktasklib/version.py,sha256=QbZ6_WbnWwX1okaf0hkPRoQLv9Bmyn_GB_rzwDsk0rI,2599
22
+ dktasklib/watch.py,sha256=qm4oE-8y8GNg7esYyc90O9u9Koug2LwvxxSrUkTUcVM,2416
23
+ dktasklib/wintask.py,sha256=gb7JnDmiV9O-nDOTUq_Kci02_PR3v5J4GpK7M6ffR3M,45
24
+ dktasklib/entry_points/__init__.py,sha256=1vjEu0jaBda00GRE2eJrronfxy0TaCSUTe4jUikzw1U,45
25
+ dktasklib/entry_points/confbase.py,sha256=3GFatqsGxo0c4DZYgWLgJdWri7AVspqSS96PhWOLeSo,5352
26
+ dktasklib/entry_points/dktasklibcmd.py,sha256=fTulpX9lQa7H_iUNNuJxMCeHTRfF7V2Ylgv-oTEVFNo,2824
27
+ dktasklib/entry_points/pytemplate.py,sha256=po3Xybr9xuRM4BABVwBgyyd525jJNUU6Q8clp5wPcnQ,431
28
+ dktasklib/entry_points/taskbase.py,sha256=LquiNxt5kYVztBG8xBtrTwRfF7DtDhTBiY2j8GiyjfM,4021
29
+ dktasklib/package/__init__.py,sha256=K_ojfTu6ebW0ETjXUrUZTrluJS0OWcjigdNAATd6ga4,1138
30
+ dktasklib/package/package_interface.py,sha256=vICO-4Y0Zjuwyba_ERlZoexFT9TlZH0bhsn_qFKz2cw,3068
31
+ dk_tasklib-3.0.9.dist-info/METADATA,sha256=HtrAsqA0B-X4gYuLBzf-YIdkjoY9dv3KK22XY3giFZM,2087
32
+ dk_tasklib-3.0.9.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
33
+ dk_tasklib-3.0.9.dist-info/entry_points.txt,sha256=3ay7Osx3BNuj1RbAquh72G6hTkL9FYNyydH1G45uT6M,72
34
+ dk_tasklib-3.0.9.dist-info/top_level.txt,sha256=yG3xqUjpFZUCFf-_OhR5X2Is0NVbOW6e9YBmYnFlwhU,10
35
+ dk_tasklib-3.0.9.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ dk-tasklib = dktasklib.entry_points.dktasklibcmd:main
@@ -0,0 +1 @@
1
+ dktasklib
dktasklib/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from ._version import __version__ # noqa
2
+ from .package import Package # noqa
3
+ from . import lessc # noqa
dktasklib/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = '3.0.9'
dktasklib/clean.py ADDED
@@ -0,0 +1,8 @@
1
+ from dktasklib.wintask import task
2
+
3
+
4
+ @task
5
+ def clean(ctx, directory):
6
+ """Remove all contents from build subdirectory `directory`.
7
+ """
8
+ ctx.run("rm -rf build/{directory}/*".format(directory=directory))
dktasklib/commands.py ADDED
@@ -0,0 +1,95 @@
1
+ import os
2
+ import re
3
+ import invoke
4
+
5
+ from .executables import exe
6
+ from .utils import fmt
7
+
8
+
9
+ default_command_policy = dict(
10
+ negative_bool='omit', # omit False boolean parameters (or 'prefix')
11
+ list_join=',',
12
+ )
13
+
14
+
15
+ class Command:
16
+ def __init__(self, name, argspec="",
17
+ requirements=(),
18
+ policy=default_command_policy,
19
+ **optdefs):
20
+ self._args = (name, argspec, requirements, policy, optdefs)
21
+ self._initialized = False
22
+
23
+ def _initialize(self):
24
+ if self._initialized:
25
+ return
26
+ name, argspec, requirements, policy, optdefs = self._args
27
+ # handle any required params attached to the command, i.e. if the
28
+ # command is specified as foo = Command('foo -v', ...)
29
+ exename, reqparams = (name + ' ').split(' ', 1)
30
+ # make sure all requirements are present
31
+ exe.require(*requirements)
32
+ if os.path.isfile(exename): # if path to executable is given
33
+ self.cmd = exename # then use it
34
+ else:
35
+ self.cmd = exe.find(exename) # else search path
36
+ # fetch names of all argspec params
37
+ self.params = [p[1:-1] for p in re.findall(r'\{[^}]*\}', argspec)]
38
+ self.required_params = reqparams.strip()
39
+ self.argspec = argspec
40
+ self.policy = policy
41
+ self.optdefs = optdefs
42
+ self._initialized = True
43
+
44
+ def _kw_to_opts(self, kw):
45
+ res = ""
46
+ for k, v in kw.items():
47
+ if k in self.params: # skip conversion of parameters to argspec
48
+ continue
49
+
50
+ res += ' '
51
+ flag = ('-' if len(k) == 1 else '--') + k.replace('_', '-')
52
+
53
+ if isinstance(v, bool):
54
+ if v:
55
+ res += flag
56
+ else:
57
+ if self.policy['negative_bool'] == 'prefix':
58
+ res += '--no-' + flag.lstrip('-')
59
+
60
+ elif isinstance(v, (list, tuple)) and v:
61
+ res += flag + '=' + self.policy['list_join'].join(str(item) for item in v)
62
+
63
+ elif isinstance(v, str):
64
+ res += flag + '="%s"' % v
65
+
66
+ else:
67
+ res += flag + '=' + str(v)
68
+
69
+ return res
70
+
71
+ def __call__(self, ctx=None, *args, **kwargs):
72
+ self._initialize()
73
+ if ctx is not None and not isinstance(ctx, invoke.Context):
74
+ # we've been passed a real argument in position 0
75
+ args = (ctx,) + args
76
+ ctx = None
77
+
78
+ kwargs['opts'] = self._kw_to_opts(kwargs)
79
+ posargs = ' '.join(args)
80
+ if 'pos' in self.params:
81
+ kwargs['pos'] = posargs
82
+ fparams = fmt(self.argspec, kwargs)
83
+ if 'pos' not in self.params:
84
+ # if placement of positional args is not specified, then put at end
85
+ fparams += " " + posargs
86
+ if ctx is None:
87
+ ctx = invoke.Context()
88
+
89
+ cmd = '"%s"' % self.cmd if ' ' in self.cmd else self.cmd
90
+ cmd += ' ' + self.required_params + fparams
91
+ ctx.run(cmd, warn=True)
92
+ return cmd
93
+
94
+
95
+ tree = Command('tree -I ".git|*.pyc|*.swp|dist|*.egg-info|_static|_build|_templates"')
dktasklib/concat.py ADDED
@@ -0,0 +1,76 @@
1
+ import os
2
+ import sys
3
+
4
+ from dkfileutils.path import Path
5
+
6
+
7
+ def line_endings(fname):
8
+ """Return all line endings in the file.
9
+ """
10
+ _endings = {line[-2:] for line in open(fname, 'rb').readlines()}
11
+ res = set()
12
+ for e in _endings:
13
+ if e.endswith(b'\r'):
14
+ res.add(b'\r')
15
+ elif e.endswith(b'\r\n'):
16
+ res.add(b'\r\n')
17
+ elif e.endswith(b'\n'):
18
+ res.add(b'\n')
19
+ return res
20
+
21
+
22
+ def chomp(s):
23
+ """Remove line terminator if it exists.
24
+ """
25
+ if s[-2:] == b'\r\n':
26
+ return s[:-2]
27
+ if s[-1:] == b'\r' or s[-1:] == b'\n':
28
+ return s[:-1]
29
+ return s
30
+
31
+
32
+ def fix_line_endings(fname, eol=b'\n'):
33
+ """Change all line endings to ``eol``.
34
+ """
35
+ lines = [chomp(line) for line in open(fname, 'rb').readlines()]
36
+ with open(fname, 'wb') as fp:
37
+ for line in lines:
38
+ fp.write(line + eol)
39
+
40
+
41
+ def copy(ctx, source, dest, force=False):
42
+ """Copy ``source`` to ``dest``, which can be a file or directory.
43
+ """
44
+ if source == dest:
45
+ return dest
46
+
47
+ source = os.path.normcase(os.path.normpath(str(source)))
48
+ dest = os.path.normcase(os.path.normpath(str(dest)))
49
+ flags = ""
50
+ if sys.platform == 'win32':
51
+ if force:
52
+ flags += " /Y"
53
+ ctx.run('copy {flags} {source} {dest}'.format(**locals()))
54
+ else: # pragma: nocover
55
+ if force:
56
+ flags += " --force"
57
+ ctx.run('cp {flags} {source} {dest}'.format(**locals()))
58
+ return dest
59
+
60
+
61
+ def concat(ctx, dest, *sources, **kw):
62
+ force = kw.pop('force', False) # noqa
63
+ placement = Path(dest).dirname()
64
+ placement.makedirs()
65
+
66
+ with open(dest, 'w') as out:
67
+ print("Opened:", dest, "for writing.")
68
+ for s in sources:
69
+ with open(s, 'r') as inp:
70
+ print(" appending:", s)
71
+ out.writelines(inp.readlines())
72
+ out.write('\n')
73
+
74
+ fix_line_endings(dest)
75
+
76
+ return dest
dktasklib/docs.py ADDED
@@ -0,0 +1,225 @@
1
+ import datetime
2
+ import os
3
+ import sys
4
+ import textwrap
5
+ import webbrowser
6
+ from os.path import join
7
+
8
+ from dkfileutils.changed import changed
9
+ from dkfileutils.path import Path
10
+
11
+ from dktasklib.entry_points.pytemplate import PyTemplate
12
+ from dktasklib.runners import run
13
+ from dktasklib.wintask import task
14
+ from invoke import Collection
15
+
16
+ from dktasklib import concat
17
+ from dktasklib import Package
18
+
19
+
20
+ DIRNAME = Path(__file__).dirname()
21
+
22
+
23
+ @task(name='clean')
24
+ def _clean(ctx): # Underscored func name to avoid shadowing kwargs in build()
25
+ """Nuke docs build target directory so next build is clean.
26
+ """
27
+ if 'pkg' not in ctx:
28
+ ctx.pkg = Package()
29
+ builddir = ctx.pkg.root / 'build' / 'docs'
30
+ if os.path.exists(builddir) and len(os.listdir(builddir)) > 0:
31
+ ctx.run("rm -rf {0}/*".format(builddir))
32
+
33
+
34
+ # Ditto
35
+ @task(name='browse')
36
+ def _browse(ctx): # pragma: nocover
37
+ """Open build target's index.html in a browser (using the :py:mod:`webbrowser` module).
38
+ """
39
+ index = join(ctx.pkg.root / 'build' / 'docs' / 'index.html')
40
+ webbrowser.open_new(index)
41
+
42
+
43
+ @task(help=dict(
44
+ author='full name of author',
45
+ language='two letter language code (en, no, etc.)',
46
+
47
+ ))
48
+ def initdocs(ctx, author, language):
49
+ """Run sphinx-quickstart to create an initial docs folder.
50
+ """
51
+ pkg = Package()
52
+ cmd = (
53
+ 'sphinx-quickstart '
54
+ ' --dot _'
55
+ ' -a "{author}"'
56
+ ' -l {language}'
57
+ ' -v {pkg.version}'
58
+ ' -r {pkg.version}'
59
+ ' -p {pkg.name}'
60
+ ' --no-makefile'
61
+ ' --no-batchfile'
62
+ ' --ext-autodoc'
63
+ ' --ext-intersphinx'
64
+ ' --ext-todo'
65
+ ' --ext-viewcode'
66
+ ' --suffix=".rst"'
67
+ ' --master="index"'
68
+ ' {docsdir}'
69
+ ).format(
70
+ pkg=pkg,
71
+ docsdir=pkg.docs.relpath(pkg.root),
72
+ author=author,
73
+ language=language,
74
+ )
75
+ print("""
76
+
77
+ invoke can't run this command on windows..
78
+
79
+ {cmd}
80
+ """.format(cmd=cmd))
81
+
82
+
83
+ @task
84
+ def create_docs_directory(ctx, force=False):
85
+ """Create docs/conf.py
86
+ """
87
+ cwd = Path.curdir() # noqa
88
+ ctx.pkg.docs = ctx.pkg.docs
89
+ if ctx.pkg.docs.exists() and not force:
90
+ print("docs directory exists (use --force to overwrite)")
91
+ sys.exit(1)
92
+ ctx.pkg.docs.makedirs()
93
+ ctx.pkg.docs.makedirs('_static')
94
+ ctx.pkg.docs.makedirs('_templates')
95
+ confbase = DIRNAME / 'entry_points/confbase.py'
96
+ txt = confbase.read('rb')
97
+ txt = txt.replace(b'\r\n', b'\n')
98
+ t = PyTemplate(txt)
99
+
100
+ (ctx.pkg.docs / 'conf.py').write(t.substitute(
101
+ VERSION=ctx.pkg.version,
102
+ PACKAGE=ctx.pkg.name,
103
+ YEAR=datetime.date.today().year,
104
+ AUTHOR=run('python setup.py --author').strip()
105
+ ))
106
+
107
+
108
+ @task
109
+ def create_index(ctx, force=False):
110
+ """Create docs/index.rst
111
+ """
112
+ index = ctx.pkg.docs / 'index.rst'
113
+ if index.exists() and not force:
114
+ print("docs/index.rst exists (use --force to overwrite)")
115
+ sys.exit(1)
116
+ confbase = DIRNAME / 'entry_points/index.rst'
117
+ txt = confbase.read('rb')
118
+ txt = txt.replace(b'\r\n', b'\n')
119
+ t = PyTemplate(txt)
120
+
121
+ index.write(t.substitute(
122
+ PACKAGE=ctx.pkg.name,
123
+ SOURCEDIR=ctx.pkg.source,
124
+ ))
125
+
126
+
127
+ @task
128
+ def make_api_docs(ctx, prefix='', force=False):
129
+ """Run sphinx-apidoc to write autodoc documentation to `docs/api/*`
130
+ """
131
+ ctx.run("rm -rf {pkg.docs}/api".format(pkg=ctx.pkg))
132
+ ctx.run("sphinx-apidoc -o {pkg.docs}/api {pkg.source} {pkg.source}/migrations {pkg.source}/models {pkg.source}/models.py".format(pkg=ctx.pkg))
133
+ if prefix:
134
+ for fname in (ctx.pkg.docs / 'api').glob('*.rst'):
135
+ f = Path(fname)
136
+ lines = f.read().split('\n')
137
+ for i, line in enumerate(lines):
138
+ lines[i] = line.replace(ctx.pkg.name, prefix + ctx.pkg.name)
139
+ if i > 0 and len(set(line)) == 1 and len(line) < len(lines[i-1]):
140
+ lines[i] = line[0] * len(lines[i-1])
141
+ f.write('\n'.join(lines))
142
+ pre, pkgname, post = fname.rpartition(ctx.pkg.name)
143
+ if pkgname and os.path.sep not in post:
144
+ f.rename(pre + prefix + pkgname + post)
145
+
146
+ concat.copy(ctx, ctx.pkg.docs / 'api' / '*', ctx.pkg.docs)
147
+ ctx.run("rm -rf {pkg.docs}/api".format(pkg=ctx.pkg))
148
+
149
+ if ".. include:: modules.rst" not in open(ctx.pkg.docs / 'index.rst').read():
150
+ print(textwrap.dedent("""\
151
+ WARNING: you need to include the following in docs/index.rst
152
+
153
+ .. include:: modules.rst
154
+
155
+ """))
156
+
157
+
158
+ @task(default=True, help={
159
+ 'opts': "Extra sphinx-build options/args",
160
+ 'prefix': "Module prefix, for submodules",
161
+ 'clean': "Remove build tree before building",
162
+ 'browse': "Open docs index in browser after building",
163
+ 'warn': "Build with stricter warnings/errors enabled",
164
+ 'builder': "Builder to use, defaults to html",
165
+ 'force': "Force re-reading of all files (ignore cache)",
166
+ })
167
+ def build(ctx, clean=False, browse=False, warn=False,
168
+ builder='html',
169
+ force=False,
170
+ opts="", prefix=''):
171
+ """
172
+ Build the project's Sphinx docs.
173
+ """
174
+ if not force and not changed(ctx.pkg.docs):
175
+ print("""
176
+ No changes detected in {}, add --force to build docs anyway.
177
+ """.format(ctx.pkg.docs))
178
+ return # should perhaps check if code has changed too? (autodoc)
179
+
180
+ if clean:
181
+ _clean(ctx)
182
+ make_api_docs(ctx, force=force, prefix=prefix)
183
+
184
+ if opts is None: # pragma: nocover
185
+ opts = ""
186
+ opts += " -b %s" % builder
187
+ if warn:
188
+ opts += " -n -W"
189
+ if force:
190
+ opts += " -a -E"
191
+ cmd = "sphinx-build {opts} {ctx.pkg.docs} {ctx.pkg.root}/build/docs".format(opts=opts, ctx=ctx)
192
+ dj_settings = ctx.pkg.get('django_settings_module', "")
193
+ if dj_settings:
194
+ os.environ['DJANGO_SETTINGS_MODULE'] = dj_settings
195
+ ctx.run(cmd)
196
+ if browse: # pragma: nocover
197
+ _browse(ctx)
198
+
199
+
200
+ @task
201
+ def tree(ctx):
202
+ """Display the docs tree.
203
+ """
204
+ ignore = ".git|*.pyc|*.swp|dist|*.egg-info|_static|_build|_templates"
205
+ ctx.run('tree -Ca -I "{0}" {1}'.format(ignore, ctx.pkg.docs))
206
+
207
+
208
+ # Vanilla/default/parameterized collection for normal use
209
+ ns = Collection(
210
+ 'docs',
211
+ _clean,
212
+ _browse,
213
+ build,
214
+ tree,
215
+ initdocs,
216
+ create_docs_directory,
217
+ create_index,
218
+ )
219
+ ns.configure({
220
+ 'docs': {
221
+ 'source': 'docs',
222
+ 'builddir': join('build', 'docs'),
223
+ 'target_file': 'index.html',
224
+ }
225
+ })
@@ -0,0 +1,5 @@
1
+ """
2
+ """
3
+
4
+ if __name__ == "__main__":
5
+ pass