pyact-cli 0.4.0__tar.gz → 0.6.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyact-cli
3
- Version: 0.4.0
3
+ Version: 0.6.0
4
4
  Summary: A CLI tool to compile .pamd files to Markdown.
5
5
  Home-page: https://github.com/Abstergo2003/PyAct
6
6
  Author: Abstergo2003
@@ -10,11 +10,14 @@ Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.8
12
12
  Description-Content-Type: text/markdown
13
+ Requires-Dist: python-docx
14
+ Requires-Dist: ezdxf
13
15
  Dynamic: author
14
16
  Dynamic: classifier
15
17
  Dynamic: description
16
18
  Dynamic: description-content-type
17
19
  Dynamic: home-page
20
+ Dynamic: requires-dist
18
21
  Dynamic: requires-python
19
22
  Dynamic: summary
20
23
 
@@ -65,10 +68,17 @@ You can also include other pamd files as templates!
65
68
  <tmp>path/to/another_file</tmp>
66
69
  ```
67
70
 
68
- ### 3. Compile!
71
+ ### 3. Compile to Markdown
69
72
  Run the compiler from your terminal to generate your final Markdown document:
70
73
  ```bash
71
74
  pyact path/to/your_file.pamd -o output.md
72
75
  ```
73
76
 
74
- That's it! You now have a standard Markdown file ready to be shared, published, or converted to PDF.
77
+ ### 4. Compile to Word (DOCX)
78
+ You can also compile straight to a native Microsoft Word Document, complete with CSS styling!
79
+ ```bash
80
+ pyact path/to/your_file.pamd -o output.md --docx output.docx
81
+ ```
82
+ *Note: If you have a `style.css` file in the same directory, PyAct will automatically load it to style your Word Document. You can also specify one manually using the `--css path/to/style.css` flag!*
83
+
84
+ That's it! You now have documents ready to be shared, published, or distributed.
@@ -45,10 +45,17 @@ You can also include other pamd files as templates!
45
45
  <tmp>path/to/another_file</tmp>
46
46
  ```
47
47
 
48
- ### 3. Compile!
48
+ ### 3. Compile to Markdown
49
49
  Run the compiler from your terminal to generate your final Markdown document:
50
50
  ```bash
51
51
  pyact path/to/your_file.pamd -o output.md
52
52
  ```
53
53
 
