borg-space 2.2__tar.gz → 2.4__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.
@@ -1,3 +1,31 @@
1
+ Metadata-Version: 2.4
2
+ Name: borg_space
3
+ Version: 2.4
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.2
15
- :Released: 2023-06-12
42
+ :Version: 2.4
43
+ :Released: 2026-01-27
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
 
@@ -99,6 +131,7 @@ You can create a NestedText_ settings file to specify default behaviors and
99
131
  define composite repositories. For example::
100
132
 
101
133
  default repository: home
134
+ default path: ~{user}/.local/share/assimilate/{config}.latest.nt
102
135
  report style: tree
103
136
  compact format: {name}: {size:{fmt}}. Last back up: {last_create:ddd, MMM DD}. Last squeeze: {last_squeeze:ddd, MMM DD}.
104
137
  table format: {host:<8} {user:<5} {config:<9} {size:<8.2b} {last_create:ddd, MMM DD}
@@ -114,7 +147,11 @@ define composite repositories. For example::
114
147
  dev: root@dev~root
115
148
  mail: root@mail~root
116
149
  files: root@files~root
117
- bastion: root@bastion~root
150
+ bastion:
151
+ config: root
152
+ host: bastion
153
+ user: root
154
+ path: /root/.local/share/emborg/root.latest.nt
118
155
  media: root@media~root
119
156
  web: root@web~root
120
157
  cluster: home@cluster
@@ -134,7 +171,17 @@ define composite repositories. For example::
134
171
  children: home servers root
135
172
 
136
173
  default repository:
137
- The name of the repository to be used if none are given on the command line.
174
+ The name (or names) of the repository to be used if none are given on the
175
+ command line.
176
+
177
+ default path:
178
+ The path to the *Emborg* or *Assimilate* generated latest.nt files. If not
179
+ give, it defaults to::
180
+
181
+ ~{user}/.local/share/emborg/{config}.latest.nt
182
+
183
+ ``{config}`` and ``{user}`` are placeholders that are replaced by the
184
+ corresponding component of the repository specification.
138
185
 
139
186
  report style:
140
187
  The report style to be used if none is specified on the command line.
@@ -230,6 +277,7 @@ repositories:
230
277
  config: home
231
278
  host: host
232
279
  user: user
280
+ path: ~user/.local/share/emborg/home.latest.nt
233
281
 
234
282
  repositiories:
235
283
  all: home@host~user work@host~user
@@ -248,6 +296,7 @@ repositories:
248
296
  config: home
249
297
  host: host
250
298
  user: user
299
+ path: ~user/.local/share/emborg/home.latest.nt
251
300
  -
252
301
  config: work
253
302
  host: host
@@ -310,14 +359,30 @@ scaled nicely on the same graph::
310
359
  Installation
311
360
  ------------
312
361
 
313
- *Borg-Space* requires *Emborg* version 1.37 or newer.
362
+ *Borg-Space* requires *Emborg* version 1.37 or newer or *Assimilate*.
314
363
 
315
364
  Install with::
316
365
 
317
366
  > pip3 install borg-space
318
367
 
319
368
 
369
+ Assimilate and Borg 2
370
+ ---------------------
371
+
372
+ Borg_ 2 will be released soon, and with it will come Assimilate_, the next
373
+ generation of Emborg_. *Assimilate* is intended to be used with *Borg 2.0* and
374
+ beyond while *Emborg* would be used with older versions of *Borg*. To use
375
+ *Assimilate* you should set the *default path* accordingly. To support both
376
+ *Emborg* and *Assimilate* simultaneously, you should set *default path* for one
377
+ and then use *path* overrides for individual repositories.
378
+
379
+ *Assimilate* only saves the space used by the repository when running
380
+ a *compact* command and only if the *get_repo_size* is set to ``'yes``.
381
+
382
+ .. _assimilate: https://assimilate.readthedocs.io
383
+ .. _borg: https://borgbackup.readthedocs.io
320
384
  .. _emborg: https://emborg.readthedocs.io
321
385
  .. _nestedtext: https://nestedtext.org
322
386
  .. _arrow: https://arrow.readthedocs.io/en/latest/guide.html#supported-tokens
