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.
dktasklib/pset.py ADDED
@@ -0,0 +1,122 @@
1
+ # noinspection PyPep8Naming
2
+ class pset(dict):
3
+ """Property Set class.
4
+ A property set is an object where values are attached to attributes,
5
+ but can still be iterated over as key/value pairs.
6
+ The order of assignment is maintained during iteration.
7
+ Only one value allowed per key.
8
+
9
+ >>> x = pset()
10
+ >>> x.a = 42
11
+ >>> x.b = 'foo'
12
+ >>> x.a = 314
13
+ >>> x
14
+ pset(a=314, b='foo')
15
+
16
+ """
17
+ def __init__(self, items=(), **attrs):
18
+ object.__setattr__(self, '_order', [])
19
+ super().__init__()
20
+ if isinstance(items, dict):
21
+ items = items.items()
22
+ for k, v in items:
23
+ self._add(k, v)
24
+ for k, v in attrs.items():
25
+ self._add(k, v)
26
+
27
+ def __repr__(self):
28
+ return '{%s}' % ', '.join(["%r: %r" % kv for kv in self])
29
+
30
+ def _add(self, key, value):
31
+ """Add key->value to client vars.
32
+ """
33
+ if key not in self._order:
34
+ self._order.append(key)
35
+ dict.__setitem__(self, key, value)
36
+
37
+ def remove(self, key):
38
+ """Remove key from client vars.
39
+ """
40
+ if key in self._order:
41
+ self._order.remove(key)
42
+ dict.__delitem__(self, key)
43
+
44
+ def __eq__(self, other):
45
+ """Equal iff they have the same set of keys, and the values for
46
+ each key is equal. Key order is not considered for equality.
47
+ """
48
+ if other is None:
49
+ return False
50
+ if type(other) is dict:
51
+ return dict.__eq__(self, other)
52
+ # noinspection PyProtectedMember
53
+ if set(self._order) == set(other._order): # pylint: disable=W0212
54
+ for key in self._order:
55
+ if self[key] != other[key]:
56
+ return False
57
+ return True
58
+ return False
59
+
60
+ def __ne__(self, other):
61
+ return not (self == other)
62
+
63
+ def __iadd__(self, other):
64
+ for k, v in other:
65
+ self._add(k, v)
66
+ return self
67
+
68
+ def __add__(self, other):
69
+ """self + other
70
+ """
71
+ tmp = self.__class__()
72
+ tmp += self
73
+ tmp += other
74
+ return tmp
75
+
76
+ def __radd__(self, other):
77
+ """other + self
78
+ """
79
+ tmp = self.__class__()
80
+ for k, v in other.items():
81
+ tmp[k] = v
82
+ tmp += self
83
+ return tmp
84
+
85
+ def __getattr__(self, key):
86
+ if not super().__contains__(key):
87
+ raise AttributeError(key)
88
+ return dict.get(self, key)
89
+
90
+ def __getitem__(self, key):
91
+ return dict.get(self, key)
92
+
93
+ def __delattr__(self, key):
94
+ if key in self:
95
+ self.remove(key)
96
+
97
+ def __delitem__(self, key):
98
+ if key in self:
99
+ self.remove(key)
100
+
101
+ def __iter__(self):
102
+ return ((k, dict.get(self, k)) for k in self._order)
103
+
104
+ def items(self):
105
+ return iter(self)
106
+
107
+ def values(self):
108
+ # type: () -> list
109
+ return [dict.get(self, k) for k in self._order]
110
+
111
+ def keys(self):
112
+ return self._order
113
+
114
+ def __setattr__(self, key, val):
115
+ # assert key not in self._reserved, key
116
+ if key.startswith('_'):
117
+ object.__setattr__(self, key, val)
118
+ else:
119
+ self._add(key, val)
120
+
121
+ def __setitem__(self, key, val):
122
+ self._add(key, val)
dktasklib/publish.py ADDED
@@ -0,0 +1,50 @@
1
+ from dktasklib.executables import requires
2
+ from dktasklib.wintask import task
3
+ from . import Package
4
+
5
+
6
+ @requires('wheel', 'twine')
7
+ @task(
8
+ default=True,
9
+ help={
10
+ 'force': 'sets all other options to True',
11
+ 'clean': 'remove the build/ and dist/ directory before starting',
12
+ 'docs': 'build and upload docs to PyPi',
13
+ 'wheel': 'build wheel (in addition to sdist)',
14
+ 'sign': 'sign the wheel using weel sign pkgname',
15
+ 'upload': 'upload to PyPI after building'
16
+ }
17
+ )
18
+ def publish(ctx, force=False, clean=True, wheel=True, sign=True, docs=False, upload=False):
19
+ """Publish to PyPi
20
+ """
21
+ pkg = Package()
22
+ if force: # pragma: nocover
23
+ clean = True
24
+ wheel = True
25
+ docs = True
26
+ upload = True
27
+ sign = True # noqa
28
+
29
+ if not wheel:
30
+ sign = False # noqa
31
+
32
+ with pkg.root.cd():
33
+ if clean:
34
+ ctx.run("rm -rf dist")
35
+ ctx.run("rm -rf build/lib")
36
+ ctx.run("rm -rf build/bdist.win32")
37
+
38
+ targets = 'sdist'
39
+ if wheel:
40
+ targets += ' bdist_wheel'
41
+
42
+ ctx.run("python setup.py " + targets)
43
+
44
+ if docs:
45
+ ctx.run("python setup.py build_sphinx")
46
+
47
+ if upload:
48
+ ctx.run("twine upload dist/*")
49
+ else:
50
+ print("Not uploading (use --upload flag to upload).")
dktasklib/rule.py ADDED
@@ -0,0 +1,65 @@
1
+ import invoke
2
+
3
+
4
+ class BuildRule:
5
+ requires = []
6
+ after = []
7
+ _temp_mark = _perm_mark = False
8
+
9
+ def __init__(self, *args, **kwargs):
10
+ self.ctx = None
11
+ self.kwargs = kwargs
12
+ self.args = ()
13
+ if len(args) == 0:
14
+ return
15
+
16
+ ctx = None
17
+ first, rest = args[0], args[1:]
18
+ if isinstance(first, invoke.Context):
19
+ ctx = first
20
+ self.args = rest
21
+ else:
22
+ self.args = args
23
+
24
+ if ctx is not None:
25
+ self.run(ctx)
26
+
27
+ def run(self, ctx):
28
+ self.ctx = ctx
29
+ for task_obj in self.topsort(self.requires):
30
+ task_obj.run(ctx)
31
+
32
+ if self.needs_to_run():
33
+ self(*self.args, **self.kwargs)
34
+ for task_obj in self.topsort(self.after):
35
+ task_obj.run(ctx)
36
+
37
+ def __call__(self, *args, **kwargs):
38
+ raise NotImplementedError
39
+
40
+ def needs_to_run(self):
41
+ return True
42
+
43
+ def topsort(self, tasklist):
44
+ """Topological sort
45
+ """
46
+ permanent = set()
47
+ temporary = set()
48
+ res = []
49
+
50
+ def visit(task):
51
+ name = id(task)
52
+ if name in temporary:
53
+ raise ValueError("Circularity", name, res)
54
+ if name in permanent:
55
+ return
56
+ temporary.add(name)
57
+ for dependency in task.requires:
58
+ visit(dependency)
59
+ permanent.add(name)
60
+ temporary.remove(name)
61
+ res.append(task)
62
+
63
+ for task in tasklist:
64
+ visit(task)
65
+ return res
dktasklib/runners.py ADDED
@@ -0,0 +1,40 @@
1
+ import subprocess
2
+
3
+
4
+ class Result(str):
5
+ cmd = None
6
+ returncode = None
7
+
8
+
9
+ def run(cmdline, throw=False):
10
+ try:
11
+ output = subprocess.check_output(cmdline, shell=True)
12
+ res = Result(output.decode('u8'))
13
+ res.cmd = cmdline
14
+ return res
15
+ except subprocess.CalledProcessError as e:
16
+ if throw:
17
+ raise
18
+ output = e.output.decode('u8') if isinstance(e.output, bytes) else e.output
19
+ res = Result(output)
20
+ res.cmd = cmdline
21
+ res.returncode = e.returncode
22
+ return res
23
+
24
+
25
+ def command(executable, dryrun=False):
26
+ def _call(*args, **kwargs):
27
+ cmdline = " ".join(args)
28
+ for k, v in kwargs.items():
29
+ if v is True:
30
+ cmdline += " --" + k
31
+ else:
32
+ if ' ' in v:
33
+ v = '"%s"' % v
34
+ cmdline += ' --%s=%s' % (k, v)
35
+
36
+ if dryrun:
37
+ print(cmdline)
38
+ else:
39
+ return run(cmdline)
40
+ return _call
dktasklib/upversion.py ADDED
@@ -0,0 +1,165 @@
1
+ """
2
+ Update a package's version number.
3
+ """
4
+ import re
5
+ import os
6
+ import textwrap
7
+ import warnings
8
+
9
+ from dkfileutils.path import Path
10
+ from dktasklib.wintask import task
11
+ from invoke import Collection
12
+ from .rule import BuildRule
13
+ from .package import Package
14
+
15
+
16
+ def files_with_version_numbers(pkg=None):
17
+ pkg = pkg or Package()
18
+ root = pkg.root
19
+ default = {
20
+ root / 'setup.py',
21
+ root / 'package.json',
22
+ root / 'package.ini',
23
+ root / 'package.yaml',
24
+ root / 'dkbuild.yml',
25
+ root / 'docs' / 'conf.py',
26
+ root / 'src' / 'version.js',
27
+ root / 'js' / 'version.js',
28
+ root / 'styles' / 'index.less',
29
+ root / 'styles' / 'index.scss',
30
+ root / 'less' / 'index.less',
31
+ pkg.source / '__init__.py',
32
+ pkg.source / '_version.py',
33
+ pkg.source / 'package.json',
34
+ }
35
+ return default
36
+
37
+
38
+ def _replace_version(fname, cur_version, new_version):
39
+ """Replace the version string ``cur_version`` with the version string
40
+ ``new_version`` in ``fname``.
41
+ """
42
+ if not fname.exists():
43
+ return False
44
+
45
+ with open(fname, 'rb') as fp:
46
+ txt = fp.read()
47
+
48
+ cur_version = cur_version.encode('u8')
49
+ new_version = new_version.encode('u8')
50
+ if cur_version not in txt: # pragma: nocover
51
+ return False
52
+ occurences = txt.count(cur_version)
53
+ if occurences > 2: # pragma: nocover
54
+ warnings.warn(
55
+ "Found version string (%r) multiple times in %r, skipping" % (
56
+ cur_version, fname
57
+ )
58
+ )
59
+ txt = txt.replace(cur_version, new_version)
60
+
61
+ with open(fname, 'wb') as fp:
62
+ fp.write(txt)
63
+ return 1
64
+
65
+
66
+ @task(
67
+ autoprint=True,
68
+ default=True,
69
+ help=dict(
70
+ major="update major version number (set minor and patch to 0)",
71
+ minor="update minor version number (set patch to 0)",
72
+ patch="(default) update patch version",
73
+ tag="create a tag (git only)"
74
+ )
75
+ )
76
+ def upversion(ctx, major=False, minor=False, patch=False, tag=False):
77
+ """Update package version (default patch-level increase).
78
+ """
79
+ # while it may be tempting to make this task auto-tag the new version,
80
+ # this is generally a bad idea (bumpversion did this, and it was a mess)
81
+ pkg = Package()
82
+ if not (major or minor or patch):
83
+ patch = True # pragma: nocover
84
+ txt_version = pkg.version
85
+ cur_version = [int(n, 10) for n in txt_version.split('.')]
86
+ if major:
87
+ cur_version[0] += 1
88
+ cur_version[1] = 0
89
+ cur_version[2] = 0
90
+ elif minor:
91
+ cur_version[1] += 1
92
+ cur_version[2] = 0
93
+ elif patch:
94
+ cur_version[2] += 1
95
+ new_version = '.'.join([str(n) for n in cur_version])
96
+
97
+ changed = 0
98
+ changed_files = []
99
+ addlfiles = set()
100
+ if hasattr(ctx, 'versionfiles'):
101
+ addlfiles = {pkg.root / fname for fname in ctx.versionfiles}
102
+ for fname in addlfiles | files_with_version_numbers():
103
+ was_changed = _replace_version(fname, txt_version, new_version)
104
+ changed += was_changed
105
+ if was_changed:
106
+ changed_files.append(fname)
107
+ if changed == 0:
108
+ warnings.warn("I didn't change any files...!") # pragma: nocover
109
+ elif tag and pkg.vcs() == 'git':
110
+ with pkg.root.abspath().cd():
111
+ ctx.run('git tag -a v{version} -m "Version {version}"'.format(
112
+ version=new_version
113
+ ))
114
+ ctx.run('git push origin --tags')
115
+ print("changed version to %s in %d files" % (new_version, changed))
116
+ for fname in changed_files:
117
+ print(' ', fname)
118
+ return new_version
119
+
120
+
121
+ class UpdateTemplateVersion(BuildRule):
122
+ def __call__(self, fname):
123
+ fname = fname.format(**self.ctx)
124
+
125
+ if not os.path.exists(fname):
126
+ Path(self.ctx.pkg.root).makedirs(Path(fname).dirname())
127
+ with open(fname, 'w') as fp:
128
+ fp.write(textwrap.dedent("""
129
+ {% load staticfiles %}
130
+ {% with "0.0.0" as version %}
131
+ {# keep the above exactly as-is (it will be overwritten when compiling the css). #}
132
+ {% with app_path="PKGNAME/PKGNAME-"|add:version|add:".min.css" %}
133
+ {% if debug %}
134
+ <link rel="stylesheet" type="text/css" href='{% static "PKGNAME/PKGNAME.css" %}'>
135
+ {% else %}
136
+ <link rel="stylesheet" type="text/css" href="{% static app_path %}">
137
+ {% endif %}
138
+ {% endwith %}
139
+ {% endwith %}
140
+ """).replace("PKGNAME", self.ctx.pkg.name))
141
+
142
+ with open(fname, 'r') as fp:
143
+ txt = fp.read()
144
+
145
+ newtxt = re.sub(
146
+ r'{% with "(\d+\.\d+\.\d+)" as version',
147
+ '{{% with "{}" as version'.format(self.ctx.pkg.version),
148
+ txt
149
+ )
150
+ with open(fname, 'w') as fp:
151
+ fp.write(newtxt)
152
+ print('Updated {% import %} template:', fname)
153
+
154
+
155
+ ns = Collection(
156
+ 'upversion',
157
+ upversion,
158
+ )
159
+ ns.configure({
160
+ 'force': False,
161
+ 'pkg': {
162
+ 'name': '<package-name>',
163
+ 'version': '<version-string>',
164
+ },
165
+ })
@@ -0,0 +1,90 @@
1
+ from dktasklib.wintask import task
2
+ import base64
3
+ import os
4
+ import re
5
+ from urllib.request import urlopen
6
+
7
+
8
+ def inline_data(data, type='image/png', name=""):
9
+ """Inline (encode) the ``data``.
10
+ """
11
+ if len(data) > 10 * 1024:
12
+ print("%s is too big (%d bytes), max is 10KB" % (name, len(data)))
13
+ return name
14
+
15
+ encoded = base64.b64encode(data).decode('ascii')
16
+ return 'data:{type};base64,{encoded}'.format(
17
+ type=type,
18
+ encoded=encoded.replace('\n', '')
19
+ )
20
+
21
+
22
+ def inline_file(fname):
23
+ """Inline from a file source named ``fname``.
24
+ """
25
+ ext = os.path.splitext(fname)[1] # ".png"
26
+ return inline_data(
27
+ open(fname, 'rb').read(),
28
+ type='image/' + ext[1:], # remove the dot
29
+ name=fname
30
+ )
31
+
32
+
33
+ def inline_url(uri):
34
+ """Fetch ``uri`` and inline.
35
+ """
36
+ with urlopen(uri) as fp:
37
+ return inline_data(
38
+ fp.read(),
39
+ type=fp.headers['Content-Type'],
40
+ name=uri
41
+ )
42
+
43
+
44
+ @task(
45
+ default=True
46
+ )
47
+ def inline(ctx, fname):
48
+ """Compile a file of inlines to data uris.
49
+
50
+ The file should be named foo.less.inline, and contain a list
51
+ of variables, i.e.::
52
+
53
+ @foo: "http://.." ;
54
+ @bar: "path/to/local/file.png";
55
+
56
+ into::
57
+
58
+ @foo: "data:image/png;base64,..."
59
+ @bar: "data:image/png;base64,..."
60
+
61
+ for all resources that are less than 10KB and writes the result
62
+ to `foo.less` (i.e. the filname without the `.inline` extension).
63
+
64
+ If the resource is too big, the original value is passed through,
65
+ so if you specify a url it should always be safe (but slower to build).
66
+ """
67
+ lines = open(fname).readlines()
68
+ output_fname = os.path.splitext(fname)[0]
69
+ with open(output_fname, 'w') as fp:
70
+ for line in lines:
71
+ if line.startswith('@'):
72
+ varname, content = line.split(':', 1)
73
+ content = content.strip().rstrip(';').strip("\"'")
74
+ if re.match(r'^https?://', content):
75
+ data = inline_url(content)
76
+ else:
77
+ data = inline_file(content)
78
+ line = '{varname}: "{content}";\n'.format(
79
+ varname=varname,
80
+ content=data
81
+ )
82
+ fp.write(line)
83
+
84
+
85
+ @task
86
+ def list_urls(ctx, filename):
87
+ """List all url(..) targets in the filename.
88
+ """
89
+ for match in re.findall(r'url\((.*?)\)', open(filename).read()):
90
+ print(match)