54
- That's it! You now have a standard Markdown file ready to be shared, published, or converted to PDF.
54
+ ### 4. Compile to Word (DOCX)
55
+ You can also compile straight to a native Microsoft Word Document, complete with CSS styling!
56
+ ```bash
57
+ pyact path/to/your_file.pamd -o output.md --docx output.docx
58
+ ```
59
+ *Note: If you have a `style.css` file in the same directory, PyAct will automatically load it to style your Word Document. You can also specify one manually using the `--css path/to/style.css` flag!*
60
+
61
+ That's it! You now have documents ready to be shared, published, or distributed.
@@ -0,0 +1,243 @@
1
+ import inspect
2
+ from typing import Callable, List, Any
3
+ from pyact.py2tex import py2tex
4
+
5
+ def equation(func: Callable, values: List[Any]) -> str:
6
+ """
7
+ Converts a Python lambda or simple function into a LaTeX math equation string.
8
+
9
+ Why it is needed:
10
+ To allow users to define equations in Python syntax and automatically render them
11
+ as mathematical formulas in the final document, without needing to hand-write LaTeX.
12
+
13
+ Inputs:
14
+ func (Callable): A simple Python function (e.g., `lambda x: x**2`).
15
+ values (List[Any]): The arguments to pass to the function to compute the result.
16
+
17
+ Outputs:
18
+ str: A Markdown math string formatted as `$$ Name = Equation = Value $$`.
19
+ """
20
+ source = inspect.getsource(func)
21
+ value = func(*values)
22
+ name, sep, tail = source.rpartition("=")
23
+ head, sep, equ = tail.rpartition(":")
24
+ return f"$$ {name} = {py2tex(equ.replace(chr(10), ''))} = {value} $$"
25
+
26
+ def image(link: str, caption: str) -> str:
27
+ """
28
+ Generates a Markdown image string with an embedded HTML caption.
29
+
30
+ Why it is needed:
31
+ Standard Markdown images `![caption](url)` don't display visible captions below the image.
32
+ This helper injects a styled HTML `<span>` below the image to force a visual caption.
33
+
34
+ Inputs:
35
+ link (str): The URL or local path to the image.
36
+ caption (str): The text to display as the caption.
37
+
38
+ Outputs:
39
+ str: A formatted string containing the Markdown image and HTML span caption.
40
+ """
41
+ image_link = f"![{caption}]({link})"
42
+ caption_html = f"<span style='text-align: center; display: block; font-style: italic;'>{caption}</span>"
43
+ return f"{image_link} \n {caption_html}"
44
+
45
+ def table(headers: List[str], values: List[List[Any]], caption: str, add_no: bool = False, starting_number: int = 1) -> str:
46
+ """
47
+ Generates a Markdown table from Python lists, including a caption.
48
+
49
+ Why it is needed:
50
+ Writing Markdown tables by hand is tedious. This function allows users to programmatically
51
+ build tables from 2D data arrays, inject automatic numbering, and append a styled caption.
52
+
53
+ Inputs:
54
+ headers (List[str]): List of column header names.
55
+ values (List[List[Any]]): A 2D list containing the row data.
56
+ caption (str): The text to display above the table.
57
+ add_no (bool): If True, adds an auto-incrementing "No." column to the left.
58
+ starting_number (int): The starting index for the "No." column.
59
+
60
+ Outputs:
61
+ str: A formatted Markdown string containing the caption and the table.
62
+ """
63
+ if add_no:
64
+ headers_string = f"|No.|"
65
+ breaker_line = "|---|"
66
+ else:
67
+ headers_string = f"|"
68
+ breaker_line = "|"
69
+
70
+ for i in headers:
71
+ headers_string += f"{i}|"
72
+ breaker_line += "-"*len(i)+"|"
73
+
74
+ table_content = ""
75
+ for i, row in enumerate(values):
76
+ if add_no:
77
+ table_content += f"|{i+starting_number}|"
78
+ else:
79
+ table_content += "|"
80
+ for value in row:
81
+ table_content += f"{value}|"
82
+ table_content += "\n"
83
+
84
+ caption_html = f"<span style='text-align: center; display: block; font-style: italic;'>{caption}</span>"
85
+ return f"{caption_html} \n {headers_string}\n{breaker_line}\n{table_content}"
86
+
87
+ def hyperlink(caption: str, link: str) -> str:
88
+ """
89
+ Generates a simple Markdown hyperlink.
90
+
91
+ Why it is needed:
92
+ Provides a standardized helper method in the PyAct ecosystem for injecting links.
93
+
94
+ Inputs:
95
+ caption (str): The clickable text to display.
96
+ link (str): The URL destination.
97
+
98
+ Outputs:
99
+ str: The Markdown hyperlink string `[caption](link)`.
100
+ """
101
+ return f"[{caption}]({link})"
102
+
103
+ def unordered_list(items: List[str]) -> str:
104
+ """
105
+ Generates a Markdown unordered (bullet) list from a Python list.
106
+
107
+ Why it is needed:
108
+ Allows programmatic generation of bullet points from array data.
109
+
110
+ Inputs:
111
+ items (List[str]): A list of string items.
112
+
113
+ Outputs:
114
+ str: A Markdown string where each item is prefixed with `* `.
115
+ """
116
+ list_string = ""
117
+ for i in items:
118
+ list_string += f"* {i}\n"
119
+ return list_string
120
+
121
+ def ordered_list(items: List[str]) -> str:
122
+ """
123
+ Generates a Markdown ordered (numbered) list from a Python list.
124
+
125
+ Why it is needed:
126
+ Allows programmatic generation of numbered lists from array data.
127
+
128
+ Inputs:
129
+ items (List[str]): A list of string items.
130
+
131
+ Outputs:
132
+ str: A Markdown string where each item is numbered sequentially (1. 2. 3.).
133
+ """
134
+ list_string = ""
135
+ for i, item in enumerate(items):
136
+ list_string += f"{i+1}. {item}\n"
137
+ return list_string
138
+
139
+ def checklist(items: List[str]) -> str:
140
+ """
141
+ Generates a Markdown task checklist from a Python list.
142
+
143
+ Why it is needed:
144
+ Allows programmatic generation of checkable task items.
145
+
146
+ Inputs:
147
+ items (List[str]): A list of string items.
148
+
149
+ Outputs:
150
+ str: A Markdown string where each item is prefixed with `- [ ] `.
151
+ """
152
+ list_string = ""
153
+ for i in items:
154
+ list_string += f"- [ ] {i}\n"
155
+ return list_string
156
+
157
+ class Footnote:
158
+ """
159
+ A helper class for managing Markdown footnotes.
160
+
161
+ Why it is needed:
162
+ Footnotes require two separate parts in Markdown: the annotation mark in the text `[^1]`,
163
+ and the definition at the bottom of the document `[^1]: The text`. This class links
164
+ the two together to prevent numbering mismatches.
165
+ """
166
+ def __init__(self, number: int, text: str):
167
+ """
168
+ Inputs:
169
+ number (int): The footnote reference number.
170
+ text (str): The expanded text definition of the footnote.
171
+ """
172
+ self.number = number
173
+ self.text = text
174
+
175
+ def define_string(self) -> str:
176
+ """
177
+ Outputs (str): The full footnote definition block to place at the bottom of the document.
178
+ """
179
+ return f"[^{self.number}]: {self.text}"
180
+
181
+ def adnotation(self) -> str:
182
+ """
183
+ Outputs (str): The short inline reference marker to place in the paragraph text.
184
+ """
185
+ return f"[^{self.number}]\n"
186
+
187
+ def table_to_dxf(headers: List[str], values: List[List[Any]], filename: str, cell_width: float = 50.0, cell_height: float = 10.0) -> str:
188
+ """
189
+ Exports a table (headers + values) to a 2D CAD DXF file.
190
+
191
+ Why it is needed:
192
+ Engineers and designers often need to embed tabulated data (e.g. Bill of Materials,
193
+ coordinate tables, parts lists) directly into AutoCAD drawings. This helper converts
194
+ a Python table directly into DXF geometry (lines and text) that can be opened in any CAD software.
195
+
196
+ Inputs:
197
+ headers (List[str]): List of column header names.
198
+ values (List[List[Any]]): A 2D list containing the row data.
199
+ filename (str): The output filename (e.g., 'table.dxf').
200
+ cell_width (float): The width of each cell in drawing units.
201
+ cell_height (float): The height of each cell in drawing units.
202
+
203
+ Outputs:
204
+ str: A string confirming the export, formatted as a Markdown link.
205
+ """
206
+ try:
207
+ import ezdxf
208
+ except ImportError:
209
+ return f"*(Error: ezdxf library is not installed. Cannot generate {filename})*"
210
+
211
+ doc = ezdxf.new('R2010')
212
+ msp = doc.modelspace()
213
+
214
+ # Construct full grid data
215
+ rows = [headers] + values
216
+ num_rows = len(rows)
217
+ num_cols = len(headers)
218
+
219
+ # Draw horizontal grid lines
220
+ for i in range(num_rows + 1):
221
+ y = -i * cell_height
222
+ msp.add_line((0, y), (num_cols * cell_width, y))
223
+
224
+ # Draw vertical grid lines
225
+ for j in range(num_cols + 1):
226
+ x = j * cell_width
227
+ msp.add_line((x, 0), (x, -num_rows * cell_height))
228
+
229
+ # Draw text centered in each cell
230
+ for i, row in enumerate(rows):
231
+ y = -i * cell_height - (cell_height / 2)
232
+ for j, cell_val in enumerate(row):
233
+ if j >= num_cols: continue # Safety bounds
234
+ x = j * cell_width + (cell_width / 2)
235
+ msp.add_text(
236
+ str(cell_val),
237
+ dxfattribs={
238
+ 'height': cell_height * 0.4,
239
+ }
240
+ ).set_placement((x, y), align=ezdxf.enums.TextEntityAlignment.MIDDLE_CENTER)
241
+
242
+ doc.saveas(filename)
243
+ return f"*[Successfully exported table to {filename}]({filename})*"
@@ -0,0 +1,126 @@
1
+ """
2
+ Engineering Unit Conversions for PyAct
3
+
4
+ A collection of helper functions to seamlessly convert values between
5
+ Metric (SI) and Imperial / US Customary systems.
6
+ """
7
+
8
+ # ==========================================
9
+ # LENGTH CONVERSIONS
10
+ # ==========================================
11
+
12
+ def mm_to_inch(mm: float) -> float:
13
+ """Converts millimeters [mm] to inches [in]"""
14
+ return mm / 25.4
15
+
16
+ def inch_to_mm(inch: float) -> float:
17
+ """Converts inches [in] to millimeters [mm]"""
18
+ return inch * 25.4
19
+
20
+ def m_to_ft(m: float) -> float:
21
+ """Converts meters [m] to feet [ft]"""
22
+ return m * 3.280839895
23
+
24
+ def ft_to_m(ft: float) -> float:
25
+ """Converts feet [ft] to meters [m]"""
26
+ return ft / 3.280839895
27
+
28
+ # ==========================================
29
+ # AREA & VOLUME CONVERSIONS
30
+ # ==========================================
31
+
32
+ def mm2_to_in2(mm2: float) -> float:
33
+ """Converts square millimeters [mm^2] to square inches [in^2]"""
34
+ return mm2 / 645.16
35
+
36
+ def in2_to_mm2(in2: float) -> float:
37
+ """Converts square inches [in^2] to square millimeters [mm^2]"""
38
+ return in2 * 645.16
39
+
40
+ def m3_to_ft3(m3: float) -> float:
41
+ """Converts cubic meters [m^3] to cubic feet [ft^3]"""
42
+ return m3 * 35.3146667
43
+
44
+ def ft3_to_m3(ft3: float) -> float:
45
+ """Converts cubic feet [ft^3] to cubic meters [m^3]"""
46
+ return ft3 / 35.3146667
47
+
48
+ # ==========================================
49
+ # MASS & FORCE CONVERSIONS
50
+ # ==========================================
51
+
52
+ def kg_to_lbs(kg: float) -> float:
53
+ """Converts kilograms [kg] to pounds-mass [lbs]"""
54
+ return kg * 2.20462262
55
+
56
+ def lbs_to_kg(lbs: float) -> float:
57
+ """Converts pounds-mass [lbs] to kilograms [kg]"""
58
+ return lbs / 2.20462262
59
+
60
+ def kn_to_kips(kn: float) -> float:
61
+ """Converts kilonewtons [kN] to kilopounds-force [kips]"""
62
+ return kn * 0.224808943
63
+
64
+ def kips_to_kn(kips: float) -> float:
65
+ """Converts kilopounds-force [kips] to kilonewtons [kN]"""
66
+ return kips / 0.224808943
67
+
68
+ def n_to_lbf(n: float) -> float:
69
+ """Converts newtons [N] to pounds-force [lbf]"""
70
+ return n * 0.224808943
71
+
72
+ def lbf_to_n(lbf: float) -> float:
73
+ """Converts pounds-force [lbf] to newtons [N]"""
74
+ return lbf / 0.224808943
75
+
76
+ # ==========================================
77
+ # PRESSURE & STRESS CONVERSIONS
78
+ # ==========================================
79
+
80
+ def mpa_to_psi(mpa: float) -> float:
81
+ """Converts Megapascals [MPa] (or N/mm^2) to pounds per square inch [psi]"""
82
+ return mpa * 145.037738
83
+
84
+ def psi_to_mpa(psi: float) -> float:
85
+ """Converts pounds per square inch [psi] to Megapascals [MPa]"""
86
+ return psi / 145.037738
87
+
88
+ def mpa_to_ksi(mpa: float) -> float:
89
+ """Converts Megapascals [MPa] to kilopounds per square inch [ksi]"""
90
+ return mpa * 0.145037738
91
+
92
+ def ksi_to_mpa(ksi: float) -> float:
93
+ """Converts kilopounds per square inch [ksi] to Megapascals [MPa]"""
94
+ return ksi / 0.145037738
95
+
96
+ # ==========================================
97
+ # MOMENT & TORQUE CONVERSIONS
98
+ # ==========================================
99
+
100
+ def knm_to_kipft(knm: float) -> float:
101
+ """Converts kilonewton-meters [kNm] to kilopound-feet [kip-ft]"""
102
+ return knm * 0.737562149
103
+
104
+ def kipft_to_knm(kipft: float) -> float:
105
+ """Converts kilopound-feet [kip-ft] to kilonewton-meters [kNm]"""
106
+ return kipft / 0.737562149
107
+
108
+ def knm_to_lbfft(knm: float) -> float:
109
+ """Converts kilonewton-meters [kNm] to pound-force feet [lbf-ft]"""
110
+ return knm * 737.562149
111
+
112
+ def lbfft_to_knm(lbfft: float) -> float:
113
+ """Converts pound-force feet [lbf-ft] to kilonewton-meters [kNm]"""
114
+ return lbfft / 737.562149
115
+
116
+ # ==========================================
117
+ # TEMPERATURE CONVERSIONS
118
+ # ==========================================
119
+
120
+ def c_to_f(celsius: float) -> float:
121
+ """Converts degrees Celsius [°C] to degrees Fahrenheit [°F]"""
122
+ return (celsius * 9/5) + 32
123
+
124
+ def f_to_c(fahrenheit: float) -> float:
125
+ """Converts degrees Fahrenheit [°F] to degrees Celsius [°C]"""
126
+ return (fahrenheit - 32) * 5/9
@@ -0,0 +1,43 @@
1
+ class Unit:
2
+ def __init__(self, value: float, unit: str):
3
+ self.value: float = value
4
+ self.unit: str = unit
5
+
6
+ def __repr__(self) -> str:
7
+ return f"{self.value} {self.unit}"
8
+
9
+ def _extract(self, other) -> float:
10
+ return float(other.value) if isinstance(other, Unit) else float(other)
11
+
12
+ def __add__(self, other):
13
+ return float(self.value) + self._extract(other)
14
+
15
+ def __radd__(self, other):
16
+ return self._extract(other) + float(self.value)
17
+
18
+ def __sub__(self, other):
19
+ return float(self.value) - self._extract(other)
20
+
21
+ def __rsub__(self, other):
22
+ return self._extract(other) - float(self.value)
23
+
24
+ def __mul__(self, other):
25
+ return float(self.value) * self._extract(other)
26
+
27
+ def __rmul__(self, other):
28
+ return self._extract(other) * float(self.value)
29
+
30
+ def __truediv__(self, other):
31
+ return float(self.value) / self._extract(other)
32
+
33
+ def __rtruediv__(self, other):
34
+ return self._extract(other) / float(self.value)
35
+
36
+ def __pow__(self, power):
37
+ return float(self.value) ** self._extract(power)
38
+
39
+ def __neg__(self):
40
+ return -float(self.value)
41
+
42
+ def __abs__(self):
43
+ return abs(float(self.value))
@@ -0,0 +1 @@
1
+ __version__ = "0.6.0"
@@ -0,0 +1,113 @@
1
+ import argparse
2
+ import sys
3
+ import os
4
+ from .core import map_content, process_content
5
+
6
+ def is_cache_valid(build_tree, cache_mtime):
7
+ file_path = os.path.join(build_tree.get("path", ""), build_tree.get("name", "") + ".pamd")
8
+ if os.path.exists(file_path) and os.path.getmtime(file_path) > cache_mtime:
9
+ return False
10
+ for t in build_tree.get("templates", []):
11
+ if not is_cache_valid(t, cache_mtime):
12
+ return False
13
+ return True
14
+
15
+ def main():
16
+ """
17
+ Main entry point for the PyAct Command Line Interface.
18
+
19
+ Why it is needed:
20
+ This acts as the bridge between the terminal and the PyAct compilation engine.
21
+ It parses command-line arguments, triggers the document mapping/compilation process,
22
+ and handles file I/O for outputting the compiled Markdown and DOCX files.
23
+
24
+ Inputs (via sys.argv):
25
+ input (str): Positional argument for the target `.pamd` file path.
26
+ --output (str): Optional path to save the generated `.md` file.
27
+ --docx (str): Optional path to save the generated `.docx` file.
28
+ --css (str): Optional path to a `.css` file for styling the DOCX output.
29
+ --lint (bool): Run the linter to check for unused files and variables.
30
+
31
+ Outputs:
32
+ Writes the generated `.md` and `.docx` files to the filesystem and prints
33
+ status messages to stdout/stderr.
34
+ """
35
+ parser = argparse.ArgumentParser(description="PyAct CLI - PAMD to Markdown compiler")
36
+ parser.add_argument("input", help="Path to the main .pamd file")
37
+ parser.add_argument("-o", "--output", help="Output file path (default prints to stdout)")
38
+ parser.add_argument("--docx", help="Also generate a DOCX file at this path")
39
+ parser.add_argument("--css", help="Optional CSS file path to style the DOCX")
40
+ parser.add_argument("--lint", action="store_true", help="Run the linter on the project to check for unused variables and files")
41
+
42
+ args = parser.parse_args()
43
+
44
+ input_path = os.path.abspath(args.input)
45
+ directory = os.path.dirname(input_path)
46
+ filename = os.path.basename(input_path)
47
+
48
+ if filename.endswith(".pamd"):
49
+ filename = filename[:-5]
50
+
51
+ if args.lint:
52
+ from .linter import run_linter
53
+ run_linter(filename, directory)
54
+ return
55
+
56
+ try:
57
+ build_tree = map_content(filename, directory)
58
+
59
+ cache_dir = os.path.join(directory, ".pamd-cache")
60
+ cache_file = os.path.join(cache_dir, f"{filename}.cache.md")
61
+ use_cache = False
62
+
63
+ if os.path.exists(cache_file):
64
+ cache_mtime = os.path.getmtime(cache_file)
65
+ if is_cache_valid(build_tree, cache_mtime):
66
+ use_cache = True
67
+
68
+ if use_cache:
69
+ print("pamd-cache: Using cached markdown...")
70
+ with open(cache_file, "r", encoding="utf-8") as f:
71
+ content = f.read()
72
+ else:
73
+ print("pamd-cache: Cache invalid or missing. Rebuilding...")
74
+ content = process_content(build_tree)
75
+ # Write cache
76
+ if not os.path.exists(cache_dir):
77
+ os.makedirs(cache_dir)
78
+ with open(cache_file, "w", encoding="utf-8") as f:
79
+ f.write(content)
80
+
81
+ if args.output:
82
+ with open(args.output, "w", encoding="utf-8") as f:
83
+ f.write(content)
84
+ print(f"Successfully compiled to {args.output}")
85
+ else:
86
+ if not args.docx:
87
+ print(content)
88
+
89
+ if args.docx:
90
+ from .mdTOword import style_parser, markdown_parser, docx_writer
91
+ styles = {}
92
+ if args.css:
93
+ styles = style_parser(args.css)
94
+ else:
95
+ # Try to look for style.css in the active directory
96
+ local_css = os.path.join(directory, "style.css")
97
+ if os.path.exists(local_css):
98
+ styles = style_parser(local_css)
99
+ else:
100
+ # Fallback to the default one packaged in pyact
101
+ default_css = os.path.join(os.path.dirname(__file__), "style.css")
102
+ styles = style_parser(default_css)
103
+
104
+ blocks = markdown_parser(content)
105
+ docx_writer(blocks, styles, args.docx)
106
+ print(f"Successfully compiled DOCX to {args.docx}")
107
+
108
+ except Exception as e:
109
+ print(f"Error: {e}", file=sys.stderr)
110
+ sys.exit(1)
111
+
112
+ if __name__ == "__main__":
113
+ main()
@@ -0,0 +1,198 @@
1
+ import re
2
+ import json
3
+
4
+ def get_directory(path: str) -> list:
5
+ """
6
+ Splits a full file path into its directory component and its filename component.
7
+
8
+ Why it is needed:
9
+ When resolving nested templates or generating output files, the engine needs to
10
+ know the base directory of the current `.pamd` file so it can correctly locate
11
+ sibling template files.
12
+
13
+ Inputs:
14
+ path (str): The full path to a file (e.g., './main/retro.pamd').
15
+
16
+ Outputs:
17
+ list: A two-element list `[directory_path, filename]`.
18
+ """
19
+ head, sep, tail = path.rpartition('/')
20
+ return [head + sep, tail]
21
+
22
+ def read_pamd_cells(file_path: str):
23
+ """
24
+ Reads a .pamd (JSON Notebook) file and extracts the Markdown and Python code.
25
+
26
+ Why it is needed:
27
+ `.pamd` files are stored exactly like Jupyter Notebooks (a list of cell objects).
28
+ This function parses the JSON structure and concatenates all 'code' cells into a
29
+ single executable Python script, and all 'markdown' cells into a single Markdown document.
30
+
31
+ Inputs:
32
+ file_path (str): Path to the `.pamd` file to read.
33
+
34
+ Outputs:
35
+ tuple (str, str): A tuple containing `(code_content, markdown_content)`.
36
+ """
37
+ with open(file_path, 'r', encoding='utf-8') as f:
38
+ data = json.load(f)
39
+
40
+ code_content = ""
41
+ markdown_content = ""
42
+
43
+ for cell in data.get("cells", []):
44
+ cell_type = cell.get("cell_type", "")
45
+ source = cell.get("source", "")
46
+ if isinstance(source, list):
47
+ source = "".join(source)
48
+
49
+ if cell_type == "code":
50
+ code_content += source + "\n"
51
+ elif cell_type == "markdown":
52
+ markdown_content += source + "\n"
53
+
54
+ return code_content, markdown_content
55
+
56
+ def get_imports(file: str, code_text: str):
57
+ """
58
+ Executes the Python code from a .pamd file and retrieves the context variables.
59
+
60
+ Why it is needed:
61
+ To inject dynamic data into the Markdown document, PyAct executes the user's Python
62
+ script. The user must define a `context()` function that returns a dictionary mapping
63
+ variable names (like 'title') to their computed values. This function safely executes
64
+ that code and extracts the resulting dictionary.
65
+
66
+ Inputs:
67
+ file (str): The name of the file being executed (for error reporting).
68
+ code_text (str): The raw Python code extracted from the `.pamd` file.
69
+
70
+ Outputs:
71
+ dict: The dictionary of variables returned by the user's `context()` function.
72
+ """
73
+ file_namespace = {"__file__": f"{file}.pamd"}
74
+ if not code_text.strip():
75
+ raise ValueError(f"The file {file}.pamd needs a 'context()' function but has no code cell.")
76
+
77
+ try:
78
+ exec(code_text, file_namespace)
79
+ except Exception as e:
80
+ raise RuntimeError(f"Error executing code in {file}.pamd: {e}")
81
+
82
+ if "context" not in file_namespace:
83
+ raise ValueError(f"The file {file}.pamd is missing the required 'context()' function.")
84
+
85
+ context_func = file_namespace["context"]
86
+
87
+ if not callable(context_func):
88
+ raise TypeError(f"In {file}.pamd, 'context' was found but it is not a function!")
89
+
90
+ result_dict = context_func()
91
+
92
+ if not isinstance(result_dict, dict):
93
+ raise TypeError(f"The 'context()' function in {file}.pamd must return a dictionary.")
94
+
95
+ return result_dict
96
+
97
+ def find_imports(text: str):
98
+ """
99
+ Finds all `<ctx>var_name</ctx>` tags in a markdown string.
100
+
101
+ Why it is needed:
102
+ Identifies which variables the Markdown document is requesting from the Python context.
103
+
104
+ Inputs:
105
+ text (str): The raw Markdown text.
106
+
107
+ Outputs:
108
+ list: A list of variable names extracted from the tags.
109
+ """
110
+ pattern = r"<ctx>(.*?)</ctx>"
111
+ return re.findall(pattern, text)
112
+
113
+
114
+ def find_templates(text: str):
115
+ """
116
+ Finds all `<tmp>file_name</tmp>` tags in a markdown string.
117
+
118
+ Why it is needed:
119
+ Identifies nested `.pamd` template files that need to be recursively compiled
120
+ and injected into the current document.
121
+
122
+ Inputs:
123
+ text (str): The raw Markdown text.
124
+
125
+ Outputs:
126
+ list: A list of template file names extracted from the tags.
127
+ """
128
+ pattern = r"<tmp>(.*?)</tmp>"
129
+ return re.findall(pattern, text)
130
+
131
+ def map_content(file: str, path: str):
132
+ """
133
+ Recursively maps the dependency tree of a `.pamd` file.
134
+
135
+ Why it is needed:
136
+ Before compiling, PyAct needs to build an Abstract Syntax Tree (AST) of the document,
137
+ mapping out all required context variables and any nested child templates that need
138
+ to be resolved. This allows for deep, nested project structures.
139
+
140
+ Inputs:
141
+ file (str): The target `.pamd` file name (without extension).
142
+ path (str): The directory where the file is located.
143
+
144
+ Outputs:
145
+ dict: A recursive "build tree" dictionary containing metadata, required imports,
146
+ and the build trees of all nested templates.
147
+ """
148
+ if path and not path.endswith('/'):
149
+ full_path = f"{path}/{file}"
150
+ elif path.endswith('/'):
151
+ full_path = f"{path}{file}"
152
+ else:
153
+ full_path = file
154
+
155
+ code_text, main_file = read_pamd_cells(f"{full_path}.pamd")
156
+ templates = find_templates(main_file)
157
+ imports = find_imports(main_file)
158
+ meta = get_directory(full_path)
159
+
160
+ ready_t = []
161
+ for i in templates:
162
+ ready_t.append(map_content(i, meta[0]))
163
+
164
+ return {
165
+ "tag_name": file,
166
+ "name": meta[1],
167
+ "path": meta[0],
168
+ "imports": imports,
169
+ "templates": ready_t
170
+ }
171
+
172
+ def process_content(build_tree: dict):
173
+ """
174
+ Compiles a mapped build tree into a final flat Markdown string.
175
+
176
+ Why it is needed:
177
+ This is the core renderer. It reads the `.pamd` files, executes their Python code
178
+ to fetch the context dictionary, replaces all `<ctx>` tags with dynamic data,
179
+ and recursively replaces all `<tmp>` tags with their fully compiled child documents.
180
+
181
+ Inputs:
182
+ build_tree (dict): The AST mapping generated by `map_content()`.
183
+
184
+ Outputs:
185
+ str: The final, fully compiled Markdown text string ready for export.
186
+ """
187
+ file_path_base = build_tree.get("path", "") + build_tree.get("name", "")
188
+ code_text, file_text = read_pamd_cells(file_path_base + ".pamd")
189
+
190
+ needed_imports = build_tree.get("imports", [])
191
+ if needed_imports:
192
+ imports = get_imports(file_path_base, code_text)
193
+ for i in needed_imports:
194
+ file_text = file_text.replace(f"<ctx>{i}</ctx>", str(imports.get(i, '')))
195
+
196
+ for i in build_tree.get("templates", []):
197
+ file_text = file_text.replace(f"<tmp>{i.get('tag_name')}</tmp>", process_content(i))
198
+ return file_text
@@ -2,7 +2,21 @@ import re
2
2
  import json