323
387
  .. _quantiphy: https://quantiphy.readthedocs.io/en/stable/api.html#quantiphy.Quantity.format
388
+
@@ -1,30 +1,3 @@
1
- Metadata-Version: 2.1
2
- Name: borg_space
3
- Version: 2.2
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.2
42
- :Released: 2023-06-12
14
+ :Version: 2.4
15
+ :Released: 2026-01-27
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
 
@@ -126,6 +103,7 @@ You can create a NestedText_ settings file to specify default behaviors and
126
103
  define composite repositories. For example::
127
104
 
128
105
  default repository: home
106
+ default path: ~{user}/.local/share/assimilate/{config}.latest.nt
129
107
  report style: tree
130
108
  compact format: {name}: {size:{fmt}}. Last back up: {last_create:ddd, MMM DD}. Last squeeze: {last_squeeze:ddd, MMM DD}.
131
109
  table format: {host:<8} {user:<5} {config:<9} {size:<8.2b} {last_create:ddd, MMM DD}
@@ -141,7 +119,11 @@ define composite repositories. For example::
141
119
  dev: root@dev~root
142
120
  mail: root@mail~root
143
121
  files: root@files~root
144
- bastion: root@bastion~root
122
+ bastion:
123
+ config: root
124
+ host: bastion
125
+ user: root
126
+ path: /root/.local/share/emborg/root.latest.nt
145
127
  media: root@media~root
146
128
  web: root@web~root
147
129
  cluster: home@cluster
@@ -161,7 +143,17 @@ define composite repositories. For example::
161
143
  children: home servers root
162
144
 
163
145
  default repository:
164
- The name of the repository to be used if none are given on the command line.
146
+ The name (or names) of the repository to be used if none are given on the
147
+ command line.
148
+
149
+ default path:
150
+ The path to the *Emborg* or *Assimilate* generated latest.nt files. If not
151
+ give, it defaults to::
152
+
153
+ ~{user}/.local/share/emborg/{config}.latest.nt
154
+
155
+ ``{config}`` and ``{user}`` are placeholders that are replaced by the
156
+ corresponding component of the repository specification.
165
157
 
166
158
  report style:
167
159
  The report style to be used if none is specified on the command line.
@@ -257,6 +249,7 @@ repositories:
257
249
  config: home
258
250
  host: host
259
251
  user: user
252
+ path: ~user/.local/share/emborg/home.latest.nt
260
253
 
261
254
  repositiories:
262
255
  all: home@host~user work@host~user
@@ -275,6 +268,7 @@ repositories:
275
268
  config: home
276
269
  host: host
277
270
  user: user
271
+ path: ~user/.local/share/emborg/home.latest.nt
278
272
  -
279
273
  config: work
280
274
  host: host
@@ -337,15 +331,29 @@ scaled nicely on the same graph::
337
331
  Installation
338
332
  ------------
339
333
 
340
- *Borg-Space* requires *Emborg* version 1.37 or newer.
334
+ *Borg-Space* requires *Emborg* version 1.37 or newer or *Assimilate*.
341
335
 
342
336
  Install with::
343
337
 
344
338
  > pip3 install borg-space
345
339
 
346
340
 
341
+ Assimilate and Borg 2
342
+ ---------------------
343
+
344
+ Borg_ 2 will be released soon, and with it will come Assimilate_, the next
345
+ generation of Emborg_. *Assimilate* is intended to be used with *Borg 2.0* and
346
+ beyond while *Emborg* would be used with older versions of *Borg*. To use
347
+ *Assimilate* you should set the *default path* accordingly. To support both
348
+ *Emborg* and *Assimilate* simultaneously, you should set *default path* for one
349
+ and then use *path* overrides for individual repositories.
350
+
351
+ *Assimilate* only saves the space used by the repository when running
352
+ a *compact* command and only if the *get_repo_size* is set to ``'yes``.
353
+
354
+ .. _assimilate: https://assimilate.readthedocs.io
355
+ .. _borg: https://borgbackup.readthedocs.io
347
356
  .. _emborg: https://emborg.readthedocs.io
348
357
  .. _nestedtext: https://nestedtext.org
