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/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
@@ -0,0 +1,4 @@
1
+ from invoke import task
2
+
3
+
4
+ __all__ = ['task']