3
3
 
4
4
  def css_to_dict(css_string: str) -> dict:
5
- """Parses a CSS string into a Python dictionary."""
5
+ """
6
+ Parses a raw CSS string into a nested Python dictionary.
7
+
8
+ Why it is needed:
9
+ CSS is text-based and formatted with selectors, curly braces, and semicolons.
10
+ To programmatically apply these styles to python-docx elements, we need to
11
+ convert the syntax into a highly structured, queryable dictionary map.
12
+
13
+ Inputs:
14
+ css_string (str): The raw contents of a CSS file as a string.
15
+
16
+ Outputs:
17
+ dict: A dictionary where keys are CSS selectors (e.g., 'p', 'h1') and
18
+ values are dictionaries of style properties (e.g., {'color': '#000'}).
19
+ """
6
20
  # Remove CSS comments
7
21
  css_string = re.sub(r'/\*[\s\S]*?\*/', '', css_string)
8
22
 
@@ -36,8 +50,19 @@ def css_to_dict(css_string: str) -> dict:
36
50
 
37
51
  def parse_css_file(file_path: str, as_json_string: bool = False):
38
52
  """
39
- Reads a CSS file and converts it to a dictionary map.
40
- If as_json_string is True, returns a formatted JSON string instead.
53
+ Reads a CSS file from disk and parses it into a dictionary or JSON string.
54
+
55
+ Why it is needed:
56
+ Provides a clean interface for reading a physical file from the OS, extracting
57
+ its contents, and piping it into the core `css_to_dict` parser.
58
+
59
+ Inputs:
60
+ file_path (str): The path to the `.css` file on disk.
61
+ as_json_string (bool): If True, returns the output as a serialized JSON string
62
+ instead of a Python dictionary. Defaults to False.
63
+
64
+ Outputs:
65
+ dict or str: The parsed CSS dictionary, or a JSON-formatted string if requested.
41
66
  """