349
358
  .. _arrow: https://arrow.readthedocs.io/en/latest/guide.html#supported-tokens
350
359
  .. _quantiphy: https://quantiphy.readthedocs.io/en/stable/api.html#quantiphy.Quantity.format
351
-
@@ -32,7 +32,6 @@ if new_home: # pragma: no cover
32
32
 
33
33
  # GLOBALS {{{1
34
34
  set_prefs(use_inform=True)
35
- settings_file = to_path(user_config_dir('borg-space')) / 'settings.nt'
36
35
  voluptuous_error_msg_mappings = {
37
36
  "extra keys not allowed": ("unknown key", "key"),
38
37
  "expected a dictionary": ("expected key:value pair", "value"),
@@ -41,24 +40,43 @@ voluptuous_key_prefix = "key contains"
41
40
  hostname = socket.gethostname().split('.')[0]
42
41
  # version of the hostname (the hostname without any domain name)
43
42
  username = pwd.getpwuid(os.getuid()).pw_name
43
+ program_name = 'borg-space'
44
+ if 'XDG_CONFIG_HOME' in os.environ:
45
+ config_dir = os.sep.join([os.environ['XDG_CONFIG_HOME'], program_name])
46
+ else:
47
+ config_dir = user_config_dir(program_name)
48
+ settings_file = to_path(config_dir) / 'settings.nt'
44
49
 
45
50
 
46
51
  # REPOSITORY {{{1
47
52
  # Repository class {{{2
48
53
  class Repository:
49
54
  def __init__(self, spec, name=None):
50
- prefix, _, user = spec.partition('~')
51
- config, _, host = prefix.partition('@')
55
+ if is_str(spec):
56
+ prefix, _, user = spec.partition('~')
57
+ config, _, host = prefix.partition('@')
58
+ path = None
59
+ else:
60
+ user = spec.get('user')
61
+ config = spec.get('config')
62
+ host = spec.get('host')
63
+ path = spec.get('path')
64
+ spec = spec.get('spec')
65
+
52
66
  if not config:
53
67
  raise Error("spec is missing Emborg config name.", culprit=spec)
54
68
  if not name:
55
69
  name = spec
56
70
 
57
- self.spec = spec
58
- self.name = name
59
- self.config = a_name(config)
60
- self.host = a_name(host) or hostname
61
- self.user = a_name(user) or username
71
+ try:
72
+ self.spec = spec
73
+ self.name = name
74
+ self.config = a_name(config)
75
+ self.host = a_name(host) or hostname
76
+ self.user = a_name(user) or username
77
+ self.path = path
78
+ except Invalid as e:
79
+ raise Error(e, culprit=spec)
62
80
  self.latest = None
63
81
 
64
82
  def __str__(self):
@@ -87,14 +105,21 @@ class Repository:
87
105
  user = self.user,
88
106
  full_spec = str(self)
89
107
  )
90
- info.update(self.latest)
108
+ if self.latest:
109
+ info.update(self.latest)
91
110
  return info
92
111
 
93
112
  def get_path(self):
94
113
  user = self.user if self.user else getpass.getuser()
95
114
  config = self.config
96
115
  assert config
97
- path = f"~{user}/.local/share/emborg/{config}.latest.nt"
116
+ path = self.path
117
+ if not path:
118
+ path = settings.get(
119
+ 'default_path',
120
+ "~{user}/.local/share/emborg/{config}.latest.nt"
121
+ )
122
+ path = path.format(user=user, config=config)
98
123
  return (self.host, path)
99
124
 
100
125
  def get_latest(self):
@@ -109,11 +134,11 @@ class Repository:
109
134
  try:
110
135
  content = to_path(path).read_text()
111
136
  except FileNotFoundError:
112
- raise Error('unknown repository.', culprit=str(self))
137
+ raise Error(f'repository not found: {path}', culprit=str(self))
113
138
  raw_data = nt.loads(content)
114
139
  self.latest = data = {}
115
140
  if 'repository size' in raw_data:
116
- data['size'] = Quantity(raw_data['repository size'], 'B')
141
+ data['size'] = Quantity(raw_data['repository size'], 'B', binary=True)
117
142
  if 'create last run' in raw_data:
118
143
  data['last_create'] = arrow.get(raw_data['create last run'])
119
144
  if 'prune last run' in raw_data:
@@ -128,27 +153,27 @@ class Repository:
128
153
  def get_repos(spec):
129
154
  if not spec:
130
155
  spec = settings.get('default_repository')
131
- if not spec:
132
- raise Error('there is no default repository.')
133
156
 
134
- try:
135
- children = repositories[spec]
136
- except (TypeError, KeyError):
137
- # not found in repositories specified in settings file.
138
- # see if it exists on local machine
139
- children = [Repository(spec)]
140
-
141
- results = {}
142
- for child in children:
143
- host, path = child.get_path()
144
- name = str(child)
157
+ specs = spec.split()
158
+ for spec in specs:
145
159
  try:
146
- child.get_latest()
147
- results[name] = child
148
- except Error as e:
149
- e.report(culprit=name)
150
- except OSError as e:
151
- error(os_error(e), culprit=name)
160
+ children = repositories[spec]
161
+ except (TypeError, KeyError):
162
+ # not found in repositories specified in settings file.
163
+ # see if it exists on local machine
164
+ children = [Repository(spec)]
165
+
166
+ results = {}
167
+ for child in children:
168
+ # host, path = child.get_path()
169
+ name = str(child)
170
+ try:
171
+ child.get_latest()
172
+ results[name] = child
173
+ except Error as e:
174
+ e.report(culprit=name)
175
+ except OSError as e:
176
+ error(os_error(e), culprit=name)
152
177
  return results
153
178
 
154
179
 
@@ -168,7 +193,7 @@ def to_list(args):
168
193
  if is_str(args):
169
194
  args = args.split()
170
195
  if is_mapping(args):
171
- raise Invalid(f"expected a list or string")
196
+ raise Invalid("expected a list or string")
172
197
  return args
173
198
 
174
199
  # a_name() {{{2
@@ -200,7 +225,7 @@ def a_spec(arg):
200
225
  if is_str(arg):
201
226
  return arg
202
227
  if is_mapping(arg):
203
- unknown_keys = arg.keys() - set(['config', 'host', 'user'])
228
+ unknown_keys = arg.keys() - set(['config', 'host', 'user', 'path'])
204
229
  if unknown_keys:
205
230
  raise Invalid(f"unknown {plural(unknown_keys):key}: {conjoin(unknown_keys)}.")
206
231
  if 'config' not in arg:
@@ -210,7 +235,8 @@ def a_spec(arg):
210
235
  spec = f"{spec}@{arg.get('host')}"
211
236
  if arg.get('user'):
212
237
  spec = f"{spec}~{arg.get('user')}"
213
- return spec
238
+ arg['spec'] = spec
239
+ return arg
214
240
  raise Invalid("expected a specification")
215
241
 
216
242
  # to_specs() {{{2
@@ -218,7 +244,7 @@ def to_specs(arg):
218
244
  if is_str(arg):
219
245
  return [a_spec(r) for r in arg.split()]
220
246
  if is_mapping(arg):
221
- unknown_keys = arg.keys() - set(['config', 'host', 'user'])
247
+ unknown_keys = arg.keys() - set(['config', 'host', 'user', 'path'])
222
248
  if unknown_keys:
223
249
  raise Invalid(f"unknown {plural(unknown_keys):key}: {conjoin(unknown_keys)}.")
224
250
  return [a_spec(arg)]
