hekatan 0.1.0__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.
hekatan-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Giorgio Burbanelli
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.
hekatan-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.4
2
+ Name: hekatan
3
+ Version: 0.1.0
4
+ Summary: Python display library for engineering calculations - matrices, equations, formatted output
5
+ Author-email: Giorgio Burbanelli <giorgioburbanelli89@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/GiorgioBurbanelli89/pyhekatan
8
+ Project-URL: Repository, https://github.com/GiorgioBurbanelli89/pyhekatan
9
+ Project-URL: Issues, https://github.com/GiorgioBurbanelli89/pyhekatan/issues
10
+ Keywords: engineering,calculations,matrix,equations,display,hekatan
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Intended Audience :: Education
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering
23
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Dynamic: license-file
28
+
29
+ # hekatan
30
+
31
+ > Python display library for engineering calculations — matrices, equations, formatted output
32
+
33
+ [![PyPI](https://img.shields.io/pypi/v/hekatan.svg)](https://pypi.org/project/hekatan/)
34
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install hekatan
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ```python
45
+ from hekatan import matrix, eq, var, fraction, title, text, show
46
+
47
+ title("Beam Design")
48
+ text("Rectangular section properties:")
49
+
50
+ var("b", 300, "mm", "beam width")
51
+ var("h", 500, "mm", "beam height")
52
+
53
+ eq("A", 300 * 500, "mm²")
54
+
55
+ title("Stiffness Matrix", level=2)
56
+ K = [[12, 6, -12, 6],
57
+ [6, 4, -6, 2],
58
+ [-12, -6, 12, -6],
59
+ [6, 2, -6, 4]]
60
+ matrix(K, "K")
61
+
62
+ title("Design Ratio", level=2)
63
+ fraction("M_u", "φ · b · d²", "R_n")
64
+
65
+ show() # Opens formatted HTML in your browser
66
+ ```
67
+
68
+ ## How It Works
69
+
70
+ Each function (`matrix()`, `eq()`, `var()`, etc.) works in **3 modes**:
71
+
72
+ | Mode | When | Behavior |
73
+ |------|------|----------|
74
+ | **Hekatan** | Inside Hekatan Calc (WPF/CLI) | Emits `@@HEKATAN` markers → rendered as formatted HTML |
75
+ | **Standalone** | Regular Python script | `show()` generates HTML, opens in browser |
76
+ | **Console** | Fallback | ASCII formatted output |
77
+
78
+ Mode is auto-detected via `HEKATAN_RENDER=1` environment variable.
79
+
80
+ ## Functions
81
+
82
+ | Function | Description | Example |
83
+ |----------|-------------|---------|
84
+ | `matrix(data, name)` | Display formatted matrix | `matrix([[1,2],[3,4]], "A")` |
85
+ | `eq(name, value, unit)` | Equation: name = value unit | `eq("F", 25.5, "kN")` |
86
+ | `var(name, value, unit, desc)` | Variable with description | `var("b", 300, "mm", "width")` |
87
+ | `fraction(num, den, name)` | Formatted fraction | `fraction("M", "S", "σ")` |
88
+ | `title(text, level)` | Heading (h1-h6) | `title("Results", 2)` |
89
+ | `text(content)` | Paragraph text | `text("Design is OK.")` |
90
+ | `show(filename)` | Generate HTML + open browser | `show()` or `show("out.html")` |
91
+ | `clear()` | Clear accumulated buffer | `clear()` |
92
+ | `set_mode(mode)` | Force mode | `set_mode("console")` |
93
+
94
+ ## Integration with Hekatan Calc
95
+
96
+ When used inside a Hekatan Calc `.hcalc` document:
97
+
98
+ ```
99
+ # My Calculation
100
+
101
+ @{python}
102
+ from hekatan import matrix, eq
103
+
104
+ K = [[12, 6], [6, 4]]
105
+ matrix(K, "K")
106
+ eq("det_K", 12*4 - 6*6)
107
+ @{end python}
108
+ ```
109
+
110
+ The output is automatically formatted with Hekatan's CSS (matrices with brackets, equations with proper typography).
111
+
112
+ ## License
113
+
114
+ MIT — Giorgio Burbanelli
@@ -0,0 +1,86 @@
1
+ # hekatan
2
+
3
+ > Python display library for engineering calculations — matrices, equations, formatted output
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/hekatan.svg)](https://pypi.org/project/hekatan/)
6
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pip install hekatan
12
+ ```
13
+
14
+ ## Quick Start
15
+
16
+ ```python
17
+ from hekatan import matrix, eq, var, fraction, title, text, show
18
+
19
+ title("Beam Design")
20
+ text("Rectangular section properties:")
21
+
22
+ var("b", 300, "mm", "beam width")
23
+ var("h", 500, "mm", "beam height")
24
+
25
+ eq("A", 300 * 500, "mm²")
26
+
27
+ title("Stiffness Matrix", level=2)
28
+ K = [[12, 6, -12, 6],
29
+ [6, 4, -6, 2],
30
+ [-12, -6, 12, -6],
31
+ [6, 2, -6, 4]]
32
+ matrix(K, "K")
33
+
34
+ title("Design Ratio", level=2)
35
+ fraction("M_u", "φ · b · d²", "R_n")
36
+
37
+ show() # Opens formatted HTML in your browser
38
+ ```
39
+
40
+ ## How It Works
41
+
42
+ Each function (`matrix()`, `eq()`, `var()`, etc.) works in **3 modes**:
43
+
44
+ | Mode | When | Behavior |
45
+ |------|------|----------|
46
+ | **Hekatan** | Inside Hekatan Calc (WPF/CLI) | Emits `@@HEKATAN` markers → rendered as formatted HTML |
47
+ | **Standalone** | Regular Python script | `show()` generates HTML, opens in browser |
48
+ | **Console** | Fallback | ASCII formatted output |
49
+
50
+ Mode is auto-detected via `HEKATAN_RENDER=1` environment variable.
51
+
52
+ ## Functions
53
+
54
+ | Function | Description | Example |
55
+ |----------|-------------|---------|
56
+ | `matrix(data, name)` | Display formatted matrix | `matrix([[1,2],[3,4]], "A")` |
57
+ | `eq(name, value, unit)` | Equation: name = value unit | `eq("F", 25.5, "kN")` |
58
+ | `var(name, value, unit, desc)` | Variable with description | `var("b", 300, "mm", "width")` |
59
+ | `fraction(num, den, name)` | Formatted fraction | `fraction("M", "S", "σ")` |
60
+ | `title(text, level)` | Heading (h1-h6) | `title("Results", 2)` |
61
+ | `text(content)` | Paragraph text | `text("Design is OK.")` |
62
+ | `show(filename)` | Generate HTML + open browser | `show()` or `show("out.html")` |
63
+ | `clear()` | Clear accumulated buffer | `clear()` |
64
+ | `set_mode(mode)` | Force mode | `set_mode("console")` |
65
+
66
+ ## Integration with Hekatan Calc
67
+
68
+ When used inside a Hekatan Calc `.hcalc` document:
69
+
70
+ ```
71
+ # My Calculation
72
+
73
+ @{python}
74
+ from hekatan import matrix, eq
75
+
76
+ K = [[12, 6], [6, 4]]
77
+ matrix(K, "K")
78
+ eq("det_K", 12*4 - 6*6)
79
+ @{end python}
80
+ ```
81
+
82
+ The output is automatically formatted with Hekatan's CSS (matrices with brackets, equations with proper typography).
83
+
84
+ ## License
85
+
86
+ MIT — Giorgio Burbanelli
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hekatan"
7
+ version = "0.1.0"
8
+ description = "Python display library for engineering calculations - matrices, equations, formatted output"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.8"
12
+ authors = [
13
+ {name = "Giorgio Burbanelli", email = "giorgioburbanelli89@gmail.com"}
14
+ ]
15
+ keywords = ["engineering", "calculations", "matrix", "equations", "display", "hekatan"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Science/Research",
19
+ "Intended Audience :: Education",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.8",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Topic :: Scientific/Engineering",
29
+ "Topic :: Scientific/Engineering :: Mathematics",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/GiorgioBurbanelli89/pyhekatan"
34
+ Repository = "https://github.com/GiorgioBurbanelli89/pyhekatan"
35
+ Issues = "https://github.com/GiorgioBurbanelli89/pyhekatan/issues"
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,32 @@
1
+ """
2
+ Hekatan - Python display library for engineering calculations.
3
+
4
+ Works in 3 modes:
5
+ 1. Inside Hekatan Calc (WPF/CLI) - emits markers for formatted HTML rendering
6
+ 2. Standalone Python - show() generates HTML and opens in browser
7
+ 3. Console fallback - ASCII formatted output
8
+
9
+ Usage:
10
+ from hekatan import matrix, eq, var, fraction, title, text, show
11
+
12
+ A = [[1, 2], [3, 4]]
13
+ matrix(A, "A")
14
+ eq("F", 25.5, "kN")
15
+ var("b", 300, "mm")
16
+ show() # Opens HTML in browser (standalone mode)
17
+ """
18
+
19
+ __version__ = "0.1.0"
20
+
21
+ from hekatan.display import (
22
+ matrix,
23
+ eq,
24
+ var,
25
+ fraction,
26
+ title,
27
+ text,
28
+ heading,
29
+ show,
30
+ clear,
31
+ set_mode,
32
+ )
@@ -0,0 +1,473 @@
1
+ """
2
+ Core display functions for Hekatan.
3
+
4
+ Each function works in 3 modes:
5
+ - HEKATAN mode: prints @@HEKATAN markers (detected by C# side)
6
+ - STANDALONE mode: accumulates HTML, show() opens in browser
7
+ - CONSOLE mode: ASCII formatted output
8
+ """
9
+
10
+ import os
11
+ import sys
12
+ import tempfile
13
+ import webbrowser
14
+ from typing import Any, List, Optional, Union
15
+
16
+ # ============================================================
17
+ # Mode detection
18
+ # ============================================================
19
+
20
+ _MODE = None # 'hekatan', 'standalone', 'console'
21
+ _BUFFER = [] # Accumulated elements for standalone mode
22
+
23
+
24
+ def _detect_mode() -> str:
25
+ """Auto-detect rendering mode."""
26
+ if os.environ.get("HEKATAN_RENDER") == "1":
27
+ return "hekatan"
28
+ return "standalone"
29
+
30
+
31
+ def _get_mode() -> str:
32
+ global _MODE
33
+ if _MODE is None:
34
+ _MODE = _detect_mode()
35
+ return _MODE
36
+
37
+
38
+ def set_mode(mode: str):
39
+ """Force a specific mode: 'hekatan', 'standalone', or 'console'."""
40
+ global _MODE
41
+ if mode not in ("hekatan", "standalone", "console"):
42
+ raise ValueError(f"Invalid mode: {mode}. Use 'hekatan', 'standalone', or 'console'.")
43
+ _MODE = mode
44
+
45
+
46
+ def clear():
47
+ """Clear the accumulated buffer."""
48
+ _BUFFER.clear()
49
+
50
+
51
+ # ============================================================
52
+ # Matrix display
53
+ # ============================================================
54
+
55
+ def matrix(data: List[List[Any]], name: Optional[str] = None):
56
+ """
57
+ Display a matrix with formatted brackets.
58
+
59
+ Args:
60
+ data: 2D list [[row1], [row2], ...]
61
+ name: Optional name (e.g. "A", "K")
62
+
63
+ Example:
64
+ matrix([[1, 2], [3, 4]], "A")
65
+ """
66
+ mode = _get_mode()
67
+
68
+ if mode == "hekatan":
69
+ rows = ";".join(",".join(str(x) for x in row) for row in data)
70
+ marker = f"@@HEKATAN:MATRIX:{name or ''}[{rows}]"
71
+ print(marker)
72
+
73
+ elif mode == "standalone":
74
+ html = _matrix_to_html(data, name)
75
+ _BUFFER.append(html)
76
+
77
+ else: # console
78
+ _matrix_to_console(data, name)
79
+
80
+
81
+ def _matrix_to_html(data: List[List[Any]], name: Optional[str] = None) -> str:
82
+ """Generate HTML for a matrix using Hekatan CSS classes."""
83
+ rows_html = []
84
+ for row in data:
85
+ cells = "".join(f'<td class="td">{x}</td>' for x in row)
86
+ # Empty first/last cells create bracket effect
87
+ rows_html.append(f'<tr class="tr"><td class="td"></td>{cells}<td class="td"></td></tr>')
88
+
89
+ table = f'<table class="matrix">{"".join(rows_html)}</table>'
90
+
91
+ if name:
92
+ return f'<div class="eq"><var>{name}</var> = {table}</div>'
93
+ return f'<div class="eq">{table}</div>'
94
+
95
+
96
+ def _matrix_to_console(data: List[List[Any]], name: Optional[str] = None):
97
+ """Print matrix in ASCII format."""
98
+ if not data:
99
+ print("[]")
100
+ return
101
+
102
+ # Calculate column widths
103
+ col_widths = []
104
+ for j in range(len(data[0])):
105
+ w = max(len(str(data[i][j])) for i in range(len(data)))
106
+ col_widths.append(w)
107
+
108
+ prefix = f"{name} = " if name else ""
109
+ pad = " " * len(prefix)
110
+
111
+ for i, row in enumerate(data):
112
+ cells = " ".join(str(row[j]).rjust(col_widths[j]) for j in range(len(row)))
113
+ line_prefix = prefix if i == len(data) // 2 else pad
114
+ print(f"{line_prefix}| {cells} |")
115
+
116
+
117
+ # ============================================================
118
+ # Equation display
119
+ # ============================================================
120
+
121
+ def eq(name: str, value: Any, unit: str = ""):
122
+ """
123
+ Display a formatted equation: name = value [unit]
124
+
125
+ Args:
126
+ name: Variable name (e.g. "F", "M_u")
127
+ value: Numeric value
128
+ unit: Optional unit string (e.g. "kN", "mm")
129
+
130
+ Example:
131
+ eq("F", 25.5, "kN")
132
+ eq("A_s", 1256.64, "mm²")
133
+ """
134
+ mode = _get_mode()
135
+
136
+ if mode == "hekatan":
137
+ marker = f"@@HEKATAN:EQ:{name}={value}"
138
+ if unit:
139
+ marker += f"|{unit}"
140
+ print(marker)
141
+
142
+ elif mode == "standalone":
143
+ unit_html = f' <span class="unit">{unit}</span>' if unit else ""
144
+ # Handle subscripts: A_s -> A<sub>s</sub>
145
+ display_name = _format_subscript(name)
146
+ html = f'<div class="eq"><var>{display_name}</var> = <b>{value}</b>{unit_html}</div>'
147
+ _BUFFER.append(html)
148
+
149
+ else: # console
150
+ unit_str = f" {unit}" if unit else ""
151
+ print(f"{name} = {value}{unit_str}")
152
+
153
+
154
+ # ============================================================
155
+ # Variable display (with description)
156
+ # ============================================================
157
+
158
+ def var(name: str, value: Any, unit: str = "", desc: str = ""):
159
+ """
160
+ Display a variable with optional description.
161
+
162
+ Args:
163
+ name: Variable name
164
+ value: Value
165
+ unit: Optional unit
166
+ desc: Optional description
167
+
168
+ Example:
169
+ var("b", 300, "mm", "ancho de la viga")
170
+ """
171
+ mode = _get_mode()
172
+
173
+ if mode == "hekatan":
174
+ parts = [f"@@HEKATAN:VAR:{name}={value}"]
175
+ if unit:
176
+ parts[0] += f"|{unit}"
177
+ if desc:
178
+ parts[0] += f"|{desc}"
179
+ print(parts[0])
180
+
181
+ elif mode == "standalone":
182
+ display_name = _format_subscript(name)
183
+ unit_html = f' <span class="unit">{unit}</span>' if unit else ""
184
+ desc_html = f' <span class="desc">— {desc}</span>' if desc else ""
185
+ html = f'<div class="eq"><var>{display_name}</var> = <b>{value}</b>{unit_html}{desc_html}</div>'
186
+ _BUFFER.append(html)
187
+
188
+ else: # console
189
+ unit_str = f" {unit}" if unit else ""
190
+ desc_str = f" - {desc}" if desc else ""
191
+ print(f"{name} = {value}{unit_str}{desc_str}")
192
+
193
+
194
+ # ============================================================
195
+ # Fraction display
196
+ # ============================================================
197
+
198
+ def fraction(numerator: Any, denominator: Any, name: Optional[str] = None):
199
+ """
200
+ Display a fraction.
201
+
202
+ Args:
203
+ numerator: Top value
204
+ denominator: Bottom value
205
+ name: Optional name for the result
206
+
207
+ Example:
208
+ fraction("M_u", "phi * b * d²", "R_n")
209
+ """
210
+ mode = _get_mode()
211
+
212
+ if mode == "hekatan":
213
+ marker = f"@@HEKATAN:FRAC:{name or ''}={numerator}/{denominator}"
214
+ print(marker)
215
+
216
+ elif mode == "standalone":
217
+ name_html = f'<var>{_format_subscript(name)}</var> = ' if name else ""
218
+ html = (
219
+ f'<div class="eq">{name_html}'
220
+ f'<span class="dvc">'
221
+ f'<span class="dvl">{numerator}</span>'
222
+ f'<span class="dvl">{denominator}</span>'
223
+ f'</span></div>'
224
+ )
225
+ _BUFFER.append(html)
226
+
227
+ else: # console
228
+ prefix = f"{name} = " if name else ""
229
+ num_str = str(numerator)
230
+ den_str = str(denominator)
231
+ width = max(len(num_str), len(den_str))
232
+ print(f"{prefix}{num_str.center(width)}")
233
+ print(f"{' ' * len(prefix)}{'-' * width}")
234
+ print(f"{' ' * len(prefix)}{den_str.center(width)}")
235
+
236
+
237
+ # ============================================================
238
+ # Text elements
239
+ # ============================================================
240
+
241
+ def title(text_content: str, level: int = 1):
242
+ """Display a heading."""
243
+ mode = _get_mode()
244
+ tag = f"h{min(level, 6)}"
245
+
246
+ if mode == "hekatan":
247
+ print(f"@@HEKATAN:TITLE:{level}|{text_content}")
248
+
249
+ elif mode == "standalone":
250
+ _BUFFER.append(f"<{tag}>{text_content}</{tag}>")
251
+
252
+ else:
253
+ marker = "#" * level
254
+ print(f"\n{marker} {text_content}\n")
255
+
256
+
257
+ def heading(text_content: str, level: int = 2):
258
+ """Alias for title()."""
259
+ title(text_content, level)
260
+
261
+
262
+ def text(content: str):
263
+ """Display plain text or markdown."""
264
+ mode = _get_mode()
265
+
266
+ if mode == "hekatan":
267
+ print(f"@@HEKATAN:TEXT:{content}")
268
+
269
+ elif mode == "standalone":
270
+ _BUFFER.append(f"<p>{content}</p>")
271
+
272
+ else:
273
+ print(content)
274
+
275
+
276
+ # ============================================================
277
+ # Show (standalone mode - generates HTML)
278
+ # ============================================================
279
+
280
+ def show(filename: Optional[str] = None):
281
+ """
282
+ Generate HTML document and open in browser.
283
+ Only works in standalone mode.
284
+
285
+ Args:
286
+ filename: Optional output file path. If None, uses temp file.
287
+ """
288
+ if not _BUFFER:
289
+ print("hekatan: nothing to show")
290
+ return
291
+
292
+ html = _generate_html()
293
+
294
+ if filename:
295
+ path = filename
296
+ else:
297
+ fd, path = tempfile.mkstemp(suffix=".html", prefix="hekatan_")
298
+ os.close(fd)
299
+
300
+ with open(path, "w", encoding="utf-8") as f:
301
+ f.write(html)
302
+
303
+ webbrowser.open(f"file://{os.path.abspath(path)}")
304
+ print(f"hekatan: opened {path}")
305
+
306
+
307
+ def _generate_html() -> str:
308
+ """Generate complete HTML document with Hekatan CSS."""
309
+ body = "\n".join(_BUFFER)
310
+ return f"""<!DOCTYPE html>
311
+ <html lang="en">
312
+ <head>
313
+ <meta charset="UTF-8">
314
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
315
+ <title>Hekatan Output</title>
316
+ <style>
317
+ {_CSS}
318
+ </style>
319
+ </head>
320
+ <body>
321
+ <div class="hekatan-doc">
322
+ {body}
323
+ </div>
324
+ </body>
325
+ </html>"""
326
+
327
+
328
+ # ============================================================
329
+ # Helpers
330
+ # ============================================================
331
+
332
+ def _format_subscript(name: str) -> str:
333
+ """Convert A_s to A<sub>s</sub>."""
334
+ if "_" in name:
335
+ parts = name.split("_", 1)
336
+ return f"{parts[0]}<sub>{parts[1]}</sub>"
337
+ return name
338
+
339
+
340
+ # ============================================================
341
+ # Embedded CSS (matches Hekatan Calc rendering)
342
+ # ============================================================
343
+
344
+ _CSS = """
345
+ /* Hekatan Display CSS */
346
+ * { margin: 0; padding: 0; box-sizing: border-box; }
347
+
348
+ body {
349
+ font-family: 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
350
+ font-size: 11pt;
351
+ line-height: 1.6;
352
+ color: #333;
353
+ background: #fff;
354
+ padding: 24px 32px;
355
+ max-width: 900px;
356
+ margin: 0 auto;
357
+ }
358
+
359
+ .hekatan-doc { padding: 16px 0; }
360
+
361
+ h1 { font-size: 1.8em; margin: 16px 0 8px; color: #1a1a1a; border-bottom: 2px solid #e0c060; padding-bottom: 4px; }
362
+ h2 { font-size: 1.4em; margin: 14px 0 6px; color: #2a2a2a; }
363
+ h3 { font-size: 1.15em; margin: 12px 0 4px; color: #333; }
364
+
365
+ p { margin: 6px 0; }
366
+
367
+ /* Equation line */
368
+ .eq {
369
+ font-family: 'Cambria Math', 'Latin Modern Math', 'STIX Two Math', serif;
370
+ font-size: 11pt;
371
+ line-height: 2;
372
+ margin: 2px 0;
373
+ display: flex;
374
+ align-items: center;
375
+ gap: 6px;
376
+ flex-wrap: wrap;
377
+ }
378
+
379
+ .eq var {
380
+ font-style: italic;
381
+ color: #333;
382
+ }
383
+
384
+ .eq b {
385
+ font-weight: 600;
386
+ color: #1a1a1a;
387
+ }
388
+
389
+ .unit {
390
+ font-size: 0.9em;
391
+ color: #666;
392
+ }
393
+
394
+ .desc {
395
+ font-size: 0.9em;
396
+ color: #888;
397
+ font-style: italic;
398
+ }
399
+
400
+ /* Matrix */
401
+ .matrix {
402
+ display: inline-table;
403
+ border-collapse: collapse;
404
+ margin: 4px 2px;
405
+ vertical-align: middle;
406
+ }
407
+
408
+ .matrix .tr {
409
+ display: table-row;
410
+ }
411
+
412
+ .matrix .td {
413
+ display: table-cell;
414
+ text-align: center;
415
+ padding: 2px 8px;
416
+ font-size: 10.5pt;
417
+ }
418
+
419
+ /* Bracket effect: first and last cells have borders */
420
+ .matrix .tr .td:first-child {
421
+ border-left: 2px solid #333;
422
+ border-top: 2px solid #333;
423
+ border-bottom: 2px solid #333;
424
+ padding: 2px 4px;
425
+ width: 4px;
426
+ }
427
+
428
+ .matrix .tr:first-child .td:first-child { border-bottom: none; }
429
+ .matrix .tr:last-child .td:first-child { border-top: none; }
430
+ .matrix .tr:not(:first-child):not(:last-child) .td:first-child { border-top: none; border-bottom: none; }
431
+
432
+ .matrix .tr .td:last-child {
433
+ border-right: 2px solid #333;
434
+ border-top: 2px solid #333;
435
+ border-bottom: 2px solid #333;
436
+ padding: 2px 4px;
437
+ width: 4px;
438
+ }
439
+
440
+ .matrix .tr:first-child .td:last-child { border-bottom: none; }
441
+ .matrix .tr:last-child .td:last-child { border-top: none; }
442
+ .matrix .tr:not(:first-child):not(:last-child) .td:last-child { border-top: none; border-bottom: none; }
443
+
444
+ /* Fraction */
445
+ .dvc {
446
+ display: inline-flex;
447
+ flex-direction: column;
448
+ align-items: center;
449
+ vertical-align: middle;
450
+ margin: 0 4px;
451
+ }
452
+
453
+ .dvc .dvl {
454
+ display: block;
455
+ text-align: center;
456
+ padding: 1px 6px;
457
+ }
458
+
459
+ .dvc .dvl:first-child {
460
+ border-bottom: 1.5px solid #333;
461
+ }
462
+
463
+ /* Code output */
464
+ .lang-output-text {
465
+ font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
466
+ font-size: 10.5pt;
467
+ line-height: 1.5;
468
+ white-space: pre-wrap;
469
+ color: #333;
470
+ padding: 4px 0;
471
+ margin: 4px 0;
472
+ }
473
+ """
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.4
2
+ Name: hekatan
3
+ Version: 0.1.0
4
+ Summary: Python display library for engineering calculations - matrices, equations, formatted output
5
+ Author-email: Giorgio Burbanelli <giorgioburbanelli89@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/GiorgioBurbanelli89/pyhekatan
8
+ Project-URL: Repository, https://github.com/GiorgioBurbanelli89/pyhekatan
9
+ Project-URL: Issues, https://github.com/GiorgioBurbanelli89/pyhekatan/issues
10
+ Keywords: engineering,calculations,matrix,equations,display,hekatan
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Intended Audience :: Education
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering
23
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Dynamic: license-file
28
+
29
+ # hekatan
30
+
31
+ > Python display library for engineering calculations — matrices, equations, formatted output
32
+
33
+ [![PyPI](https://img.shields.io/pypi/v/hekatan.svg)](https://pypi.org/project/hekatan/)
34
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install hekatan
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ```python
45
+ from hekatan import matrix, eq, var, fraction, title, text, show
46
+
47
+ title("Beam Design")
48
+ text("Rectangular section properties:")
49
+
50
+ var("b", 300, "mm", "beam width")
51
+ var("h", 500, "mm", "beam height")
52
+
53
+ eq("A", 300 * 500, "mm²")
54
+
55
+ title("Stiffness Matrix", level=2)
56
+ K = [[12, 6, -12, 6],
57
+ [6, 4, -6, 2],
58
+ [-12, -6, 12, -6],
59
+ [6, 2, -6, 4]]
60
+ matrix(K, "K")
61
+
62
+ title("Design Ratio", level=2)
63
+ fraction("M_u", "φ · b · d²", "R_n")
64
+
65
+ show() # Opens formatted HTML in your browser
66
+ ```
67
+
68
+ ## How It Works
69
+
70
+ Each function (`matrix()`, `eq()`, `var()`, etc.) works in **3 modes**:
71
+
72
+ | Mode | When | Behavior |
73
+ |------|------|----------|
74
+ | **Hekatan** | Inside Hekatan Calc (WPF/CLI) | Emits `@@HEKATAN` markers → rendered as formatted HTML |
75
+ | **Standalone** | Regular Python script | `show()` generates HTML, opens in browser |
76
+ | **Console** | Fallback | ASCII formatted output |
77
+
78
+ Mode is auto-detected via `HEKATAN_RENDER=1` environment variable.
79
+
80
+ ## Functions
81
+
82
+ | Function | Description | Example |
83
+ |----------|-------------|---------|
84
+ | `matrix(data, name)` | Display formatted matrix | `matrix([[1,2],[3,4]], "A")` |
85
+ | `eq(name, value, unit)` | Equation: name = value unit | `eq("F", 25.5, "kN")` |
86
+ | `var(name, value, unit, desc)` | Variable with description | `var("b", 300, "mm", "width")` |
87
+ | `fraction(num, den, name)` | Formatted fraction | `fraction("M", "S", "σ")` |
88
+ | `title(text, level)` | Heading (h1-h6) | `title("Results", 2)` |
89
+ | `text(content)` | Paragraph text | `text("Design is OK.")` |
90
+ | `show(filename)` | Generate HTML + open browser | `show()` or `show("out.html")` |
91
+ | `clear()` | Clear accumulated buffer | `clear()` |
92
+ | `set_mode(mode)` | Force mode | `set_mode("console")` |
93
+
94
+ ## Integration with Hekatan Calc
95
+
96
+ When used inside a Hekatan Calc `.hcalc` document:
97
+
98
+ ```
99
+ # My Calculation
100
+
101
+ @{python}
102
+ from hekatan import matrix, eq
103
+
104
+ K = [[12, 6], [6, 4]]
105
+ matrix(K, "K")
106
+ eq("det_K", 12*4 - 6*6)
107
+ @{end python}
108
+ ```
109
+
110
+ The output is automatically formatted with Hekatan's CSS (matrices with brackets, equations with proper typography).
111
+
112
+ ## License
113
+
114
+ MIT — Giorgio Burbanelli
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/hekatan/__init__.py
5
+ src/hekatan/display.py
6
+ src/hekatan.egg-info/PKG-INFO
7
+ src/hekatan.egg-info/SOURCES.txt
8
+ src/hekatan.egg-info/dependency_links.txt
9
+ src/hekatan.egg-info/top_level.txt
10
+ tests/test_display.py
@@ -0,0 +1 @@
1
+ hekatan
@@ -0,0 +1,46 @@
1
+ """Basic tests for hekatan display functions."""
2
+
3
+ import sys
4
+ import os
5
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
6
+
7
+ from hekatan import matrix, eq, var, fraction, title, text, clear, set_mode
8
+ import hekatan.display as disp
9
+
10
+
11
+ def test_matrix_console():
12
+ set_mode("console")
13
+ matrix([[1, 2], [3, 4]], "A")
14
+
15
+
16
+ def test_eq_console():
17
+ set_mode("console")
18
+ eq("F", 25.5, "kN")
19
+
20
+
21
+ def test_var_console():
22
+ set_mode("console")
23
+ var("b", 300, "mm", "ancho")
24
+
25
+
26
+ def test_fraction_console():
27
+ set_mode("console")
28
+ fraction("M_u", "phi*b*d^2", "R_n")
29
+
30
+
31
+ def test_standalone_buffer():
32
+ set_mode("standalone")
33
+ clear()
34
+ matrix([[1, 2], [3, 4]], "A")
35
+ eq("F", 25.5, "kN")
36
+ var("b", 300, "mm")
37
+ assert len(disp._BUFFER) == 3, f"Expected 3, got {len(disp._BUFFER)}"
38
+
39
+
40
+ if __name__ == "__main__":
41
+ test_matrix_console()
42
+ test_eq_console()
43
+ test_var_console()
44
+ test_fraction_console()
45
+ test_standalone_buffer()
46
+ print("\nAll tests passed!")