42
67
  with open(file_path, 'r', encoding='utf-8') as f:
43
68
  css_string = f.read()
@@ -0,0 +1,85 @@
1
+ import os
2
+ from .core import read_pamd_cells, get_imports, find_imports, map_content
3
+
4
+ def run_linter(main_filename: str, directory: str):
5
+ """
6
+ Runs the linter on a PyAct project.
7
+
8
+ Checks for:
9
+ 1. Unused .pamd files in the directory.
10
+ 2. Context keys exported but not used in the markdown.
11
+ 3. Context tags requested in the markdown but missing from the context dictionary.
12
+ """
13
+ print(f"--- PyAct Linter ---")
14
+ print(f"Analyzing project starting from: {main_filename}.pamd\n")
15
+
16
+ try:
17
+ build_tree = map_content(main_filename, directory)
18
+ except Exception as e:
19
+ print(f"Error mapping project: {e}")
20
+ return
21
+
22
+ used_files = set()
23
+
24
+ def collect_used_files(tree):
25
+ file_path = os.path.join(tree['path'], tree['name'])
26
+ used_files.add(os.path.abspath(file_path + ".pamd"))
27
+ for child in tree.get("templates", []):
28
+ collect_used_files(child)
29
+
30
+ collect_used_files(build_tree)
31
+
32
+ # Check for unused .pamd files in the base directory
33
+ all_pamd_files = set()
34
+ for root, _, files in os.walk(directory):
35
+ for f in files:
36
+ if f.endswith(".pamd"):
37
+ all_pamd_files.add(os.path.abspath(os.path.join(root, f)))
38
+
39
+ unused_files = all_pamd_files - used_files
40
+ if unused_files:
41
+ print(f"[WARNING] Unused .pamd files found in project directory:")
42
+ for uf in unused_files:
43
+ print(f" - {os.path.relpath(uf, directory)}")
44
+ else:
45
+ print("[OK] No unused .pamd files found.")
46
+
47
+ print("\n--- Context Check ---")
48
+
49
+ # Check context in each used file
50
+ for used_file in used_files:
51
+ rel_path = os.path.relpath(used_file, directory)
52
+ try:
53
+ code_text, md_text = read_pamd_cells(used_file)
54
+ except Exception:
55
+ continue
56
+
57
+ try:
58
+ # Need to pass base name for error reporting in core.py
59
+ base_name = os.path.splitext(os.path.basename(used_file))[0]
60
+ context_dict = get_imports(base_name, code_text)
61
+ exported_keys = set(context_dict.keys())
62
+ except Exception:
63
+ # If a file has no code cell or no context(), it returns an empty dict effectively,
64
+ # or throws ValueError in get_imports. Let's just treat as empty if error.
65
+ exported_keys = set()
66
+
67
+ requested_keys = set(find_imports(md_text))
68
+
69
+ unused_exports = exported_keys - requested_keys
70
+ missing_exports = requested_keys - exported_keys
71
+
72
+ if unused_exports or missing_exports:
73
+ print(f"\nFile: {rel_path}")
74
+ if unused_exports:
75
+ print(f" [WARNING] Unused context exports (defined in python, not used in markdown):")
76
+ for k in unused_exports:
77
+ print(f" - {k}")
78
+ if missing_exports:
79
+ print(f" [ERROR] Missing context exports (used in markdown, missing in python):")
80
+ for k in missing_exports:
81
+ print(f" - {k}")
82
+ else:
83
+ pass
84
+
85
+ print("\nLinting complete!")
@@ -7,9 +7,22 @@ from docx.oxml.ns import nsdecls
7
7
  import math2docx