@@ -230,6 +256,7 @@ def to_specs(arg):
230
256
  validate_settings = Schema({
231
257
  'repositories': {key_as_name: to_specs},
232
258
  'default_repository': str,
259
+ 'default_path': str,
233
260
  'report_style': str,
234
261
  'compact_format': str,
235
262
  'table_format': str,
@@ -264,9 +291,12 @@ try:
264
291
  repositories[name] = []
265
292
  alias = name if len(specs) <= 1 else None
266
293
  for spec in specs:
267
- if spec in repositories and spec != name:
294
+ specname = spec
295
+ if is_mapping(spec):
296
+ specname = spec['spec']
297
+ if specname in repositories and specname != name:
268
298
  # this is a known (previously defined) repository
269
- repositories[name].extend(repositories[spec])
299
+ repositories[name].extend(repositories[specname])
270
300
  else:
271
301
  repositories[name].append(Repository(spec, alias))
272
302
  else:
@@ -31,12 +31,11 @@ Settings are held in ~/.config/borg-space/settings.nt.
31
31
  """
32
32
 
33
33
  # imports {{{1
34
- from .config import settings, get_repos
35
- from .trees import tree
34
+ from .config import settings, get_repos, program_name
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, terminate, 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,19 @@ 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
+ import os
49
+
48
50
 
49
51
  # globals {{{1
50
- data_dir = Path(user_data_dir('borg-space'))
52
+ if 'XDG_DATA_HOME' in os.environ:
53
+ data_dir = Path(os.sep.join([os.environ['XDG_DATA_HOME'], program_name]))
54
+ else:
55
+ data_dir = Path(user_data_dir(program_name))
51
56
  now = str(arrow.now())
52
57
  Quantity.set_prefs(prec='full')
53
- __version__ = "2.2"
54
- __released__ = "2023-06-12"
58
+ __version__ = "2.4"
59
+ __released__ = "2026-01-27"
55
60
  date_format = settings.get('date_format', 'D MMMM YYYY')
56
61
  size_format = settings.get('size_format', '.2b')
57
62
  nestedtext_size_format = settings.get('nestedtext_size_format', size_format)
@@ -64,6 +69,8 @@ not_available = "⟪not available⟫"
64
69
  # collect_repos() {{{1
65
70
  def collect_repos(requests, record_size):
66
71
  repos = {}
72
+ if not requests:
73
+ raise Error('there is no default repository.')
67
74
  for request in requests:
68
75
  new_repos = get_repos(request)
69
76
  repos.update(new_repos)
@@ -73,7 +80,7 @@ def collect_repos(requests, record_size):
73
80
  for name, repo in repos.items():
74
81
 
75
82
  # read previously recorded sizes
76
- data_path = Path(data_dir / f'{name}.nt')
83
+ data_path = data_dir / f'{name}.nt'
77
84
  try:
78
85
  data = nt.load(data_path, top=dict)
79
86
  except FileNotFoundError:
@@ -174,6 +181,7 @@ def generate_graph(repos, svg_file, log_scale):
174
181
 
175
182
  # draw the graph {{{3
176
183
  ax.legend(loc='upper left')
184
+ # labelLines(ax.get_lines())
177
185
  if svg_file:
178
186
  plt.savefig(svg_file)
179
187
  else:
@@ -269,7 +277,7 @@ def print_tree_report(repos):
269
277
  fmt = formatter,
270
278
  fields = fields,
271
279
  missing = not_available,
272
- )
280
+ ), squeeze=True
273
281
  )
274
282
  )
275
283
 
@@ -328,7 +336,7 @@ def main():
328
336
 
329
337
  requests = cmdline['<spec>']
330
338
  if not requests:
331
- requests = [''] # this gets the default config
339
+ requests = settings.get('default_repository', '').split()
332
340
 
333
341
  try:
334
342
  repos = collect_repos(requests, cmdline['--record'])
@@ -1,13 +1,13 @@
1
1
  [project]
2
2
  name = "borg_space"
3
3
  dist-name = "borg-space"
4
- version = "2.2"
4
+ version = "2.4"
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 = "emborg@nurdletech.com"}]
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]
@@ -39,3 +40,16 @@ borg-space = "borg_space.main:main"
39
40
  [build-system]
40
41
  requires = ["flit_core >=2,<4"]
41
42
  build-backend = "flit_core.buildapi"
43
+
44
+ [tool.pytest.ini_options]
45
+ addopts = "--tb=short"
46
+
47
+ [tool.ruff]
48
+ exclude = [".tox", "doc", "Diffs"]
49
+
50
+ [tool.ruff.lint]
51
+ select = ["F"]
52
+ ignore = []
53
+
54
+ [tool.ruff.lint.per-file-ignores]
55
+ "borg_space/__init__.py" = ["F401"]
@@ -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