borg-space 2.1__tar.gz → 2.3__tar.gz
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.
- borg_space-2.1/README.rst → borg_space-2.3/PKG-INFO +55 -2
- borg_space-2.1/PKG-INFO → borg_space-2.3/README.rst +26 -30
- {borg_space-2.1 → borg_space-2.3}/borg_space/config.py +44 -24
- {borg_space-2.1 → borg_space-2.3}/borg_space/main.py +11 -6
- {borg_space-2.1 → borg_space-2.3}/pyproject.toml +5 -4
- borg_space-2.1/borg_space/trees.py +0 -94
- {borg_space-2.1 → borg_space-2.3}/LICENSE +0 -0
- {borg_space-2.1 → borg_space-2.3}/borg_space/__init__.py +0 -0
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: borg_space
|
|
3
|
+
Version: 2.3
|
|
4
|
+
Summary: Accessory for Emborg used to report and track the size of your Borg repositories
|
|
5
|
+
Keywords: emborg,borg,backups
|
|
6
|
+
Author-email: Ken Kundert <borg-space@nurdletech.com>
|
|
7
|
+
Requires-Python: >=3.6
|
|
8
|
+
Description-Content-Type: text/x-rst
|
|
9
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
10
|
+
Classifier: Natural Language :: English
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Utilities
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: appdirs
|
|
16
|
+
Requires-Dist: arrow
|
|
17
|
+
Requires-Dist: docopt
|
|
18
|
+
Requires-Dist: inform>=1.34
|
|
19
|
+
Requires-Dist: matplotlib
|
|
20
|
+
Requires-Dist: nestedtext
|
|
21
|
+
Requires-Dist: quantiphy
|
|
22
|
+
Requires-Dist: shlib
|
|
23
|
+
Requires-Dist: voluptuous>=0.14
|
|
24
|
+
Project-URL: changelog, https://github.com/KenKundert/ntlog/blob/master/CHANGELOG.rst
|
|
25
|
+
Project-URL: documentation, https://github.com/KenKundert/borg-space/blob/master/README.rst
|
|
26
|
+
Project-URL: homepage, https://github.com/kenkundert/borg-space
|
|
27
|
+
Project-URL: repository, https://github.com/kenkundert/borg-space
|
|
28
|
+
|
|
1
29
|
Borg-Space — Report and track the size of your Emborg repositories
|
|
2
30
|
==================================================================
|
|
3
31
|
|
|
@@ -11,8 +39,8 @@ Borg-Space — Report and track the size of your Emborg repositories
|
|
|
11
39
|
:target: https://pypi.python.org/pypi/borg-space/
|
|
12
40
|
|
|
13
41
|
:Author: Ken Kundert
|
|
14
|
-
:Version: 2.
|
|
15
|
-
:Released:
|
|
42
|
+
:Version: 2.3
|
|
43
|
+
:Released: 2025-05-11
|
|
16
44
|
|
|
17
45
|
*Borg-Space* is an accessory for Emborg_. It reports on the space consumed by
|
|
18
46
|
your *BorgBackup* repositories. You can get this information using the
|
|
@@ -32,6 +60,10 @@ To show the size of one or more repositories, simply run::
|
|
|
32
60
|
# borg-space home
|
|
33
61
|
home: 12.81 GB
|
|
34
62
|
|
|
63
|
+
This reports on the latest repository size and, of course, assumes that you have
|
|
64
|
+
already run *emborg create*. You must not use the ``--fast`` command line
|
|
65
|
+
option when running *create*.
|
|
66
|
+
|
|
35
67
|
You can specify any number of repositories, and they can be composites. In the
|
|
36
68
|
following example, *home* is an alias that expands to *borgbase* and *rsync*::
|
|
37
69
|
|
|
@@ -317,7 +349,28 @@ Install with::
|
|
|
317
349
|
> pip3 install borg-space
|
|
318
350
|
|
|
319
351
|
|
|
352
|
+
Borg 2
|
|
353
|
+
------
|
|
354
|
+
|
|
355
|
+
Borg_ 2 will be released soon, and with it will come Assimilate_, the next
|
|
356
|
+
generation of Emborg_. *Assimilate* is intended to be used with *Borg 2.0* and
|
|
357
|
+
beyond while *Emborg* would be used with older versions of *Borg*. Currently
|
|
358
|
+
*Borg-Space* does not support *Assimilate* directly, but the *latest.nt* files
|
|
359
|
+
produced by *Assimilate* are compatible with *Borg-Space*, only their location
|
|
360
|
+
differs. You can get the current version of *Borg-Space* to read *Assimilate*
|
|
361
|
+
*latest.nt* files by simply creating a symbolic link from the expected location
|
|
362
|
+
to the actual location. For example, if you convert your *home* repository from
|
|
363
|
+
*Emborg* to *Assimilate*, you can use the following commands to get *Borg-Space*
|
|
364
|
+
to use the *latest.nt* file produced by *Assimilate*::
|
|
365
|
+
|
|
366
|
+
cd ~/.local/share/emborg
|
|
367
|
+
rm home.latest.nt
|
|
368
|
+
ln -s ../assimilate/home.latest.nt .
|
|
369
|
+
|
|
370
|
+
.. _assimilate: https://assimilate.readthedocs.io
|
|
371
|
+
.. _borg: https://borgbackup.readthedocs.io
|
|
320
372
|
.. _emborg: https://emborg.readthedocs.io
|
|
321
373
|
.. _nestedtext: https://nestedtext.org
|
|
322
374
|
.. _arrow: https://arrow.readthedocs.io/en/latest/guide.html#supported-tokens
|
|
323
375
|
.. _quantiphy: https://quantiphy.readthedocs.io/en/stable/api.html#quantiphy.Quantity.format
|
|
376
|
+
|
|
@@ -1,30 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: borg_space
|
|
3
|
-
Version: 2.1
|
|
4
|
-
Summary: Accessory for Emborg used to report and track the size of your Borg repositories
|
|
5
|
-
Keywords: emborg,borg,backups
|
|
6
|
-
Author-email: Ken Kundert <emborg@nurdletech.com>
|
|
7
|
-
Requires-Python: >=3.6
|
|
8
|
-
Description-Content-Type: text/x-rst
|
|
9
|
-
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
10
|
-
Classifier: Natural Language :: English
|
|
11
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Topic :: Utilities
|
|
14
|
-
Requires-Dist: appdirs
|
|
15
|
-
Requires-Dist: arrow
|
|
16
|
-
Requires-Dist: docopt
|
|
17
|
-
Requires-Dist: inform
|
|
18
|
-
Requires-Dist: matplotlib
|
|
19
|
-
Requires-Dist: nestedtext
|
|
20
|
-
Requires-Dist: quantiphy
|
|
21
|
-
Requires-Dist: shlib
|
|
22
|
-
Requires-Dist: voluptuous
|
|
23
|
-
Project-URL: changelog, https://github.com/KenKundert/ntlog/blob/master/CHANGELOG.rst
|
|
24
|
-
Project-URL: documentation, https://github.com/KenKundert/borg-space/blob/master/README.rst
|
|
25
|
-
Project-URL: homepage, https://github.com/kenkundert/borg-space
|
|
26
|
-
Project-URL: repository, https://github.com/kenkundert/borg-space
|
|
27
|
-
|
|
28
1
|
Borg-Space — Report and track the size of your Emborg repositories
|
|
29
2
|
==================================================================
|
|
30
3
|
|
|
@@ -38,8 +11,8 @@ Borg-Space — Report and track the size of your Emborg repositories
|
|
|
38
11
|
:target: https://pypi.python.org/pypi/borg-space/
|
|
39
12
|
|
|
40
13
|
:Author: Ken Kundert
|
|
41
|
-
:Version: 2.
|
|
42
|
-
:Released:
|
|
14
|
+
:Version: 2.3
|
|
15
|
+
:Released: 2025-05-11
|
|
43
16
|
|
|
44
17
|
*Borg-Space* is an accessory for Emborg_. It reports on the space consumed by
|
|
45
18
|
your *BorgBackup* repositories. You can get this information using the
|
|
@@ -59,6 +32,10 @@ To show the size of one or more repositories, simply run::
|
|
|
59
32
|
# borg-space home
|
|
60
33
|
home: 12.81 GB
|
|
61
34
|
|
|
35
|
+
This reports on the latest repository size and, of course, assumes that you have
|
|
36
|
+
already run *emborg create*. You must not use the ``--fast`` command line
|
|
37
|
+
option when running *create*.
|
|
38
|
+
|
|
62
39
|
You can specify any number of repositories, and they can be composites. In the
|
|
63
40
|
following example, *home* is an alias that expands to *borgbase* and *rsync*::
|
|
64
41
|
|
|
@@ -344,8 +321,27 @@ Install with::
|
|
|
344
321
|
> pip3 install borg-space
|
|
345
322
|
|
|
346
323
|
|
|
324
|
+
Borg 2
|
|
325
|
+
------
|
|
326
|
+
|
|
327
|
+
Borg_ 2 will be released soon, and with it will come Assimilate_, the next
|
|
328
|
+
generation of Emborg_. *Assimilate* is intended to be used with *Borg 2.0* and
|
|
329
|
+
beyond while *Emborg* would be used with older versions of *Borg*. Currently
|
|
330
|
+
*Borg-Space* does not support *Assimilate* directly, but the *latest.nt* files
|
|
331
|
+
produced by *Assimilate* are compatible with *Borg-Space*, only their location
|
|
332
|
+
differs. You can get the current version of *Borg-Space* to read *Assimilate*
|
|
333
|
+
*latest.nt* files by simply creating a symbolic link from the expected location
|
|
334
|
+
to the actual location. For example, if you convert your *home* repository from
|
|
335
|
+
*Emborg* to *Assimilate*, you can use the following commands to get *Borg-Space*
|
|
336
|
+
to use the *latest.nt* file produced by *Assimilate*::
|
|
337
|
+
|
|
338
|
+
cd ~/.local/share/emborg
|
|
339
|
+
rm home.latest.nt
|
|
340
|
+
ln -s ../assimilate/home.latest.nt .
|
|
341
|
+
|
|
342
|
+
.. _assimilate: https://assimilate.readthedocs.io
|
|
343
|
+
.. _borg: https://borgbackup.readthedocs.io
|
|
347
344
|
.. _emborg: https://emborg.readthedocs.io
|
|
348
345
|
.. _nestedtext: https://nestedtext.org
|
|
349
346
|
.. _arrow: https://arrow.readthedocs.io/en/latest/guide.html#supported-tokens
|
|
350
347
|
.. _quantiphy: https://quantiphy.readthedocs.io/en/stable/api.html#quantiphy.Quantity.format
|
|
351
|
-
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
# IMPORTS {{{1
|
|
4
4
|
from appdirs import user_config_dir
|
|
5
5
|
from inform import (
|
|
6
|
-
Error, conjoin, error,
|
|
7
|
-
is_str, is_mapping, is_collection, plural, os_error,
|
|
6
|
+
Error, conjoin, error, full_stop,
|
|
7
|
+
is_str, is_mapping, is_collection, plural, os_error, terminate_if_errors
|
|
8
8
|
)
|
|
9
9
|
from quantiphy import Quantity
|
|
10
10
|
from shlib import to_path, Run, set_prefs
|
|
@@ -37,6 +37,7 @@ voluptuous_error_msg_mappings = {
|
|
|
37
37
|
"extra keys not allowed": ("unknown key", "key"),
|
|
38
38
|
"expected a dictionary": ("expected key:value pair", "value"),
|
|
39
39
|
}
|
|
40
|
+
voluptuous_key_prefix = "key contains"
|
|
40
41
|
hostname = socket.gethostname().split('.')[0]
|
|
41
42
|
# version of the hostname (the hostname without any domain name)
|
|
42
43
|
username = pwd.getpwuid(os.getuid()).pw_name
|
|
@@ -112,7 +113,7 @@ class Repository:
|
|
|
112
113
|
raw_data = nt.loads(content)
|
|
113
114
|
self.latest = data = {}
|
|
114
115
|
if 'repository size' in raw_data:
|
|
115
|
-
data['size'] = Quantity(raw_data['repository size'], 'B')
|
|
116
|
+
data['size'] = Quantity(raw_data['repository size'], 'B', binary=True)
|
|
116
117
|
if 'create last run' in raw_data:
|
|
117
118
|
data['last_create'] = arrow.get(raw_data['create last run'])
|
|
118
119
|
if 'prune last run' in raw_data:
|
|
@@ -167,21 +168,33 @@ def to_list(args):
|
|
|
167
168
|
if is_str(args):
|
|
168
169
|
args = args.split()
|
|
169
170
|
if is_mapping(args):
|
|
170
|
-
raise Invalid(f"
|
|
171
|
+
raise Invalid(f"expected a list or string")
|
|
171
172
|
return args
|
|
172
173
|
|
|
173
174
|
# a_name() {{{2
|
|
174
|
-
def a_name(arg):
|
|
175
|
+
def a_name(arg, is_key=False):
|
|
175
176
|
# names are expected to be identifiers except that dashes are allowed
|
|
177
|
+
# also allow names starting with a digit
|
|
176
178
|
if not arg:
|
|
177
179
|
return arg
|
|
178
180
|
if not is_str(arg):
|
|
179
|
-
raise Invalid("expected string")
|
|
180
|
-
cleaned = arg.replace('-', '
|
|
181
|
+
raise Invalid("expected a string")
|
|
182
|
+
cleaned = '_' + arg.replace('-', '_')
|
|
183
|
+
# add prefix to allow leading digits, replace '-' to allow dashes
|
|
181
184
|
if not cleaned.isidentifier():
|
|
182
|
-
|
|
185
|
+
from string import ascii_letters as letters, digits
|
|
186
|
+
invalid = ''.join(sorted(set(cleaned) - set(letters + digits + '-_')))
|
|
187
|
+
desc = f"{plural(invalid):/an invalid character/invalid characters}"
|
|
188
|
+
if is_key:
|
|
189
|
+
raise Invalid(f"key contains {desc}: ‘{invalid}’")
|
|
190
|
+
else:
|
|
191
|
+
raise Invalid(f"value contains {desc}: ‘{invalid}’")
|
|
183
192
|
return arg
|
|
184
193
|
|
|
194
|
+
# key_as_name() {{{2
|
|
195
|
+
def key_as_name(arg):
|
|
196
|
+
return a_name(arg, is_key=True)
|
|
197
|
+
|
|
185
198
|
# a_spec() {{{2
|
|
186
199
|
def a_spec(arg):
|
|
187
200
|
if is_str(arg):
|
|
@@ -215,7 +228,7 @@ def to_specs(arg):
|
|
|
215
228
|
|
|
216
229
|
# validate_settings {{{2
|
|
217
230
|
validate_settings = Schema({
|
|
218
|
-
'repositories': {
|
|
231
|
+
'repositories': {key_as_name: to_specs},
|
|
219
232
|
'default_repository': str,
|
|
220
233
|
'report_style': str,
|
|
221
234
|
'compact_format': str,
|
|
@@ -245,31 +258,38 @@ try:
|
|
|
245
258
|
|
|
246
259
|
# convert from specifications to Repository objects
|
|
247
260
|
repositories = {}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
261
|
+
try:
|
|
262
|
+
for name, specs in specifications.items():
|
|
263
|
+
if specs:
|
|
264
|
+
repositories[name] = []
|
|
265
|
+
alias = name if len(specs) <= 1 else None
|
|
266
|
+
for spec in specs:
|
|
267
|
+
if spec in repositories and spec != name:
|
|
268
|
+
# this is a known (previously defined) repository
|
|
269
|
+
repositories[name].extend(repositories[spec])
|
|
270
|
+
else:
|
|
271
|
+
repositories[name].append(Repository(spec, alias))
|
|
272
|
+
else:
|
|
273
|
+
repositories[name] = [Repository(name)]
|
|
274
|
+
except Invalid as e:
|
|
275
|
+
raise Error(e, culprit=name)
|
|
260
276
|
|
|
261
277
|
except nt.NestedTextError as e:
|
|
262
|
-
e.
|
|
278
|
+
e.report()
|
|
279
|
+
except Error as e:
|
|
280
|
+
e.report(culprit=(settings_file,) + e.culprit)
|
|
263
281
|
except FileNotFoundError:
|
|
264
282
|
settings = {}
|
|
265
283
|
repositories = {}
|
|
266
284
|
except OSError as e:
|
|
267
|
-
|
|
285
|
+
error(os_error(e))
|
|
268
286
|
except MultipleInvalid as e: # report schema violations
|
|
269
287
|
for err in e.errors:
|
|
270
288
|
msg, flag = voluptuous_error_msg_mappings.get(
|
|
271
289
|
err.msg, (err.msg, 'value')
|
|
272
290
|
)
|
|
291
|
+
if msg.startswith(voluptuous_key_prefix):
|
|
292
|
+
flag = 'key'
|
|
273
293
|
loc = keymap.get(tuple(err.path))
|
|
274
294
|
codicil = loc.as_line(flag) if loc else None
|
|
275
295
|
keys = nt.join_keys(err.path, keymap=keymap)
|
|
@@ -278,4 +298,4 @@ except MultipleInvalid as e: # report schema violations
|
|
|
278
298
|
culprit = (settings_file, keys),
|
|
279
299
|
codicil = codicil
|
|
280
300
|
)
|
|
281
|
-
|
|
301
|
+
terminate_if_errors()
|
|
@@ -32,11 +32,10 @@ Settings are held in ~/.config/borg-space/settings.nt.
|
|
|
32
32
|
|
|
33
33
|
# imports {{{1
|
|
34
34
|
from .config import settings, get_repos
|
|
35
|
-
from .trees import tree
|
|
36
35
|
import arrow
|
|
37
36
|
from appdirs import user_data_dir
|
|
38
37
|
from docopt import docopt
|
|
39
|
-
from inform import Error, display, error, os_error, warn
|
|
38
|
+
from inform import Error, display, error, os_error, terminate, warn, tree
|
|
40
39
|
from pathlib import Path
|
|
41
40
|
from quantiphy import Quantity
|
|
42
41
|
import json
|
|
@@ -45,13 +44,15 @@ import matplotlib
|
|
|
45
44
|
import matplotlib.pyplot as plt
|
|
46
45
|
from matplotlib.dates import AutoDateFormatter, AutoDateLocator
|
|
47
46
|
from matplotlib.ticker import FuncFormatter
|
|
47
|
+
# from labellines import labelLines
|
|
48
|
+
|
|
48
49
|
|
|
49
50
|
# globals {{{1
|
|
50
51
|
data_dir = Path(user_data_dir('borg-space'))
|
|
51
52
|
now = str(arrow.now())
|
|
52
53
|
Quantity.set_prefs(prec='full')
|
|
53
|
-
__version__ = "2.
|
|
54
|
-
__released__ = "
|
|
54
|
+
__version__ = "2.3"
|
|
55
|
+
__released__ = "2025-05-11"
|
|
55
56
|
date_format = settings.get('date_format', 'D MMMM YYYY')
|
|
56
57
|
size_format = settings.get('size_format', '.2b')
|
|
57
58
|
nestedtext_size_format = settings.get('nestedtext_size_format', size_format)
|
|
@@ -151,6 +152,8 @@ def generate_graph(repos, svg_file, log_scale):
|
|
|
151
152
|
ax.xaxis.set_major_locator(locator)
|
|
152
153
|
ax.xaxis.set_major_formatter(AutoDateFormatter(locator))
|
|
153
154
|
|
|
155
|
+
fig.autofmt_xdate()
|
|
156
|
+
|
|
154
157
|
# add traces in order of last size, largest to smallest {{{3
|
|
155
158
|
largest = 0
|
|
156
159
|
smallest = 1e100
|
|
@@ -163,7 +166,7 @@ def generate_graph(repos, svg_file, log_scale):
|
|
|
163
166
|
|
|
164
167
|
# use SI scale factors on Y-axis
|
|
165
168
|
def bytes(value, pos=None):
|
|
166
|
-
return Quantity(value, 'B').render()
|
|
169
|
+
return Quantity(value, 'B').render(prec=3)
|
|
167
170
|
ax.yaxis.set_major_formatter(FuncFormatter(bytes))
|
|
168
171
|
if largest / smallest > 10:
|
|
169
172
|
ax.yaxis.set_minor_formatter("")
|
|
@@ -172,6 +175,7 @@ def generate_graph(repos, svg_file, log_scale):
|
|
|
172
175
|
|
|
173
176
|
# draw the graph {{{3
|
|
174
177
|
ax.legend(loc='upper left')
|
|
178
|
+
# labelLines(ax.get_lines())
|
|
175
179
|
if svg_file:
|
|
176
180
|
plt.savefig(svg_file)
|
|
177
181
|
else:
|
|
@@ -267,7 +271,7 @@ def print_tree_report(repos):
|
|
|
267
271
|
fmt = formatter,
|
|
268
272
|
fields = fields,
|
|
269
273
|
missing = not_available,
|
|
270
|
-
)
|
|
274
|
+
), squeeze=True
|
|
271
275
|
)
|
|
272
276
|
)
|
|
273
277
|
|
|
@@ -341,3 +345,4 @@ def main():
|
|
|
341
345
|
error(os_error(e))
|
|
342
346
|
except KeyboardInterrupt:
|
|
343
347
|
pass
|
|
348
|
+
terminate()
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "borg_space"
|
|
3
3
|
dist-name = "borg-space"
|
|
4
|
-
version = "2.
|
|
4
|
+
version = "2.3"
|
|
5
5
|
description = "Accessory for Emborg used to report and track the size of your Borg repositories"
|
|
6
6
|
readme = "README.rst"
|
|
7
7
|
requires-python = ">=3.6"
|
|
8
8
|
license = {file = "LICENSE"}
|
|
9
9
|
keywords = ["emborg", "borg", "backups"]
|
|
10
|
-
authors = [{name = "Ken Kundert", email = "
|
|
10
|
+
authors = [{name = "Ken Kundert", email = "borg-space@nurdletech.com"}]
|
|
11
11
|
classifiers = [
|
|
12
12
|
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
|
|
13
13
|
"Natural Language :: English",
|
|
@@ -19,12 +19,13 @@ dependencies = [
|
|
|
19
19
|
"appdirs",
|
|
20
20
|
"arrow",
|
|
21
21
|
"docopt",
|
|
22
|
-
"inform",
|
|
22
|
+
"inform>=1.34",
|
|
23
23
|
"matplotlib",
|
|
24
|
+
# "matplotlib-label-lines",
|
|
24
25
|
"nestedtext",
|
|
25
26
|
"quantiphy",
|
|
26
27
|
"shlib",
|
|
27
|
-
"voluptuous",
|
|
28
|
+
"voluptuous>=0.14",
|
|
28
29
|
]
|
|
29
30
|
|
|
30
31
|
[project.urls]
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# Format a Tree
|
|
2
|
-
# Description {{{1
|
|
3
|
-
# Given a data hierarchy consisting of zero or more levels of dictionaries with
|
|
4
|
-
# lists as leaf values, where each dictionary key and list value is a string,
|
|
5
|
-
# this function creates a Unicode diagram of that tree.
|
|
6
|
-
#
|
|
7
|
-
# For example, here is a filesystem sub-hierarchy:
|
|
8
|
-
#
|
|
9
|
-
# tests/
|
|
10
|
-
# ├── examples/
|
|
11
|
-
# │ ├── test_example_01.py
|
|
12
|
-
# │ ├── test_example_02.py
|
|
13
|
-
# │ └── test_example_03.py
|
|
14
|
-
# ├── foobar/
|
|
15
|
-
# │ ├── test_foobar_01.py
|
|
16
|
-
# │ ├── test_foobar_02.py
|
|
17
|
-
# │ └── test_foobar_03.py
|
|
18
|
-
# └── hello/
|
|
19
|
-
# └── world/
|
|
20
|
-
# ├── test_world_01.py
|
|
21
|
-
# ├── test_world_02.py
|
|
22
|
-
# └── test_world_03.py
|
|
23
|
-
|
|
24
|
-
# Imports {{{1
|
|
25
|
-
from inform import Info, conjoin, is_collection
|
|
26
|
-
|
|
27
|
-
def gen_connectors(width):
|
|
28
|
-
space = " " # This is a non-breaking space, needed with variable width fonts
|
|
29
|
-
line = "─" # This is horizontal rule
|
|
30
|
-
connector_seeds = dict(
|
|
31
|
-
item = "├",
|
|
32
|
-
last_item = "└",
|
|
33
|
-
lead = "│",
|
|
34
|
-
last_lead = space,
|
|
35
|
-
)
|
|
36
|
-
pad = space if width > 1 else ''
|
|
37
|
-
|
|
38
|
-
def extend(seed):
|
|
39
|
-
fill = space if seed in [space, "│"] else line
|
|
40
|
-
return seed + (width - 2)*fill + pad
|
|
41
|
-
|
|
42
|
-
return Info(**{k: extend(v) for k, v in connector_seeds.items()})
|
|
43
|
-
|
|
44
|
-
connectors = gen_connectors(4)
|
|
45
|
-
|
|
46
|
-
def tree(data, key_suffix=''):
|
|
47
|
-
return _tree(data, key_suffix, top=True)
|
|
48
|
-
|
|
49
|
-
def _tree(data, key_suffix, top=False, leader=''):
|
|
50
|
-
lines = []
|
|
51
|
-
if hasattr(data, 'items'):
|
|
52
|
-
last = len(data) - 1
|
|
53
|
-
for i, item in enumerate(data.items()):
|
|
54
|
-
key, value = item
|
|
55
|
-
# determine key-leader-supplement and item-leader-supplement
|
|
56
|
-
if top:
|
|
57
|
-
kls = ''
|
|
58
|
-
ils = ''
|
|
59
|
-
elif i < last:
|
|
60
|
-
kls = connectors.item
|
|
61
|
-
ils = connectors.lead
|
|
62
|
-
else:
|
|
63
|
-
kls = connectors.last_item
|
|
64
|
-
ils = connectors.last_lead
|
|
65
|
-
|
|
66
|
-
if is_collection(value):
|
|
67
|
-
# append dictionary to those already processed
|
|
68
|
-
lines += [
|
|
69
|
-
leader + kls + key + key_suffix,
|
|
70
|
-
_tree(value, key_suffix, leader = leader + ils) if value else None
|
|
71
|
-
]
|
|
72
|
-
else:
|
|
73
|
-
# the value is a scalar, so squeeze key & value on one line
|
|
74
|
-
lines += [
|
|
75
|
-
leader + kls + key + ': ' + value,
|
|
76
|
-
]
|
|
77
|
-
return '\n'.join(l for l in lines if l)
|
|
78
|
-
|
|
79
|
-
elif not is_collection(data):
|
|
80
|
-
data = [str(data)]
|
|
81
|
-
|
|
82
|
-
if top:
|
|
83
|
-
joiner = '\n'
|
|
84
|
-
terminator = '\n'
|
|
85
|
-
items = conjoin(data, sep='\n', conj='\n')
|
|
86
|
-
else:
|
|
87
|
-
joiner = '\n' + leader + connectors.item
|
|
88
|
-
terminator = '\n' + leader + connectors.last_item
|
|
89
|
-
connector = connectors.item if len(data) > 1 else connectors.last_item
|
|
90
|
-
items = leader + connector + conjoin(data, sep=joiner, conj=terminator)
|
|
91
|
-
|
|
92
|
-
if items:
|
|
93
|
-
lines.append(items)
|
|
94
|
-
return '\n'.join(lines)
|
|
File without changes
|
|
File without changes
|