8
8
  from . import css2json
9
9
 
10
- def style_parser(css_path):
10
+ def style_parser(css_path: str) -> dict:
11
11
  """
12
- Reads a CSS file and converts it into a style dictionary using css2json.
12
+ Parses a CSS file and converts it into a Python dictionary containing style maps.
13
+
14
+ Why it is needed:
15
+ To allow users to style their exported Word Documents using standard CSS syntax,
16
+ we must first translate that CSS into a structured Python dictionary that the
17
+ DOCX generation engine can query.
18
+
19
+ Inputs:
20
+ css_path (str): The absolute or relative file path to the target .css file.
21
+
22
+ Outputs:
23
+ dict: A nested dictionary where keys are CSS selectors (e.g., 'h1', 'p')
24
+ and values are dictionaries of property-value pairs (e.g., {'color': '#000', 'font-size': '12pt'}).
25
+ Returns an empty dictionary {} if parsing fails.
13
26
  """
14
27
  try:
15
28
  return css2json.parse_css_file(css_path)
@@ -17,10 +30,26 @@ def style_parser(css_path):
17
30
  print(f"Warning: Could not parse CSS ({e})")
18
31
  return {}
19
32
 
20
- def markdown_parser(md_text):
33
+ def markdown_parser(md_text: str) -> list:
21
34
  """
22
- Parses Markdown text into a list of structured blocks.
23
- Supported blocks: heading, paragraph, list, table, math
35
+ Parses raw Markdown text into a structured list of block tuples.
36
+
37
+ Why it is needed:
38
+ Because python-docx generates documents block-by-block (paragraphs, tables, headings),
39
+ we need to break down the raw string of Markdown text into distinct, classified chunks
40
+ that our DOCX writer can loop through and translate into native Word elements.
41
+
42
+ Inputs:
43
+ md_text (str): The complete raw Markdown text string.
44
+
45
+ Outputs:
46
+ list: A list of tuples where each tuple represents a block element.
47
+ Examples:
48
+ - ('heading', 2, 'Title Text')
49
+ - ('paragraph', 'Some plain text')
50
+ - ('list', 'List item text', 'ordered')
51
+ - ('math', 'x = 2')
52
+ - ('table', ['|Header|', '|---|', '|Data|'])
24
53
  """
