pyact-cli 0.5.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.5.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
 
@@ -182,4 +182,62 @@ class Footnote:
182
182
  """
183
183
  Outputs (str): The short inline reference marker to place in the paragraph text.
184
184
  """
185
- return f"[^{self.number}]\n"
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"
@@ -1,75 +1,113 @@
1
- import argparse
2
- import sys
3
- import os
4
- from .core import map_content, process_content
5
-
6
- def main():
7
- """
8
- Main entry point for the PyAct Command Line Interface.
9
-
10
- Why it is needed:
11
- This acts as the bridge between the terminal and the PyAct compilation engine.
12
- It parses command-line arguments, triggers the document mapping/compilation process,
13
- and handles file I/O for outputting the compiled Markdown and DOCX files.
14
-
15
- Inputs (via sys.argv):
16
- input (str): Positional argument for the target `.pamd` file path.
17
- --output (str): Optional path to save the generated `.md` file.
18
- --docx (str): Optional path to save the generated `.docx` file.
19
- --css (str): Optional path to a `.css` file for styling the DOCX output.
20
-
21
- Outputs:
22
- Writes the generated `.md` and `.docx` files to the filesystem and prints
23
- status messages to stdout/stderr.
24
- """
25
- parser = argparse.ArgumentParser(description="PyAct CLI - PAMD to Markdown compiler")
26
- parser.add_argument("input", help="Path to the main .pamd file")
27
- parser.add_argument("-o", "--output", help="Output file path (default prints to stdout)")
28
- parser.add_argument("--docx", help="Also generate a DOCX file at this path")
29
- parser.add_argument("--css", help="Optional CSS file path to style the DOCX")
30
-
31
- args = parser.parse_args()
32
-
33
- input_path = os.path.abspath(args.input)
34
- directory = os.path.dirname(input_path)
35
- filename = os.path.basename(input_path)
36
-
37
- if filename.endswith(".pamd"):
38
- filename = filename[:-5]
39
-
40
- try:
41
- build_tree = map_content(filename, directory)
42
- content = process_content(build_tree)
43
-
44
- if args.output:
45
- with open(args.output, "w", encoding="utf-8") as f:
46
- f.write(content)
47
- print(f"Successfully compiled to {args.output}")
48
- else:
49
- print(content)
50
-
51
- if args.docx:
52
- from .mdTOword import style_parser, markdown_parser, docx_writer
53
- styles = {}
54
- if args.css:
55
- styles = style_parser(args.css)
56
- else:
57
- # Try to look for style.css in the active directory
58
- local_css = os.path.join(directory, "style.css")
59
- if os.path.exists(local_css):
60
- styles = style_parser(local_css)
61
- else:
62
- # Fallback to the default one packaged in pyact
63
- default_css = os.path.join(os.path.dirname(__file__), "style.css")
64
- styles = style_parser(default_css)
65
-
66
- blocks = markdown_parser(content)
67
- docx_writer(blocks, styles, args.docx)
68
- print(f"Successfully compiled DOCX to {args.docx}")
69
-
70
- except Exception as e:
71
- print(f"Error: {e}", file=sys.stderr)
72
- sys.exit(1)
73
-
74
- if __name__ == "__main__":
75
- main()
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,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!")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyact-cli
3
- Version: 0.5.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
 
@@ -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.5.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 +0,0 @@
1
- __version__ = "0.5.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes