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/utils.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import string
|
|
3
|
+
import sys
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
|
|
6
|
+
from dkfileutils.path import Path
|
|
7
|
+
import operator
|
|
8
|
+
|
|
9
|
+
join = os.path.join
|
|
10
|
+
null = "NUL" if sys.platform == 'win32' else '/dev/null'
|
|
11
|
+
win32 = sys.platform == 'win32'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Column(dict):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# from dkbuild/utils/tableprinter.py
|
|
19
|
+
def format_table(data, *columns, **kw):
|
|
20
|
+
"""Format data into a .rst-like formatted table.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
data: list of dict/object (having fields corresponding to `columns`)
|
|
24
|
+
columns: one-or-more
|
|
25
|
+
|
|
26
|
+
dict(title='',
|
|
27
|
+
field='',
|
|
28
|
+
align='left',
|
|
29
|
+
format=lambda x:x)
|
|
30
|
+
|
|
31
|
+
title is optional and defaults to field, other defaults as
|
|
32
|
+
indicated.
|
|
33
|
+
kw[get_field]: function to extract fields from data objects, ie.:
|
|
34
|
+
``get_field(data[i], columns[i]['field']) => data[i].field``
|
|
35
|
+
Default value is :func:`operator.itemgetter`
|
|
36
|
+
|
|
37
|
+
Usage::
|
|
38
|
+
|
|
39
|
+
>>> print format_table(
|
|
40
|
+
... [
|
|
41
|
+
... dict(field1=42, field2='world', field3='n/a'),
|
|
42
|
+
... dict(field1=12, field2='hello')
|
|
43
|
+
... ],
|
|
44
|
+
... Column(field='field1', format=str, align='center'),
|
|
45
|
+
... Column(field='field2', title='f2 ', align='right')
|
|
46
|
+
... )
|
|
47
|
+
====== =============
|
|
48
|
+
field1 f2
|
|
49
|
+
====== =============
|
|
50
|
+
42 world
|
|
51
|
+
12 hello
|
|
52
|
+
====== =============
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
fields = [col['field'] for col in columns]
|
|
56
|
+
titles = [col.get('title', col['field']) for col in columns]
|
|
57
|
+
identity = lambda x: x # noqa
|
|
58
|
+
fmt = [col.get('format', identity) for col in columns]
|
|
59
|
+
_align = [col.get('align', 'left') for col in columns]
|
|
60
|
+
_alignselect = {'left': '', 'right': '>', 'center': '^'}
|
|
61
|
+
align = [_alignselect[a] for a in _align]
|
|
62
|
+
getter = kw.get('get_field', operator.itemgetter)
|
|
63
|
+
get_fields = [getter(field) for field in fields]
|
|
64
|
+
|
|
65
|
+
field_lengths = [len(t) for t in titles]
|
|
66
|
+
for item in data:
|
|
67
|
+
values = [fmtfn(getfn(item)) for fmtfn, getfn in zip(fmt, get_fields)]
|
|
68
|
+
value_lengths = [max([0] + [len(ln) for ln in v.splitlines()]) for v in
|
|
69
|
+
values]
|
|
70
|
+
field_lengths = [max(fieldval) for fieldval in zip(value_lengths,
|
|
71
|
+
field_lengths)]
|
|
72
|
+
rst_line = ['=' * clen for clen in field_lengths]
|
|
73
|
+
thead = [
|
|
74
|
+
rst_line,
|
|
75
|
+
['%-*s' % (clen, title) for clen, title in zip(field_lengths, titles)],
|
|
76
|
+
rst_line
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
def get_lineno(n, val):
|
|
80
|
+
lines = val.splitlines()
|
|
81
|
+
if n >= len(lines):
|
|
82
|
+
return ''
|
|
83
|
+
return lines[n]
|
|
84
|
+
|
|
85
|
+
rows = []
|
|
86
|
+
for item in data:
|
|
87
|
+
values = [fmtfn(getfn(item)) for fmtfn, getfn in zip(fmt, get_fields)]
|
|
88
|
+
multiline = [v.count('\n') for v in values]
|
|
89
|
+
if any(multiline):
|
|
90
|
+
for i in range(max(multiline) + 1):
|
|
91
|
+
line = [get_lineno(i, val) for val in values]
|
|
92
|
+
rows.append([
|
|
93
|
+
'{:{align}{width}}'.format(value, align=a, width=w)
|
|
94
|
+
# line, align=align[i], width=field_lengths[i])
|
|
95
|
+
for a, w, value in zip(align, field_lengths, line)
|
|
96
|
+
])
|
|
97
|
+
else:
|
|
98
|
+
rows.append(
|
|
99
|
+
['{:{align}{width}}'.format(value, align=a, width=w)
|
|
100
|
+
for a, w, value in zip(align, field_lengths, values)]
|
|
101
|
+
)
|
|
102
|
+
if kw.get('rowspace'):
|
|
103
|
+
rows.append(['' * len(columns)])
|
|
104
|
+
if kw.get('rowspace'):
|
|
105
|
+
del rows[-1]
|
|
106
|
+
tfoot = [rst_line]
|
|
107
|
+
header = '\n'.join([' '.join(row) for row in thead])
|
|
108
|
+
body = '\n'.join([' '.join(row) for row in rows])
|
|
109
|
+
footer = '\n'.join([' '.join(row) for row in tfoot])
|
|
110
|
+
return '\n'.join([header, body, footer])
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def dest_is_newer_than_source(src, dst):
|
|
114
|
+
"""Check if destination is newer than source.
|
|
115
|
+
|
|
116
|
+
Usage::
|
|
117
|
+
|
|
118
|
+
if not force and dest_is_newer_than_source(source, dest):
|
|
119
|
+
print 'babel:', dest, 'is up-to-date.'
|
|
120
|
+
return dest
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
if not os.path.exists(dst):
|
|
124
|
+
return False
|
|
125
|
+
if not os.path.exists(src):
|
|
126
|
+
raise ValueError("Source does not exist: " + str(src))
|
|
127
|
+
return os.path.getmtime(src) < os.path.getmtime(dst)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class _MissingDottedString(str):
|
|
131
|
+
def __getattr__(self, attr):
|
|
132
|
+
return _MissingDottedString(self[:-1] + '.' + attr + '}')
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class _MissingContext(dict):
|
|
136
|
+
def __missing__(self, key):
|
|
137
|
+
return _MissingDottedString('{%s}' % key)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def fmt(s, ctx):
|
|
141
|
+
"""Use the mapping `ctx` as a formatter for the {new.style} formatting
|
|
142
|
+
string `s`.
|
|
143
|
+
"""
|
|
144
|
+
return string.Formatter().vformat(s, (), _MissingContext(ctx))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def switch_extension(fname, ext="", old_ext=None):
|
|
148
|
+
"""Switch file extension on `fname` to `ext`. Returns the resulting
|
|
149
|
+
file name.
|
|
150
|
+
|
|
151
|
+
Usage::
|
|
152
|
+
|
|
153
|
+
switch_extension('a/b/c/d.less', '.css')
|
|
154
|
+
|
|
155
|
+
"""
|
|
156
|
+
name, _ext = os.path.splitext(fname)
|
|
157
|
+
if old_ext:
|
|
158
|
+
assert old_ext == _ext
|
|
159
|
+
return name + ext
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def filename(fname):
|
|
163
|
+
"""Return only the file name (removes the path)
|
|
164
|
+
"""
|
|
165
|
+
return os.path.split(fname)[1]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@contextmanager
|
|
169
|
+
def message(s):
|
|
170
|
+
try:
|
|
171
|
+
print((' %s ' % s).center(80, '-'))
|
|
172
|
+
yield
|
|
173
|
+
except: # noqa
|
|
174
|
+
print('error =====>', s, '<====== error')
|
|
175
|
+
raise
|
|
176
|
+
else:
|
|
177
|
+
print((' (ok: %s) ' % s).center(80, '='))
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@contextmanager
|
|
181
|
+
def env(**kw):
|
|
182
|
+
"""Context amanger to temporarily override environment variables.
|
|
183
|
+
"""
|
|
184
|
+
currentvals = {k: os.environ.get(k) for k in kw}
|
|
185
|
+
for k, v in kw.items():
|
|
186
|
+
os.environ[k] = str(v)
|
|
187
|
+
try:
|
|
188
|
+
yield
|
|
189
|
+
finally:
|
|
190
|
+
for k in kw:
|
|
191
|
+
if currentvals[k] is None:
|
|
192
|
+
os.environ.pop(k, None)
|
|
193
|
+
else:
|
|
194
|
+
os.environ[k] = currentvals[k]
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@contextmanager
|
|
198
|
+
def cd(directory):
|
|
199
|
+
"""Context manager to change directory.
|
|
200
|
+
|
|
201
|
+
Usage::
|
|
202
|
+
|
|
203
|
+
with cd('foo/bar'):
|
|
204
|
+
# current directory is now foo/bar
|
|
205
|
+
# current directory restored.
|
|
206
|
+
|
|
207
|
+
"""
|
|
208
|
+
cwd = os.getcwd()
|
|
209
|
+
try:
|
|
210
|
+
os.chdir(directory)
|
|
211
|
+
yield
|
|
212
|
+
finally:
|
|
213
|
+
os.chdir(cwd)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def find_pymodule(dotted_name):
|
|
217
|
+
"""Find the directory of a python module, without importing it.
|
|
218
|
+
"""
|
|
219
|
+
name = dotted_name.split('.', 1)[0]
|
|
220
|
+
for p in sys.path:
|
|
221
|
+
pth = Path(p)
|
|
222
|
+
if not pth:
|
|
223
|
+
continue
|
|
224
|
+
try:
|
|
225
|
+
if name in pth and (pth/name).isdir():
|
|
226
|
+
return pth/name
|
|
227
|
+
if name + '.py' in pth:
|
|
228
|
+
return pth
|
|
229
|
+
except OSError:
|
|
230
|
+
continue
|
|
231
|
+
except Exception as e:
|
|
232
|
+
print('error', pth, e)
|
|
233
|
+
raise
|
|
234
|
+
raise ValueError("Path not found for: " + dotted_name)
|
dktasklib/version.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import hashlib
|
|
3
|
+
|
|
4
|
+
from dkfileutils.path import Path
|
|
5
|
+
from dktasklib.wintask import task
|
|
6
|
+
from invoke import Collection
|
|
7
|
+
from dktasklib.concat import copy
|
|
8
|
+
from .package import Package
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@task(
|
|
12
|
+
default=True,
|
|
13
|
+
autoprint=True, # print return value
|
|
14
|
+
)
|
|
15
|
+
def version(ctx):
|
|
16
|
+
"""Print this package's version number.
|
|
17
|
+
"""
|
|
18
|
+
return Package().version
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def min_name(fname, min='.min'):
|
|
22
|
+
"""Adds a `.min` extension before the last file extension.
|
|
23
|
+
"""
|
|
24
|
+
name, ext = os.path.splitext(fname)
|
|
25
|
+
return name + min + ext
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def versioned_name(fname):
|
|
29
|
+
"""Returns a template string containing `{version}` in the correct
|
|
30
|
+
place.
|
|
31
|
+
"""
|
|
32
|
+
if '.min.' in fname:
|
|
33
|
+
pre, post = fname.split('.min.')
|
|
34
|
+
return pre + '-{version}.min.' + post
|
|
35
|
+
else:
|
|
36
|
+
return min_name(fname, '-{version}')
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
version_name = versioned_name
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_version(ctx, fname, kind='pkg'):
|
|
43
|
+
"""Return the version number for fname.
|
|
44
|
+
"""
|
|
45
|
+
fname = Path(fname)
|
|
46
|
+
if kind == "pkg":
|
|
47
|
+
if not hasattr(ctx, 'pkg'):
|
|
48
|
+
ctx.pkg = Package()
|
|
49
|
+
return ctx.pkg.version
|
|
50
|
+
|
|
51
|
+
elif kind == "hash":
|
|
52
|
+
directory = fname.dirname() or Path('.')
|
|
53
|
+
md5 = directory / '.md5'
|
|
54
|
+
if md5.exists():
|
|
55
|
+
return md5.open().read()
|
|
56
|
+
return hashlib.md5(open(fname, 'rb').read()).hexdigest()
|
|
57
|
+
return ""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def copy_to_version(ctx, source, outputdir=None, kind="pkg", force=False):
|
|
61
|
+
"""Copy source with version number to `outputdir`.
|
|
62
|
+
|
|
63
|
+
The version type is specified by the ``kind`` parameter and can be
|
|
64
|
+
either "pkg" (package version), "svn" (current subversion revision
|
|
65
|
+
number), or "hash" (the md5 hash of the file's contents).
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
(str) output file name
|
|
69
|
+
"""
|
|
70
|
+
# where to place the versioned file..
|
|
71
|
+
source = Path(source)
|
|
72
|
+
outputdir = Path(outputdir) if outputdir else source.dirname() or Path('.')
|
|
73
|
+
outputdir.makedirs()
|
|
74
|
+
dst_fname = source.basename()
|
|
75
|
+
if '{version}' not in str(dst_fname):
|
|
76
|
+
dst_fname = versioned_name(dst_fname)
|
|
77
|
+
dst = outputdir / dst_fname.format(version=get_version(ctx, source, kind))
|
|
78
|
+
|
|
79
|
+
if force or not os.path.exists(dst):
|
|
80
|
+
copy(ctx, source, dst, force=force)
|
|
81
|
+
|
|
82
|
+
elif open(source).read() != open(dst).read():
|
|
83
|
+
print("""
|
|
84
|
+
Filename already exists, add --force or call upversion: {}
|
|
85
|
+
""".format(dst))
|
|
86
|
+
|
|
87
|
+
return dst
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
add_version = copy_to_version
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
ns = Collection(
|
|
94
|
+
'version',
|
|
95
|
+
version,
|
|
96
|
+
)
|
|
97
|
+
ns.configure({
|
|
98
|
+
'force': False,
|
|
99
|
+
'pkg': {
|
|
100
|
+
'name': '<package-name>',
|
|
101
|
+
'version': '<version-string>',
|
|
102
|
+
},
|
|
103
|
+
})
|
dktasklib/watch.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Usage::
|
|
3
|
+
|
|
4
|
+
@task
|
|
5
|
+
def watch(ctx):
|
|
6
|
+
watcher = Watcher(ctx)
|
|
7
|
+
watcher.watch_file(
|
|
8
|
+
name='{pkg.source}/less/{pkg.name}.less',
|
|
9
|
+
action=lambda e: build(ctx, less=True)
|
|
10
|
+
)
|
|
11
|
+
watcher.watch_directory(
|
|
12
|
+
path='{pkg.source}/js', ext='.jsx',
|
|
13
|
+
action=lambda e: build(ctx, js=True)
|
|
14
|
+
)
|
|
15
|
+
watcher.watch_directory(
|
|
16
|
+
path='{pkg.docs}', ext='.rst',
|
|
17
|
+
action=lambda e: build(ctx, docs=True)
|
|
18
|
+
)
|
|
19
|
+
watcher.start()
|
|
20
|
+
|
|
21
|
+
ns = Collection(..., watch, ...)
|
|
22
|
+
ns.configure({
|
|
23
|
+
'pkg': Package()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
import time
|
|
28
|
+
from dkfileutils.path import Path
|
|
29
|
+
from watchdog.observers import Observer
|
|
30
|
+
from watchdog.events import FileSystemEventHandler
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class FileModified(FileSystemEventHandler):
|
|
34
|
+
def __init__(self, ctx, fname, action):
|
|
35
|
+
super().__init__()
|
|
36
|
+
self.ctx = ctx
|
|
37
|
+
self.fname = Path(fname.format(pkg=ctx.pkg)).abspath()
|
|
38
|
+
self.action = action
|
|
39
|
+
|
|
40
|
+
def on_modified(self, event):
|
|
41
|
+
if Path(event.src_path).abspath() != self.fname:
|
|
42
|
+
return
|
|
43
|
+
self.action(event)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class DirectoryModified(FileSystemEventHandler):
|
|
47
|
+
def __init__(self, ctx, path, ext, action):
|
|
48
|
+
super().__init__()
|
|
49
|
+
self.ctx = ctx
|
|
50
|
+
self.path = Path(path.format(pkg=ctx.pkg)).abspath()
|
|
51
|
+
self.ext = ext
|
|
52
|
+
self.action = action
|
|
53
|
+
|
|
54
|
+
def on_modified(self, event):
|
|
55
|
+
event_path = Path(event.src_path).abspath()
|
|
56
|
+
if not event_path.startswith(self.path):
|
|
57
|
+
return
|
|
58
|
+
if self.ext and not event_path.endswith(self.ext):
|
|
59
|
+
return
|
|
60
|
+
self.action(event)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Watcher:
|
|
64
|
+
def __init__(self, ctx):
|
|
65
|
+
self.ctx = ctx
|
|
66
|
+
self.observer = Observer()
|
|
67
|
+
|
|
68
|
+
def watch_file(self, name, action):
|
|
69
|
+
self.observer.schedule(
|
|
70
|
+
FileModified(self.ctx, name, action),
|
|
71
|
+
self.ctx.pkg.root,
|
|
72
|
+
recursive=True
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def watch_directory(self, path, ext, action):
|
|
76
|
+
self.observer.schedule(
|
|
77
|
+
DirectoryModified(self.ctx, path, ext, action),
|
|
78
|
+
self.ctx.pkg.root,
|
|
79
|
+
recursive=True
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def start(self):
|
|
83
|
+
print('watching for changes.. (Ctrl-C to exit)')
|
|
84
|
+
self.observer.start()
|
|
85
|
+
try:
|
|
86
|
+
while 1:
|
|
87
|
+
time.sleep(1)
|
|
88
|
+
except KeyboardInterrupt:
|
|
89
|
+
self.observer.stop()
|
|
90
|
+
self.observer.join()
|
dktasklib/wintask.py
ADDED