25
54
  blocks = []
26
55
  lines = md_text.split('\n')
@@ -355,6 +384,19 @@ def _process_inline(paragraph, text, styles, tag):
355
384
  def docx_writer(blocks: list, styles: dict, output_file: str):
356
385
  """
357
386
  Translates parsed markdown blocks into a Word document and applies CSS styling.
387
+
388
+ Why it is needed:
389
+ This is the core generation engine. It takes the abstract syntax structure generated
390
+ by `markdown_parser()` and the style rules from `style_parser()`, loops through them,
391
+ and issues the corresponding `python-docx` commands to physically build the `.docx` file.
392
+
393
+ Inputs:
394
+ blocks (list): The list of parsed Markdown block tuples (from `markdown_parser`).
395
+ styles (dict): The nested dictionary of CSS rules (from `style_parser`).
396
+ output_file (str): The absolute or relative file path where the resulting .docx should be saved.
397
+
398
+ Outputs:
399
+ None: This function does not return a value, but it writes a binary .docx file to disk.
358
400
  """
359
401
  doc = Document()
360
402
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyact-cli
3
- Version: 0.4.0
3
+ Version: 0.6.0
4
4
  Summary: A CLI tool to compile .pamd files to Markdown.
5
5
  Home-page: https://github.com/Abstergo2003/PyAct
