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.
- dk_tasklib-3.0.9.dist-info/METADATA +78 -0
- dk_tasklib-3.0.9.dist-info/RECORD +35 -0
- dk_tasklib-3.0.9.dist-info/WHEEL +5 -0
- dk_tasklib-3.0.9.dist-info/entry_points.txt +2 -0
- dk_tasklib-3.0.9.dist-info/top_level.txt +1 -0
- dktasklib/__init__.py +3 -0
- dktasklib/_version.py +1 -0
- dktasklib/clean.py +8 -0
- dktasklib/commands.py +95 -0
- dktasklib/concat.py +76 -0
- dktasklib/docs.py +225 -0
- dktasklib/entry_points/__init__.py +5 -0
- dktasklib/entry_points/confbase.py +172 -0
- dktasklib/entry_points/dktasklibcmd.py +102 -0
- dktasklib/entry_points/pytemplate.py +16 -0
- dktasklib/entry_points/taskbase.py +135 -0
- dktasklib/environment.py +11 -0
- dktasklib/executables.py +200 -0
- dktasklib/help.py +17 -0
- dktasklib/jstools.py +320 -0
- dktasklib/lessc.py +111 -0
- dktasklib/manage.py +71 -0
- dktasklib/npm.py +19 -0
- dktasklib/package/__init__.py +31 -0
- dktasklib/package/package_interface.py +103 -0
- dktasklib/pset.py +122 -0
- dktasklib/publish.py +50 -0
- dktasklib/rule.py +65 -0
- dktasklib/runners.py +40 -0
- dktasklib/upversion.py +165 -0
- dktasklib/urlinliner.py +90 -0
- dktasklib/utils.py +234 -0
- dktasklib/version.py +103 -0
- dktasklib/watch.py +90 -0
- dktasklib/wintask.py +4 -0
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
|
+
})
|
dktasklib/urlinliner.py
ADDED
|
@@ -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)
|