pyact-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyact/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
pyact/cli.py ADDED
@@ -0,0 +1,35 @@
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
+
11
+ args = parser.parse_args()
12
+
13
+ input_path = os.path.abspath(args.input)
14
+ directory = os.path.dirname(input_path)
15
+ filename = os.path.basename(input_path)
16
+
17
+ if filename.endswith(".pamd"):
18
+ filename = filename[:-5]
19
+
20
+ try:
21
+ build_tree = map_content(filename, directory)
22
+ content = process_content(build_tree)
23
+
24
+ if args.output:
25
+ with open(args.output, "w", encoding="utf-8") as f:
26
+ f.write(content)
27
+ print(f"Successfully compiled to {args.output}")
28
+ else:
29
+ print(content)
30
+ except Exception as e:
31
+ print(f"Error: {e}", file=sys.stderr)
32
+ sys.exit(1)
33
+
34
+ if __name__ == "__main__":
35
+ main()
pyact/core.py ADDED
@@ -0,0 +1,103 @@
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
pyact/helpers.py ADDED
@@ -0,0 +1,76 @@
1
+ import inspect
2
+ from .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"
pyact/py2tex.py ADDED
@@ -0,0 +1,155 @@
1
+ import ast
2
+
3
+ class LatexVisitor(ast.NodeVisitor):
4
+
5
+ def prec(self, n):
6
+ return getattr(self, 'prec_'+n.__class__.__name__, getattr(self, 'generic_prec'))(n)
7
+
8
+ def visit_Call(self, n):
9
+ func = self.visit(n.func)
10
+ args = ', '.join(map(self.visit, n.args))
11
+ if func == 'sqrt':
12
+ return r'\sqrt{%s}' % args
13
+ else:
14
+ return r'\operatorname{%s}\left(%s\right)' % (func, args)
15
+
16
+ def prec_Call(self, n):
17
+ return 1000
18
+
19
+ def visit_Name(self, n):
20
+ return n.id
21
+
22
+ def prec_Name(self, n):
23
+ return 1000
24
+
25
+ def visit_UnaryOp(self, n):
26
+ if self.prec(n.op) > self.prec(n.operand):
27
+ return r'%s \left(%s\right)' % (self.visit(n.op), self.visit(n.operand))
28
+ else:
29
+ return r'%s %s' % (self.visit(n.op), self.visit(n.operand))
30
+
31
+ def prec_UnaryOp(self, n):
32
+ return self.prec(n.op)
33
+
34
+ def visit_BinOp(self, n):
35
+ if self.prec(n.op) > self.prec(n.left):
36
+ left = r'\left(%s\right)' % self.visit(n.left)
37
+ else:
38
+ left = self.visit(n.left)
39
+ if self.prec(n.op) > self.prec(n.right):
40
+ right = r'\left(%s\right)' % self.visit(n.right)
41
+ else:
42
+ right = self.visit(n.right)
43
+ if isinstance(n.op, ast.Div):
44
+ return r'\frac{%s}{%s}' % (self.visit(n.left), self.visit(n.right))
45
+ elif isinstance(n.op, ast.FloorDiv):
46
+ return r'\left\lfloor\frac{%s}{%s}\right\rfloor' % (self.visit(n.left), self.visit(n.right))
47
+ elif isinstance(n.op, ast.Pow):
48
+ return r'%s^{%s}' % (left, self.visit(n.right))
49
+ else:
50
+ return r'%s %s %s' % (left, self.visit(n.op), right)
51
+
52
+ def prec_BinOp(self, n):
53
+ return self.prec(n.op)
54
+
55
+ def visit_Sub(self, n):
56
+ return '-'
57
+
58
+ def prec_Sub(self, n):
59
+ return 300
60
+
61
+ def visit_Add(self, n):
62
+ return '+'
63
+
64
+ def prec_Add(self, n):
65
+ return 300
66
+
67
+ def visit_Mult(self, n):
68
+ return r'\;'
69
+
70
+ def prec_Mult(self, n):
71
+ return 400
72
+
73
+ def visit_Mod(self, n):
74
+ return r'\bmod'
75
+
76
+ def prec_Mod(self, n):
77
+ return 500
78
+
79
+ def prec_Pow(self, n):
80
+ return 700
81
+
82
+ def prec_Div(self, n):
83
+ return 400
84
+
85
+ def prec_FloorDiv(self, n):
86
+ return 400
87
+
88
+ def visit_LShift(self, n):
89
+ return r'\operatorname{shiftLeft}'
90
+
91
+ def visit_RShift(self, n):
92
+ return r'\operatorname{shiftRight}'
93
+
94
+ def visit_BitOr(self, n):
95
+ return r'\operatorname{or}'
96
+
97
+ def visit_BitXor(self, n):
98
+ return r'\operatorname{xor}'
99
+
100
+ def visit_BitAnd(self, n):
101
+ return r'\operatorname{and}'
102
+
103
+ def visit_Invert(self, n):
104
+ return r'\operatorname{invert}'
105
+
106
+ def prec_Invert(self, n):
107
+ return 800
108
+
109
+ def visit_Not(self, n):
110
+ return r'\neg'
111
+
112
+ def prec_Not(self, n):
113
+ return 800
114
+
115
+ def visit_UAdd(self, n):
116
+ return '+'
117
+
118
+ def prec_UAdd(self, n):
119
+ return 800
120
+
121
+ def visit_USub(self, n):
122
+ return '-'
123
+
124
+ def prec_USub(self, n):
125
+ return 800
126
+
127
+ def visit_Num(self, n):
128
+ return str(n.n)
129
+
130
+ def prec_Num(self, n):
131
+ return 1000
132
+
133
+ def visit_Constant(self, n):
134
+ return str(n.value)
135
+
136
+ def prec_Constant(self, n):
137
+ return 1000
138
+
139
+ def generic_visit(self, n):
140
+ if isinstance(n, ast.AST):
141
+ return r'' % (n.__class__.__name__, ', '.join(map(self.visit, [getattr(n, f) for f in n._fields])))
142
+ else:
143
+ return str(n)
144
+
145
+ def generic_prec(self, n):
146
+ return 0
147
+
148
+ def py2tex(expr):
149
+ pt = ast.parse(expr.strip())
150
+ return LatexVisitor().visit(pt.body[0].value)
151
+
152
+ if __name__ == "__main__":
153
+ print(py2tex("x**2"))
154
+ print(py2tex("x/y"))
155
+ print(py2tex("sqrt(x) + a*b"))
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyact-cli
3
+ Version: 0.1.0
4
+ Summary: A CLI tool to compile .pamd files to Markdown.
5
+ Home-page: https://github.com/yourusername/pyact
6
+ Author: Your Name
7
+ Author-email: your.email@example.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ Dynamic: author
14
+ Dynamic: author-email
15
+ Dynamic: classifier
16
+ Dynamic: description
17
+ Dynamic: description-content-type
18
+ Dynamic: home-page
19
+ Dynamic: requires-python
20
+ Dynamic: summary
21
+
22
+ # PyAct CLI
23
+
24
+ PyAct CLI is a tool designed to process `.pamd` files, which bundle Python logic and Markdown content together, similar to a Jupyter Notebook. This compiler resolves variables and dependencies to generate standard Markdown output.
25
+
26
+ ## Installation
27
+
28
+ You can install it directly from source (or after uploading to PyPI):
29
+
30
+ ```bash
31
+ pip install pyact-cli
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ Compile a `.pamd` file and output to the terminal:
37
+ ```bash
38
+ pyact path/to/your_file.pamd
39
+ ```
40
+
41
+ Compile and save the output to a new Markdown file:
42
+ ```bash
43
+ pyact path/to/your_file.pamd -o output.md
44
+ ```
@@ -0,0 +1,10 @@
1
+ pyact/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
+ pyact/cli.py,sha256=WmGvWjB7Hhm3aIKFG0oRdwYMM3i19dHf3Uz8kUqRSYQ,1079
3
+ pyact/core.py,sha256=CcUugSmc41KE5AZrP4fUSF8Q_QIIaGoaPo5J1Kzu0l0,3218
4
+ pyact/helpers.py,sha256=lSjiDkxVG_Odd_z1MmaGXzUi7twrynkE5kvVAokfdH0,2222
5
+ pyact/py2tex.py,sha256=VmrJ_OJjakGiAcEN2oiLvTuvSgoqtqJHPEicKKWzovw,3769
6
+ pyact_cli-0.1.0.dist-info/METADATA,sha256=HfW0YZjyyltAalJVH6fsmv5OPBgJwusFrgOeUttsfFk,1218
7
+ pyact_cli-0.1.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
8
+ pyact_cli-0.1.0.dist-info/entry_points.txt,sha256=hmmmLfom4oe4moDZ3NKZMviD5V4ABXI68CkphezrGZg,41
9
+ pyact_cli-0.1.0.dist-info/top_level.txt,sha256=DBvVl_zibkXqq9Ua5DkYyW18Tjsedff1nhWa3OoA04c,6
10
+ pyact_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (78.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pyact = pyact.cli:main
@@ -0,0 +1 @@
1
+ pyact