pyrbd 0.2.5__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.
pyrbd/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ """Package for creating simple reliability block diagrams using LaTeX TikZ."""
2
+
3
+ from .block import Block, Series, Group
4
+ from .diagram import Diagram
5
+
6
+ __all__ = [
7
+ "Block",
8
+ "Series",
9
+ "Group",
10
+ "Diagram",
11
+ ]
pyrbd/block.py ADDED
@@ -0,0 +1,456 @@
1
+ """Module containing Block, Series and Group class definitions."""
2
+
3
+ from typing import Optional
4
+ from itertools import combinations
5
+ from copy import deepcopy
6
+
7
+
8
+ class Block:
9
+ """Block entering a reliability block diagram.
10
+
11
+ Parameters
12
+ ----------
13
+ text : str
14
+ block text string
15
+ color : str
16
+ block color
17
+ parent : Optional[Block]
18
+ parent `Block` instance
19
+ shift : tuple[float, float], optional
20
+ additional position shift `(x, y)` relative to `parent` `Block` instance
21
+
22
+ Attributes
23
+ ----------
24
+ tikz_options : str
25
+ TikZ node formatting options
26
+
27
+
28
+ Examples
29
+ --------
30
+ >>> block_1 = Block("Start", "green")
31
+ >>> block_1.id
32
+ '1'
33
+ >>> block_2 = Block("End", "red", parent=block_1)
34
+ >>> block_2.id
35
+ '2'
36
+ """
37
+
38
+ tikz_options: str = ", ".join(
39
+ [
40
+ "anchor=west",
41
+ "align=center",
42
+ "fill={fill_color}",
43
+ "draw=black",
44
+ "minimum height=1cm",
45
+ "rounded corners=0.3mm",
46
+ "inner sep=4pt",
47
+ "outer sep=0pt",
48
+ ]
49
+ )
50
+
51
+ def __init__(
52
+ self,
53
+ text: str,
54
+ color: str,
55
+ parent: Optional["Block"] = None,
56
+ shift: tuple[float, float] = (0.0, 0.0),
57
+ ) -> None:
58
+ self.text = text
59
+ self.color = color
60
+ self.parent = parent
61
+ self.shift = shift
62
+ self.id: str = str(int(self.parent.id) + 1) if self.parent is not None else "1"
63
+
64
+ @property
65
+ def position(self) -> str:
66
+ """Block position TikZ string."""
67
+
68
+ if self.parent is None:
69
+ return ""
70
+
71
+ return f"[right={0.5 + self.shift[0]}cm of {self.parent.id}, yshift={self.shift[1]}cm]"
72
+
73
+ def arrow(self, connector_position: float) -> str:
74
+ """Get TikZ arrow string.
75
+
76
+
77
+ Parameters
78
+ ----------
79
+ connector_position : float
80
+ distance in cm to right angle bend in connector
81
+
82
+ Returns
83
+ -------
84
+ str
85
+ TikZ string for arrow from `parent` to `self` or empty string if `parent` is `None`
86
+ """
87
+
88
+ if self.parent is None:
89
+ return ""
90
+
91
+ return "".join(
92
+ [
93
+ f"\\draw[thick, rectangle connector={connector_position}cm]",
94
+ f"({self.parent.id}.east) to ({self.id}.west);\n\n",
95
+ ]
96
+ )
97
+
98
+ def get_node(self, connector_position: float = 0.25) -> str:
99
+ """Get TikZ node string.
100
+
101
+ Parameters
102
+ ----------
103
+ connector_position : float, default: 0.25
104
+ distance in cm to right angle bend in connector
105
+
106
+ Returns
107
+ -------
108
+ str
109
+ TikZ string for rendering block
110
+ """
111
+
112
+ node = "".join(
113
+ [
114
+ "% Block\n",
115
+ f"\\node[{self.tikz_options.format(fill_color=self.color)}] ",
116
+ f"({self.id}) ",
117
+ self.position,
118
+ f"{{{self.text}}};\n",
119
+ self.arrow(connector_position),
120
+ ]
121
+ )
122
+ return node
123
+
124
+ def __add__(self, block: "Block") -> "Series":
125
+ """Add two `Block` instances to make a `Series` instance.
126
+
127
+ Parameters
128
+ ----------
129
+ block : Block
130
+ another `Block` instance
131
+
132
+ Returns
133
+ -------
134
+ Series
135
+ `Series` instance with `blocks = [self, block]`
136
+
137
+ Raises
138
+ ------
139
+ TypeError
140
+ If `block` is not an instance of `Block`
141
+ """
142
+
143
+ if not isinstance(block, Block):
144
+ raise TypeError(
145
+ f"cannot add object of type {type(block)=} to Block instance."
146
+ )
147
+
148
+ return Series([self, block], parent=self.parent)
149
+
150
+ def __mul__(self, value: int) -> "Group":
151
+ """Multiply `Block` instance by `value` to make `Group` with repeated blocks.
152
+
153
+ Parameters
154
+ ----------
155
+ value : int
156
+ multiplicative factor
157
+
158
+ Returns
159
+ -------
160
+ Group
161
+ `Group` instance with `value` copies of block
162
+
163
+ Raises
164
+ ------
165
+ ValueError
166
+ If `value` is not a positive integer
167
+ """
168
+
169
+ if not isinstance(value, int) or value <= 0:
170
+ raise ValueError("Multiplicative factor `value` must be a positive integer")
171
+
172
+ blocks: list[Block] = [deepcopy(self) for _ in range(value)]
173
+
174
+ return Group(blocks, parent=self.parent)
175
+
176
+ __rmul__ = __mul__
177
+
178
+
179
+ class Series(Block):
180
+ """Series configuration of `Block` instances for horisontal grouping.
181
+
182
+ Parameters
183
+ ----------
184
+ blocks : list[Block]
185
+ list of `Block` instances
186
+ text: str, optional
187
+ series label text
188
+ color: str, optional
189
+ series color
190
+ parent : Optional[Block]
191
+ parent `Block` instance
192
+
193
+ Attributes
194
+ ----------
195
+ tikz_options : str
196
+ TikZ node options
197
+
198
+ """
199
+
200
+ tikz_options: str = ", ".join(
201
+ [
202
+ "anchor=west",
203
+ "align=center",
204
+ "inner sep=0pt",
205
+ "outer sep=0pt",
206
+ ]
207
+ )
208
+
209
+ def __init__(
210
+ self,
211
+ blocks: list[Block],
212
+ text: str = "",
213
+ color: str = "",
214
+ parent: Optional[Block] = None,
215
+ ) -> None:
216
+ Block.__init__(self, text, color, parent)
217
+
218
+ self.blocks = blocks
219
+ self.blocks[0].id = f"{self.id}+0"
220
+ for i, (block, new_parent) in enumerate(
221
+ zip(self.blocks[1::], self.blocks[0:-1]), start=1
222
+ ):
223
+ block.parent = new_parent
224
+ block.id = f"{self.id}+{i}"
225
+
226
+ @property
227
+ def background(self) -> str:
228
+ """Background rectangle TikZ string."""
229
+
230
+ if self.color in ("white", ""):
231
+ return ""
232
+
233
+ return "".join(
234
+ [
235
+ "\\begin{pgfonlayer}{background}\n",
236
+ f"\\coordinate (sw) at ($({self.id}.south west)+(-1mm, -1mm)$);\n",
237
+ f"\\coordinate (ne) at ($({self.id}.north east)+(1mm, 1mm)$);\n",
238
+ f"\\draw[{self.color}, thick] (sw) rectangle (ne);\n",
239
+ "\\end{pgfonlayer}\n",
240
+ ]
241
+ )
242
+
243
+ @property
244
+ def label(self) -> str:
245
+ """Series label string."""
246
+
247
+ if len(self.text) == 0:
248
+ return ""
249
+
250
+ return "".join(
251
+ [
252
+ f"\\coordinate (nw) at ($({self.id}.north west)+(-1mm, 1mm)$);\n",
253
+ f"\\coordinate (ne) at ($({self.id}.north east)+(1mm, 1mm)$);\n",
254
+ f"\\coordinate (n) at ($({self.id}.north)+(0mm, 1mm)$);\n",
255
+ f"\\draw[{self.color}, fill={self.color}!50, thick] (nw) ",
256
+ "rectangle ($(ne)+(0, 0.5cm)$);\n",
257
+ f"\\node[anchor=south] at (n) {{{self.text}}};\n",
258
+ ]
259
+ )
260
+
261
+ def get_node(self, connector_position: float = 0.25) -> str:
262
+ """Get TikZ node string.
263
+
264
+ Parameters
265
+ ----------
266
+ connector_position : float, default: 0.25
267
+ distance in cm to right angle bend in connector
268
+
269
+ Returns
270
+ -------
271
+ str
272
+ TikZ string for rendering series
273
+
274
+ """
275
+
276
+ block_nodes = "\n".join(
277
+ block.get_node(connector_position) for block in self.blocks
278
+ )
279
+ series_node = "".join(
280
+ [
281
+ f"\\node[{self.tikz_options}]",
282
+ f"({self.id})",
283
+ self.position,
284
+ "{\\begin{tikzpicture}\n",
285
+ block_nodes,
286
+ "\\end{tikzpicture}};\n\n",
287
+ self.arrow(connector_position),
288
+ self.background,
289
+ self.label,
290
+ ]
291
+ )
292
+ return series_node
293
+
294
+
295
+ class Group(Block):
296
+ """Group of `Block` instances for vertical stacking.
297
+
298
+ Parameters
299
+ ----------
300
+ blocks : list[Block]
301
+ list of `Block` instances
302
+ text : str, optional
303
+ group label text
304
+ color : str, optional
305
+ group color
306
+ parent : Optional[Block]
307
+ parent `Block` instance
308
+
309
+ Attributes
310
+ ----------
311
+ shift_scale : float
312
+ scaling factor for vertical shifts of blocks
313
+ tikz_options : str
314
+ TikZ node options
315
+ """
316
+
317
+ shift_scale: float = 1.2
318
+ tikz_options: str = ", ".join(
319
+ [
320
+ "anchor=west",
321
+ ]
322
+ )
323
+
324
+ def __init__(
325
+ self,
326
+ blocks: list[Block],
327
+ text: str = "",
328
+ color: str = "",
329
+ parent: Optional[Block] = None,
330
+ ) -> None:
331
+ Block.__init__(self, text, color, parent)
332
+
333
+ self.blocks = blocks
334
+ for i, (block, shift) in enumerate(zip(self.blocks, self.shifts)):
335
+ block.shift = (0, shift)
336
+ block.parent = self
337
+ block.id = f"{self.id}-{i}"
338
+
339
+ @property
340
+ def shifts(self) -> list[float]:
341
+ """List of vertical position shifts for each `Block` instance in group.
342
+
343
+ Returns
344
+ -------
345
+ list[float]
346
+ list of vertical position shifts for each `Block` instance in group
347
+ """
348
+
349
+ n_blocks = len(self.blocks)
350
+
351
+ return list(-self.shift_scale * n for n in range(n_blocks))
352
+
353
+ @property
354
+ def background(self) -> str:
355
+ """Background rectangle TikZ string."""
356
+
357
+ if self.color in ("white", ""):
358
+ return ""
359
+
360
+ return "".join(
361
+ [
362
+ "\\begin{pgfonlayer}{background}\n",
363
+ f"\\coordinate (sw) at ($({self.id}.south west)+(-1mm, -1mm)$);\n",
364
+ f"\\coordinate (ne) at ($({self.id}.north east)+(1mm, 1mm)$);\n",
365
+ f"\\draw[{self.color}, thick] (sw) rectangle (ne);\n",
366
+ "\\end{pgfonlayer}\n",
367
+ ]
368
+ )
369
+
370
+ @property
371
+ def label(self) -> str:
372
+ """Series label string."""
373
+
374
+ if len(self.text) == 0:
375
+ return ""
376
+
377
+ return "".join(
378
+ [
379
+ f"\\coordinate (nw) at ($({self.id}.north west)+(-1mm, 1mm)$);\n",
380
+ f"\\coordinate (ne) at ($({self.id}.north east)+(1mm, 1mm)$);\n",
381
+ f"\\coordinate (n) at ($({self.id}.north)+(0mm, 1mm)$);\n",
382
+ f"\\draw[{self.color}, fill={self.color}!50, thick] (nw) ",
383
+ "rectangle ($(ne)+(0, 0.5cm)$);\n",
384
+ f"\\node[anchor=south] at (n) {{{self.text}}};\n",
385
+ ]
386
+ )
387
+
388
+ def arrow(self, connector_position: float) -> str:
389
+ """Get TikZ arrow string.
390
+
391
+ Parameters
392
+ ----------
393
+ connector_position : float
394
+ distance in cm to right angle bend in connector (not used in `Group` class)
395
+
396
+ Returns
397
+ -------
398
+ str
399
+ TikZ string for arrow from `parent` to `self` or empty string if `parent` is `None`
400
+ """
401
+
402
+ if self.parent is None:
403
+ return ""
404
+
405
+ return f"\\draw[thick] ({self.parent.id}.east) to ({self.id}.west);\n"
406
+
407
+ @property
408
+ def arrows(self) -> str:
409
+ """Get TikZ string for arrow connecting stacked blocks."""
410
+
411
+ return "\n".join(
412
+ [
413
+ " ".join(
414
+ [
415
+ "\\draw[thick, rectangle line]",
416
+ f"({block1.id}.east) to ({block2.id}.east);\n",
417
+ ]
418
+ )
419
+ for (block1, block2) in combinations(self.blocks, 2)
420
+ ]
421
+ )
422
+
423
+ def get_node(self, connector_position: float = 0.0) -> str:
424
+ """Get TikZ node string.
425
+
426
+ Parameters
427
+ ----------
428
+ connector_position : float, default: 0.0
429
+ distance in cm to right angle bend in connector
430
+
431
+ Returns
432
+ -------
433
+ str
434
+ TikZ string for rendering group
435
+ """
436
+
437
+ block_nodes = "\n".join(
438
+ block.get_node(connector_position) for block in self.blocks
439
+ )
440
+
441
+ group_node = "".join(
442
+ [
443
+ f"\\node[anchor=west, outer sep=0pt, inner sep=0pt, align=center] ({self.id}) ",
444
+ self.position,
445
+ "{\\begin{tikzpicture}\n",
446
+ f"\\coordinate ({self.id}) at (0, 0);\n",
447
+ block_nodes,
448
+ self.arrows,
449
+ "\\end{tikzpicture}};\n\n",
450
+ self.arrow(connector_position),
451
+ self.background,
452
+ self.label,
453
+ ]
454
+ )
455
+
456
+ return group_node
pyrbd/diagram.py ADDED
@@ -0,0 +1,186 @@
1
+ """Module containing Diagram class definition."""
2
+
3
+ import subprocess
4
+
5
+ import pymupdf
6
+
7
+ from .block import Block
8
+
9
+
10
+ class Diagram:
11
+ """Reliability block diagram class definition.
12
+
13
+ Parameters
14
+ ----------
15
+ name : str
16
+ name of diagram
17
+ blocks : list[Block]
18
+ list of `Block` instances
19
+ hazard : str, optional
20
+ string defining the `hazard` block text
21
+ """
22
+
23
+ def __init__(self, name: str, blocks: list[Block], hazard: str = "") -> None:
24
+ self.filename = name
25
+ if hazard:
26
+ self.head = Block(hazard, "red!60")
27
+ else:
28
+ self.head = blocks.pop(0)
29
+
30
+ self.head.id = "0"
31
+ self.blocks = blocks
32
+ self.blocks[0].parent = self.head
33
+
34
+ def write(self) -> None:
35
+ """Write diagram to .tex file."""
36
+
37
+ with open(f"{self.filename}.tex", mode="w", encoding="utf-8") as file:
38
+ file.write(TEX_PREAMBLE)
39
+ for block in [self.head, *self.blocks]:
40
+ file.write(block.get_node())
41
+ file.write(TEX_END)
42
+
43
+ def _to_svg(self) -> str:
44
+ """Convert diagram file from pdf to svg.
45
+
46
+ Returns
47
+ -------
48
+ str
49
+ filename of .svg file
50
+ """
51
+
52
+ pdf_document = pymupdf.open(f"{self.filename}.pdf")
53
+ page = pdf_document[0]
54
+
55
+ # Get and convert page to svg image
56
+ svg_content = page.get_svg_image()
57
+
58
+ # Save to file
59
+ with open(output_file := f"{self.filename}.svg", "w", encoding="utf-8") as file:
60
+ file.write(svg_content)
61
+
62
+ pdf_document.close()
63
+
64
+ return output_file
65
+
66
+ def _to_png(self) -> str:
67
+ """Convert diagram file from pdf to png.
68
+
69
+ Returns
70
+ -------
71
+ str
72
+ filename of .png file
73
+ """
74
+
75
+ pdf_document = pymupdf.open(f"{self.filename}.pdf")
76
+ page = pdf_document[0]
77
+
78
+ # Get image
79
+ image = page.get_pixmap(dpi=300) # type: ignore
80
+
81
+ # Save to file
82
+ image.save(output_file := f"{self.filename}.png")
83
+
84
+ pdf_document.close()
85
+
86
+ return output_file
87
+
88
+ def compile(
89
+ self, output: str | list[str] = "pdf", clear_source: bool = True
90
+ ) -> list[str]:
91
+ """Compile diagram .tex file.
92
+
93
+ Parameters
94
+ ----------
95
+ output : str | list[str], default: 'pdf'
96
+ output format string or list of output formats for diagram. Valid output formats are
97
+
98
+ - `'pdf'` (default)
99
+ - `'svg'`
100
+ - `'png'`
101
+
102
+ clear_source : bool, default: True
103
+ .tex source file is deleted after compilation if `True`
104
+
105
+ Returns
106
+ -------
107
+ list[str]
108
+ list of output filenames
109
+
110
+ Raises
111
+ ------
112
+ FileNotFoundError
113
+ If .tex file is not found, e.g. because `Diagram.write()` has not been called
114
+ before `Diagram.compile()`.
115
+ """
116
+
117
+ try:
118
+ subprocess.check_call(["latexmk", f"{self.filename}.tex", "--silent"])
119
+ subprocess.check_call(["latexmk", "-c", f"{self.filename}.tex"])
120
+ if clear_source:
121
+ subprocess.check_call(["rm", f"{self.filename}.tex"])
122
+ except subprocess.CalledProcessError as err:
123
+ if err.returncode == 11:
124
+ raise FileNotFoundError(
125
+ (
126
+ f"File {self.filename} not found. "
127
+ + "Check if call to Class method write() is missing."
128
+ )
129
+ ) from err
130
+
131
+ output_files: list[str] = []
132
+
133
+ if not isinstance(output, list):
134
+ output = [output]
135
+
136
+ if "svg" in output:
137
+ output_files.append(self._to_svg())
138
+ if "png" in output:
139
+ output_files.append(self._to_png())
140
+ if "pdf" not in output:
141
+ subprocess.check_call(["rm", f"{self.filename}.pdf"])
142
+ else:
143
+ output_files.append(f"{self.filename}.pdf")
144
+
145
+ return output_files
146
+
147
+
148
+ TEX_PREAMBLE = "\n".join(
149
+ [
150
+ r"\documentclass{standalone}",
151
+ r"\usepackage{tikz}",
152
+ r"\usetikzlibrary{shapes,arrows,positioning,calc}",
153
+ r"\pgfdeclarelayer{background}",
154
+ r"\pgfsetlayers{background, main}",
155
+ r"\tikzset{",
156
+ r"connector/.style={",
157
+ r"-latex,",
158
+ r"font=\scriptsize},",
159
+ r"line/.style={",
160
+ r"font=\scriptsize},",
161
+ r"rectangle connector/.style={",
162
+ r"connector,"
163
+ r"to path={(\tikztostart) -- ++(#1,0pt) \tikztonodes |- (\tikztotarget) },",
164
+ r"pos=0.5},"
165
+ r"rectangle connector/.default=0.5cm,",
166
+ r"rectangle line/.style={",
167
+ r"line,"
168
+ r"to path={(\tikztostart) -- ++(#1,0pt) \tikztonodes |- (\tikztotarget) },",
169
+ r"pos=0.5},"
170
+ r"rectangle line/.default=0.5cm,",
171
+ r"straight connector/.style={",
172
+ r"connector,",
173
+ r"to path=--(\tikztotarget) \tikztonodes}",
174
+ r"}",
175
+ r"\begin{document}",
176
+ r"\begin{tikzpicture}",
177
+ "",
178
+ ]
179
+ )
180
+
181
+ TEX_END = "\n".join(
182
+ [
183
+ r"\end{tikzpicture}",
184
+ r"\end{document}",
185
+ ]
186
+ )
@@ -0,0 +1,45 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyrbd
3
+ Version: 0.2.5
4
+ Summary: Package for creating simple reliability block diagrams (RBDs) using LaTeX and TikZ.
5
+ Project-URL: Repository, https://github.com/hghugdal/pyrbd
6
+ Project-URL: Issues, https://github.com/hghugdal/pyrbd/issues
7
+ Project-URL: Documentation, https://hghugdal.github.io/pyrbd
8
+ Author-email: hghugdal <hghugdal@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Programming Language :: Python :: 3
13
+ Requires-Python: >=3.10
14
+ Requires-Dist: pymupdf>=1.26.3
15
+ Description-Content-Type: text/markdown
16
+
17
+ # <img alt="pyRBDlogo" src=docs/images/logo.svg width=40 align=top> pyRBD
18
+
19
+ <img alt="Python" src="https://img.shields.io/badge/Python->= 3.10-blue?logo=python&link=None"> ![PyPI - Version](https://img.shields.io/pypi/v/pyrbd?link=https%3A%2F%2Fpypi.org%2Fproject%2Fpyrbd) <img alt="Tests" src="https://img.shields.io/badge/Tests-Passing-darkgreen?logo=pytest&link=None"> <img alt="Coverage" src="https://img.shields.io/badge/Coverage-100%25-darkgreen?link=None"> <img alt="Pylint" src="https://img.shields.io/badge/Pylint-10%2F10-darkgreen?link=None">
20
+
21
+ A Python package for creating simple reliability block diagrams (RBDs) using `LaTeX` and [`TikZ`](https://en.wikipedia.org/wiki/PGF/TikZ).
22
+
23
+ ## Dependencies
24
+ `pyRBD` requires a working installation of `LaTeX` including [`TikZ`](https://en.wikipedia.org/wiki/PGF/TikZ) and [`latexmk`](https://ctan.org/pkg/latexmk/).
25
+
26
+ ## Simple example diagram
27
+ The blocks of the RBD are defined using `Block`, `Series` and `Group`, and the diagram itself is handled by the `Diagram` class. A simple example is given by the code
28
+ ```python linenums="1"
29
+ from pyrbd import Block, Diagram
30
+
31
+ start_block = Block("Start", "blue!30", parent=None)
32
+ parallel = 2 * Block("Parallel blocks", "gray", parent=start_block)
33
+ end_block = Block("End", "green!50", parent=parallel)
34
+
35
+ diagram = Diagram(
36
+ "simple_RBD",
37
+ blocks=[start_block, parallel, end_block],
38
+ )
39
+ diagram.write()
40
+ diagram.compile()
41
+ ```
42
+ producing the following diagram
43
+ <div><img src="docs/examples/simple_RBD.svg" width=500/></div>
44
+
45
+ For more examples, visit the [documentation](https://hghugdal.github.io/pyrbd/).
@@ -0,0 +1,7 @@
1
+ pyrbd/__init__.py,sha256=mpsb022BX9eDGSBhuYIr2gOr4sg_M9sYbPGHLPIdvl0,219
2
+ pyrbd/block.py,sha256=ETRZJeEUU4OPk0qGo6zZIsaR1oLkyDPf1kelfuKWNsc,12219
3
+ pyrbd/diagram.py,sha256=NVAsdfmqAu6-6J2mT5Gp2W_8a9WsI2iMbKjcEI6QFlE,5092
4
+ pyrbd-0.2.5.dist-info/METADATA,sha256=WN1raYOu5KJtH5wj-7b1P8wH8-1GGHmnnqvwrt5hmZs,2196
5
+ pyrbd-0.2.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
6
+ pyrbd-0.2.5.dist-info/licenses/LICENSE,sha256=mxXMgWpFgk71JpEEElFrb9FJxeoaDgvrU8gjUUU9LzA,1069
7
+ pyrbd-0.2.5.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 H. G. Hugdal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.