6
6
  Author: Abstergo2003
@@ -10,11 +10,14 @@ Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
11
  Requires-Python: >=3.8
12
12
  Description-Content-Type: text/markdown
13
+ Requires-Dist: python-docx
14
+ Requires-Dist: ezdxf
13
15
  Dynamic: author
14
16
  Dynamic: classifier
15
17
  Dynamic: description
16
18
  Dynamic: description-content-type
17
19
  Dynamic: home-page
20
+ Dynamic: requires-dist
18
21
  Dynamic: requires-python
19
22
  Dynamic: summary
20
23
 
@@ -65,10 +68,17 @@ You can also include other pamd files as templates!
65
68
  <tmp>path/to/another_file</tmp>
66
69
  ```
67
70
 
68
- ### 3. Compile!
71
+ ### 3. Compile to Markdown
69
72
  Run the compiler from your terminal to generate your final Markdown document:
70
73
  ```bash
71
74
  pyact path/to/your_file.pamd -o output.md
72
75
  ```
73
76
 
74
- That's it! You now have a standard Markdown file ready to be shared, published, or converted to PDF.
77
+ ### 4. Compile to Word (DOCX)
78
+ You can also compile straight to a native Microsoft Word Document, complete with CSS styling!
79
+ ```bash
80
+ pyact path/to/your_file.pamd -o output.md --docx output.docx
81
+ ```
82
+ *Note: If you have a `style.css` file in the same directory, PyAct will automatically load it to style your Word Document. You can also specify one manually using the `--css path/to/style.css` flag!*
83
+
84
+ That's it! You now have documents ready to be shared, published, or distributed.
@@ -1,14 +1,18 @@
1
1
  README.md
2
2
  setup.py
3
3
  pamd_helpers/__init__.py
4
+ pamd_helpers/conversions.py
5
+ pamd_helpers/unit.py
4
6
  pyact/__init__.py
5
7
  pyact/cli.py
6
8
  pyact/core.py
7
9
  pyact/css2json.py
10
+ pyact/linter.py
8
11
  pyact/mdTOword.py
9
12
  pyact/py2tex.py
10
13
  pyact_cli.egg-info/PKG-INFO
11
14
  pyact_cli.egg-info/SOURCES.txt
12
15
  pyact_cli.egg-info/dependency_links.txt
13
16
  pyact_cli.egg-info/entry_points.txt
17
+ pyact_cli.egg-info/requires.txt
14
18
  pyact_cli.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ python-docx
2
+ ezdxf
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="pyact-cli",
8
- version="0.4.0",
8
+ version="0.6.0",
9
9
  author="Abstergo2003",
10
10
  author_email="",
11
11
  description="A CLI tool to compile .pamd files to Markdown.",
@@ -19,6 +19,10 @@ setup(
19
19
  "Operating System :: OS Independent",
20
20
  ],
21
21
  python_requires=">=3.8",
22
+ install_requires=[
23
+ "python-docx",
24
+ "ezdxf"
25
+ ],
22
26
  entry_points={
23
27
  "console_scripts": [
24
28
  "pyact=pyact.cli:main",
@@ -1,76 +0,0 @@
1
- import inspect
2
- from pyact.py2tex import py2tex
3
-
4
- def equation(func: function, values: list) -> str:
5
- source = inspect.getsource(func)
6
- value = func(*values)
7
- name, sep, tail = source.rpartition("=")
8
- head, sep, equ = tail.rpartition(":")
9
- return f"$$ {name} = {py2tex(equ.replace("\n", ""))} = {value} $$"
10
-
11
-
12
- def image(link: str, caption: str) -> str:
13
- image_link = f"![{caption}]({link})"
14
- caption = f"<span style='text-align: center; display: block; font-style: italic;'>{caption}</span>"
15
- return f"{image_link} \n {caption}"
16
-
17
- def table(headers: list, values: list, caption: str, add_no: bool = False, starting_number: int = 1) -> str:
18
- if add_no:
19
- headers_string = f"|No.|"
20
- breaker_line = "|---|"
21
- else:
22
- headers_string = f"|"
23
- breaker_line = "|"
24
-
25
-
26
- for i in headers:
27
- headers_string += f"{i}|"
28
- breaker_line += "-"*len(i)+"|"
29
-
30
- table_content = ""
31
-
32
- for i, row in enumerate(values):
33
- if add_no:
34
- table_content += f"|{i+starting_number}|"
35
- else:
36
- table_content += "|"
37
-
38
- for value in row:
39
- table_content += f"{value}|"
40
- table_content += "\n"
41
-
42
- caption = f"<span style='text-align: center; display: block; font-style: italic;'>{caption}</span>"
43
- return f"{caption} \n {headers_string}\n{breaker_line}\n{table_content}"
44
-
45
-
46
- def hyperlink(caption: str, link: str) -> str:
47
- return f"[{caption}]({link})"
48
-
49
- def unordered_list(items: list) -> str:
50
- list_string = ""
51
- for i in items:
52
- list_string += f"* {i}\n"
53
- return list_string
54
-
55
- def ordered_list(items: list) -> str:
56
- list_string = ""
57
- for i, item in enumerate(items):
58
- list_string += f"{i+1}. {item}\n"
59
- return list_string
60
-
61
- def checklist(items: list) -> str:
62
- list_string = ""
63
- for i in items:
64
- list_string += f"- [ ] {i}\n"
65
- return list_string
66
-
67
- class Footnote:
68
- def __init__(self, number, text):
69
- self.number = number
70
- self.text = text
71
-
72
- def define_string(self) -> str:
73
- return f"[^{self.number}]: {self.text}"
74
-
75
- def adnotation(self) -> str:
76
- return f"[^{self.number}]\n"
@@ -1 +0,0 @@
1
- __version__ = "0.4.0"
@@ -1,57 +0,0 @@
1
- import argparse
2
- import sys
3
- import os
4
- from .core import map_content, process_content
5
-
6
- def main():
7
- parser = argparse.ArgumentParser(description="PyAct CLI - PAMD to Markdown compiler")
8
- parser.add_argument("input", help="Path to the main .pamd file")
9
- parser.add_argument("-o", "--output", help="Output file path (default prints to stdout)")
10
- parser.add_argument("--docx", help="Also generate a DOCX file at this path")
11
- parser.add_argument("--css", help="Optional CSS file path to style the DOCX")
12
-
13
- args = parser.parse_args()
14
-
15
- input_path = os.path.abspath(args.input)
16
- directory = os.path.dirname(input_path)
17
- filename = os.path.basename(input_path)
18
-
19
- if filename.endswith(".pamd"):
20
- filename = filename[:-5]
21
-
22
- try:
23
- build_tree = map_content(filename, directory)
24
- content = process_content(build_tree)
25
-
26
- if args.output:
27
- with open(args.output, "w", encoding="utf-8") as f:
28
- f.write(content)
29
- print(f"Successfully compiled to {args.output}")
30
- else:
31
- print(content)
32
-
33
- if args.docx:
34
- from .mdTOword import style_parser, markdown_parser, docx_writer
35
- styles = {}
36
- if args.css:
37
- styles = style_parser(args.css)
38
- else:
39
- # Try to look for style.css in the active directory
40
- local_css = os.path.join(directory, "style.css")
41
- if os.path.exists(local_css):
42
- styles = style_parser(local_css)
43
- else:
44
- # Fallback to the default one packaged in pyact
45
- default_css = os.path.join(os.path.dirname(__file__), "style.css")
46
- styles = style_parser(default_css)
47
-
48
- blocks = markdown_parser(content)
49
- docx_writer(blocks, styles, args.docx)
50
- print(f"Successfully compiled DOCX to {args.docx}")
51
-
52
- except Exception as e:
53
- print(f"Error: {e}", file=sys.stderr)
54
- sys.exit(1)
55
-
56
- if __name__ == "__main__":
57
- main()
@@ -1,103 +0,0 @@
1
- import re
2
- import json
3
-
4
- def get_directory(path: str) -> list:
5
- # Splits path into: ('./main', '/', 'retro.pamd')
6
- head, sep, tail = path.rpartition('/')
7
- # Recombine the first part and the slash
8
- return [head + sep, tail]
9
-
10
- def read_pamd_cells(file_path: str):
11
- with open(file_path, 'r', encoding='utf-8') as f:
12
- data = json.load(f)
13
-
14
- code_content = ""
15
- markdown_content = ""
16
-
17
- for cell in data.get("cells", []):
18
- cell_type = cell.get("cell_type", "")
19
- source = cell.get("source", "")
20
- if isinstance(source, list):
21
- source = "".join(source)
22
-
23
- if cell_type == "code":
24
- code_content += source + "\n"
25
- elif cell_type == "markdown":
26
- markdown_content += source + "\n"
27
-
28
- return code_content, markdown_content
29
-
30
- def get_imports(file: str, code_text: str):
31
- file_namespace = {"__file__": f"{file}.pamd"}
32
- if not code_text.strip():
33
- raise ValueError(f"The file {file}.pamd needs a 'context()' function but has no code cell.")
34
-
35
- try:
36
- exec(code_text, file_namespace)
37
- except Exception as e:
38
- raise RuntimeError(f"Error executing code in {file}.pamd: {e}")
39
-
40
- if "context" not in file_namespace:
41
- raise ValueError(f"The file {file}.pamd is missing the required 'context()' function.")
42
-
43
- context_func = file_namespace["context"]
44
-
45
- if not callable(context_func):
46
- raise TypeError(f"In {file}.pamd, 'context' was found but it is not a function!")
47
-
48
- result_dict = context_func()
49
-
50
- if not isinstance(result_dict, dict):
51
- raise TypeError(f"The 'context()' function in {file}.pamd must return a dictionary.")
52
-
53
- return result_dict
54
-
55
- def find_imports(text: str):
56
- # <ctx></ctx>
57
- pattern = r"<ctx>(.*?)</ctx>"
58
- return re.findall(pattern, text)
59
-
60
-
61
- def find_templates(text: str):
62
- # <tmp></tmp>
63
- pattern = r"<tmp>(.*?)</tmp>"
64
- return re.findall(pattern, text)
65
-
66
- def map_content(file: str, path: str):
67
- if path and not path.endswith('/'):
68
- full_path = f"{path}/{file}"
69
- elif path.endswith('/'):
70
- full_path = f"{path}{file}"
71
- else:
72
- full_path = file
73
-
74
- code_text, main_file = read_pamd_cells(f"{full_path}.pamd")
75
- templates = find_templates(main_file)
76
- imports = find_imports(main_file)
77
- meta = get_directory(full_path)
78
-
79
- ready_t = []
80
- for i in templates:
81
- ready_t.append(map_content(i, meta[0]))
82
-
83
- return {
84
- "tag_name": file,
85
- "name": meta[1],
86
- "path": meta[0],
87
- "imports": imports,
88
- "templates": ready_t
89
- }
90
-
91
- def process_content(build_tree: dict):
92
- file_path_base = build_tree.get("path", "") + build_tree.get("name", "")
93
- code_text, file_text = read_pamd_cells(file_path_base + ".pamd")
94
-
95
- needed_imports = build_tree.get("imports", [])
96
- if needed_imports:
97
- imports = get_imports(file_path_base, code_text)
98
- for i in needed_imports:
99
- file_text = file_text.replace(f"<ctx>{i}</ctx>", str(imports.get(i, '')))
100
-
101
- for i in build_tree.get("templates", []):
102
- file_text = file_text.replace(f"<tmp>{i.get('tag_name')}</tmp>", process_content(i))
103
- return file_text
File without changes
File without changes