pyact-cli 0.7.3__tar.gz → 0.8.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.
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/PKG-INFO +21 -2
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/README.md +20 -1
- pyact_cli-0.8.0/pyact/__init__.py +1 -0
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/pyact/cli.py +19 -1
- pyact_cli-0.8.0/pyact/conf_parser.py +44 -0
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/pyact/mdTOword.py +290 -5
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/pyact_cli.egg-info/PKG-INFO +21 -2
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/pyact_cli.egg-info/SOURCES.txt +1 -0
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/setup.py +1 -1
- pyact_cli-0.7.3/pyact/__init__.py +0 -1
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/pamd_helpers/__init__.py +0 -0
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/pyact/core.py +0 -0
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/pyact/css2json.py +0 -0
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/pyact/linter.py +0 -0
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/pyact/py2tex.py +0 -0
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/pyact_cli.egg-info/dependency_links.txt +0 -0
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/pyact_cli.egg-info/entry_points.txt +0 -0
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/pyact_cli.egg-info/requires.txt +0 -0
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/pyact_cli.egg-info/top_level.txt +0 -0
- {pyact_cli-0.7.3 → pyact_cli-0.8.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyact-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.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
|
|
@@ -86,11 +86,30 @@ Run the compiler from your terminal to generate your final Markdown document:
|
|
|
86
86
|
pyact path/to/your_file.pamd -o output.md
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
###
|
|
89
|
+
### 5. Compile to Word (DOCX)
|
|
90
90
|
You can also compile straight to a native Microsoft Word Document, complete with CSS styling!
|
|
91
91
|
```bash
|
|
92
92
|
pyact path/to/your_file.pamd -o output.md --docx output.docx
|
|
93
93
|
```
|
|
94
94
|
*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!*
|
|
95
95
|
|
|
96
|
+
### 6. Configure Word Export via `pamd.conf`
|
|
97
|
+
Control document metadata, running headers, and footers with a `pamd.conf` file:
|
|
98
|
+
|
|
99
|
+
```ini
|
|
100
|
+
[Header]
|
|
101
|
+
left = My Project
|
|
102
|
+
right = 2026
|
|
103
|
+
|
|
104
|
+
[Footer]
|
|
105
|
+
left = Confidential
|
|
106
|
+
center = Page {page} of {pages}
|
|
107
|
+
|
|
108
|
+
[Metadata]
|
|
109
|
+
author = Jane Doe
|
|
110
|
+
title = Monthly Report
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
PyAct will automatically detect `pamd.conf` in your directory, inject dynamic page numbers (`{page}` / `{pages}`), configure tab stops, and populate document metadata!
|
|
114
|
+
|
|
96
115
|
That's it! You now have documents ready to be shared, published, or distributed.
|
|
@@ -63,11 +63,30 @@ Run the compiler from your terminal to generate your final Markdown document:
|
|
|
63
63
|
pyact path/to/your_file.pamd -o output.md
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
###
|
|
66
|
+
### 5. Compile to Word (DOCX)
|
|
67
67
|
You can also compile straight to a native Microsoft Word Document, complete with CSS styling!
|
|
68
68
|
```bash
|
|
69
69
|
pyact path/to/your_file.pamd -o output.md --docx output.docx
|
|
70
70
|
```
|
|
71
71
|
*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!*
|
|
72
72
|
|
|
73
|
+
### 6. Configure Word Export via `pamd.conf`
|
|
74
|
+
Control document metadata, running headers, and footers with a `pamd.conf` file:
|
|
75
|
+
|
|
76
|
+
```ini
|
|
77
|
+
[Header]
|
|
78
|
+
left = My Project
|
|
79
|
+
right = 2026
|
|
80
|
+
|
|
81
|
+
[Footer]
|
|
82
|
+
left = Confidential
|
|
83
|
+
center = Page {page} of {pages}
|
|
84
|
+
|
|
85
|
+
[Metadata]
|
|
86
|
+
author = Jane Doe
|
|
87
|
+
title = Monthly Report
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
PyAct will automatically detect `pamd.conf` in your directory, inject dynamic page numbers (`{page}` / `{pages}`), configure tab stops, and populate document metadata!
|
|
91
|
+
|
|
73
92
|
That's it! You now have documents ready to be shared, published, or distributed.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.8.0"
|
|
@@ -43,6 +43,7 @@ def main():
|
|
|
43
43
|
parser.add_argument("-o", "--output", help="Output file path (default prints to stdout)")
|
|
44
44
|
parser.add_argument("--docx", help="Also generate a DOCX file at this path")
|
|
45
45
|
parser.add_argument("--css", help="Optional CSS file path to style the DOCX")
|
|
46
|
+
parser.add_argument("--conf", help="Optional path to pamd.conf configuration file")
|
|
46
47
|
parser.add_argument("--lint", action="store_true", help="Run the linter on the project to check for unused variables and files")
|
|
47
48
|
|
|
48
49
|
args = parser.parse_args()
|
|
@@ -123,8 +124,25 @@ def main():
|
|
|
123
124
|
default_css = os.path.join(os.path.dirname(__file__), "style.css")
|
|
124
125
|
styles = style_parser(default_css)
|
|
125
126
|
|
|
127
|
+
# Load pamd.conf configuration
|
|
128
|
+
conf = {}
|
|
129
|
+
conf_path = None
|
|
130
|
+
if args.conf:
|
|
131
|
+
conf_path = os.path.abspath(args.conf)
|
|
132
|
+
else:
|
|
133
|
+
local_conf = os.path.join(directory, "pamd.conf")
|
|
134
|
+
cwd_conf = os.path.join(os.getcwd(), "pamd.conf")
|
|
135
|
+
if os.path.exists(local_conf):
|
|
136
|
+
conf_path = local_conf
|
|
137
|
+
elif os.path.exists(cwd_conf):
|
|
138
|
+
conf_path = cwd_conf
|
|
139
|
+
|
|
140
|
+
if conf_path and os.path.exists(conf_path):
|
|
141
|
+
from .conf_parser import conf_parser
|
|
142
|
+
conf = conf_parser(conf_path)
|
|
143
|
+
|
|
126
144
|
blocks = markdown_parser(content)
|
|
127
|
-
docx_writer(blocks, styles, args.docx)
|
|
145
|
+
docx_writer(blocks, styles, args.docx, config=conf)
|
|
128
146
|
print(f"Successfully compiled DOCX to {args.docx}")
|
|
129
147
|
|
|
130
148
|
except Exception as e:
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
def conf_parser(conf_path: str) -> dict:
|
|
4
|
+
"""
|
|
5
|
+
Parses a pamd.conf configuration file into a dictionary of sections and settings.
|
|
6
|
+
|
|
7
|
+
Why it is needed:
|
|
8
|
+
Allows PyAct projects to define global Word export settings such as Headers, Footers,
|
|
9
|
+
Page margins/orientations, and document Metadata without cluttering Markdown templates
|
|
10
|
+
or Python code cells.
|
|
11
|
+
|
|
12
|
+
Inputs:
|
|
13
|
+
conf_path (str): Path to the pamd.conf file.
|
|
14
|
+
|
|
15
|
+
Outputs:
|
|
16
|
+
dict: A nested dictionary of sections and their key-value options.
|
|
17
|
+
Example: {'Header': {'left': 'PyAct'}, 'Metadata': {'author': 'Radek'}}
|
|
18
|
+
"""
|
|
19
|
+
if not conf_path or not os.path.exists(conf_path):
|
|
20
|
+
return {}
|
|
21
|
+
|
|
22
|
+
parsed_dict = {}
|
|
23
|
+
current_header = "General"
|
|
24
|
+
|
|
25
|
+
with open(conf_path, 'r', encoding='utf-8') as f:
|
|
26
|
+
for line in f:
|
|
27
|
+
stripped = line.strip()
|
|
28
|
+
# Skip empty lines and comment lines starting with # or ;
|
|
29
|
+
if not stripped or stripped.startswith(('#', ';')):
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
if stripped.startswith('[') and stripped.endswith(']'):
|
|
33
|
+
current_header = stripped[1:-1].strip()
|
|
34
|
+
if current_header not in parsed_dict:
|
|
35
|
+
parsed_dict[current_header] = {}
|
|
36
|
+
elif '=' in stripped:
|
|
37
|
+
head, tail = stripped.split('=', 1)
|
|
38
|
+
key = head.strip()
|
|
39
|
+
val = tail.strip()
|
|
40
|
+
if current_header not in parsed_dict:
|
|
41
|
+
parsed_dict[current_header] = {}
|
|
42
|
+
parsed_dict[current_header][key] = val
|
|
43
|
+
|
|
44
|
+
return parsed_dict
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from docx import Document
|
|
3
|
-
from docx.shared import Pt, RGBColor
|
|
4
|
-
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
5
|
-
from docx.oxml import parse_xml
|
|
6
|
-
from docx.oxml.ns import nsdecls
|
|
3
|
+
from docx.shared import Pt, RGBColor, Inches, Cm, Mm
|
|
4
|
+
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_TAB_ALIGNMENT
|
|
5
|
+
from docx.oxml import parse_xml, OxmlElement
|
|
6
|
+
from docx.oxml.ns import nsdecls, qn
|
|
7
7
|
import math2docx
|
|
8
8
|
from . import css2json
|
|
9
9
|
|
|
10
|
+
|
|
10
11
|
def style_parser(css_path: str) -> dict:
|
|
11
12
|
"""
|
|
12
13
|
Parses a CSS file and converts it into a Python dictionary containing style maps.
|
|
@@ -425,7 +426,286 @@ def _restart_list_numbering(doc, p):
|
|
|
425
426
|
except Exception:
|
|
426
427
|
return None
|
|
427
428
|
|
|
428
|
-
def
|
|
429
|
+
def _add_xml_field(run, field_name: str):
|
|
430
|
+
"""Inserts a dynamic Word field (like PAGE or NUMPAGES) into a run."""
|
|
431
|
+
fldChar1 = OxmlElement('w:fldChar')
|
|
432
|
+
fldChar1.set(qn('w:fldCharType'), 'begin')
|
|
433
|
+
instrText = OxmlElement('w:instrText')
|
|
434
|
+
instrText.set(qn('xml:space'), 'preserve')
|
|
435
|
+
instrText.text = f" {field_name} "
|
|
436
|
+
fldChar2 = OxmlElement('w:fldChar')
|
|
437
|
+
fldChar2.set(qn('w:fldCharType'), 'separate')
|
|
438
|
+
t = OxmlElement('w:t')
|
|
439
|
+
t.text = "1"
|
|
440
|
+
fldChar3 = OxmlElement('w:fldChar')
|
|
441
|
+
fldChar3.set(qn('w:fldCharType'), 'end')
|
|
442
|
+
|
|
443
|
+
r = run._r
|
|
444
|
+
r.append(fldChar1)
|
|
445
|
+
r.append(instrText)
|
|
446
|
+
r.append(fldChar2)
|
|
447
|
+
r.append(t)
|
|
448
|
+
r.append(fldChar3)
|
|
449
|
+
|
|
450
|
+
def _add_styled_header_footer_text(paragraph, text: str, font_rules: dict):
|
|
451
|
+
"""
|
|
452
|
+
Appends text to a header/footer paragraph, replacing {page} and {pages}
|
|
453
|
+
tokens with native Word dynamic field elements and applying font styling.
|
|
454
|
+
"""
|
|
455
|
+
if not text:
|
|
456
|
+
return
|
|
457
|
+
|
|
458
|
+
pattern = r'(\{page\}|\{pages\}|\{page_number\}|\{total_pages\})'
|
|
459
|
+
tokens = re.split(pattern, text, flags=re.IGNORECASE)
|
|
460
|
+
|
|
461
|
+
for token in tokens:
|
|
462
|
+
if not token:
|
|
463
|
+
continue
|
|
464
|
+
tok_lower = token.lower()
|
|
465
|
+
if tok_lower in ('{page}', '{page_number}'):
|
|
466
|
+
run = paragraph.add_run()
|
|
467
|
+
_add_xml_field(run, 'PAGE')
|
|
468
|
+
if font_rules:
|
|
469
|
+
_apply_css_to_run(run, font_rules)
|
|
470
|
+
elif tok_lower in ('{pages}', '{total_pages}'):
|
|
471
|
+
run = paragraph.add_run()
|
|
472
|
+
_add_xml_field(run, 'NUMPAGES')
|
|
473
|
+
if font_rules:
|
|
474
|
+
_apply_css_to_run(run, font_rules)
|
|
475
|
+
else:
|
|
476
|
+
run = paragraph.add_run(token)
|
|
477
|
+
if font_rules:
|
|
478
|
+
_apply_css_to_run(run, font_rules)
|
|
479
|
+
|
|
480
|
+
def _configure_header_or_footer(hf_obj, conf_dict: dict, section):
|
|
481
|
+
"""
|
|
482
|
+
Configures a header or footer object (section.header or section.footer).
|
|
483
|
+
Supports:
|
|
484
|
+
- text / content (with align = left|center|right)
|
|
485
|
+
- left / center / right 3-zone layout
|
|
486
|
+
- page_number = true
|
|
487
|
+
- font styling (font_name, font_size, color, italic, bold)
|
|
488
|
+
"""
|
|
489
|
+
if not conf_dict:
|
|
490
|
+
return
|
|
491
|
+
|
|
492
|
+
p = hf_obj.paragraphs[0]
|
|
493
|
+
p.text = "" # Clear default empty paragraph
|
|
494
|
+
|
|
495
|
+
# Extract font styling options
|
|
496
|
+
font_rules = {}
|
|
497
|
+
if 'font_name' in conf_dict:
|
|
498
|
+
font_rules['font-family'] = conf_dict['font_name']
|
|
499
|
+
elif 'font' in conf_dict:
|
|
500
|
+
font_rules['font-family'] = conf_dict['font']
|
|
501
|
+
|
|
502
|
+
if 'font_size' in conf_dict:
|
|
503
|
+
sz = str(conf_dict['font_size']).strip()
|
|
504
|
+
if not sz.endswith('pt'): sz = f"{sz}pt"
|
|
505
|
+
font_rules['font-size'] = sz
|
|
506
|
+
elif 'size' in conf_dict:
|
|
507
|
+
sz = str(conf_dict['size']).strip()
|
|
508
|
+
if not sz.endswith('pt'): sz = f"{sz}pt"
|
|
509
|
+
font_rules['font-size'] = sz
|
|
510
|
+
|
|
511
|
+
if 'color' in conf_dict:
|
|
512
|
+
font_rules['color'] = conf_dict['color']
|
|
513
|
+
|
|
514
|
+
if 'bold' in conf_dict:
|
|
515
|
+
font_rules['font-weight'] = 'bold' if str(conf_dict['bold']).strip().lower() in ('true', 'yes', '1') else 'normal'
|
|
516
|
+
if 'italic' in conf_dict:
|
|
517
|
+
font_rules['font-style'] = 'italic' if str(conf_dict['italic']).strip().lower() in ('true', 'yes', '1') else 'normal'
|
|
518
|
+
|
|
519
|
+
left_val = conf_dict.get('left')
|
|
520
|
+
center_val = conf_dict.get('center')
|
|
521
|
+
right_val = conf_dict.get('right')
|
|
522
|
+
|
|
523
|
+
# Check if page_number is enabled
|
|
524
|
+
has_page_num = str(conf_dict.get('page_number', '')).strip().lower() in ('true', 'yes', '1') or \
|
|
525
|
+
str(conf_dict.get('page_numbers', '')).strip().lower() in ('true', 'yes', '1')
|
|
526
|
+
|
|
527
|
+
text_val = conf_dict.get('text') or conf_dict.get('content') or conf_dict.get('title')
|
|
528
|
+
|
|
529
|
+
if has_page_num and not (left_val or center_val or right_val or text_val):
|
|
530
|
+
center_val = "Page {page} of {pages}"
|
|
531
|
+
|
|
532
|
+
if left_val is not None or center_val is not None or right_val is not None:
|
|
533
|
+
# Multi-zone layout using tab stops
|
|
534
|
+
content_width = section.page_width - section.left_margin - section.right_margin
|
|
535
|
+
tab_center = int(content_width // 2)
|
|
536
|
+
tab_right = int(content_width)
|
|
537
|
+
|
|
538
|
+
p.paragraph_format.tab_stops.add_tab_stop(tab_center, WD_TAB_ALIGNMENT.CENTER)
|
|
539
|
+
p.paragraph_format.tab_stops.add_tab_stop(tab_right, WD_TAB_ALIGNMENT.RIGHT)
|
|
540
|
+
|
|
541
|
+
# Left zone
|
|
542
|
+
if left_val:
|
|
543
|
+
_add_styled_header_footer_text(p, left_val, font_rules)
|
|
544
|
+
|
|
545
|
+
# Center zone
|
|
546
|
+
if center_val:
|
|
547
|
+
p.add_run('\t')
|
|
548
|
+
_add_styled_header_footer_text(p, center_val, font_rules)
|
|
549
|
+
elif right_val:
|
|
550
|
+
p.add_run('\t')
|
|
551
|
+
|
|
552
|
+
# Right zone
|
|
553
|
+
if right_val:
|
|
554
|
+
p.add_run('\t')
|
|
555
|
+
_add_styled_header_footer_text(p, right_val, font_rules)
|
|
556
|
+
|
|
557
|
+
elif text_val:
|
|
558
|
+
align = conf_dict.get('align', 'left').lower()
|
|
559
|
+
if align == 'center':
|
|
560
|
+
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
561
|
+
elif align == 'right':
|
|
562
|
+
p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
|
563
|
+
else:
|
|
564
|
+
p.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
|
565
|
+
|
|
566
|
+
_add_styled_header_footer_text(p, text_val, font_rules)
|
|
567
|
+
|
|
568
|
+
def _apply_metadata(doc, meta_conf: dict):
|
|
569
|
+
if not meta_conf:
|
|
570
|
+
return
|
|
571
|
+
|
|
572
|
+
cp = doc.core_properties
|
|
573
|
+
meta_map = {
|
|
574
|
+
'author': 'author',
|
|
575
|
+
'autor': 'author',
|
|
576
|
+
'creator': 'author',
|
|
577
|
+
'title': 'title',
|
|
578
|
+
'tytul': 'title',
|
|
579
|
+
'subject': 'subject',
|
|
580
|
+
'temat': 'subject',
|
|
581
|
+
'keywords': 'keywords',
|
|
582
|
+
'slowa_kluczowe': 'keywords',
|
|
583
|
+
'tags': 'keywords',
|
|
584
|
+
'category': 'category',
|
|
585
|
+
'kategoria': 'category',
|
|
586
|
+
'comments': 'comments',
|
|
587
|
+
'komentarze': 'comments',
|
|
588
|
+
'description': 'comments',
|
|
589
|
+
'opis': 'comments',
|
|
590
|
+
'status': 'content_status',
|
|
591
|
+
'content_status': 'content_status',
|
|
592
|
+
'language': 'language',
|
|
593
|
+
'lang': 'language',
|
|
594
|
+
'jezyk': 'language',
|
|
595
|
+
'version': 'version',
|
|
596
|
+
'wersja': 'version',
|
|
597
|
+
'revision': 'revision',
|
|
598
|
+
'last_modified_by': 'last_modified_by',
|
|
599
|
+
'identifier': 'identifier',
|
|
600
|
+
'id': 'identifier',
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
for k, v in meta_conf.items():
|
|
604
|
+
norm_k = k.strip().lower()
|
|
605
|
+
attr = meta_map.get(norm_k, norm_k)
|
|
606
|
+
if hasattr(cp, attr):
|
|
607
|
+
try:
|
|
608
|
+
setattr(cp, attr, str(v))
|
|
609
|
+
except Exception:
|
|
610
|
+
pass
|
|
611
|
+
|
|
612
|
+
def _parse_length(val_str: str):
|
|
613
|
+
if not val_str:
|
|
614
|
+
return None
|
|
615
|
+
s = str(val_str).strip().lower()
|
|
616
|
+
try:
|
|
617
|
+
if s.endswith('in') or s.endswith('"'):
|
|
618
|
+
return Inches(float(s.rstrip('in"').strip()))
|
|
619
|
+
elif s.endswith('cm'):
|
|
620
|
+
return Cm(float(s[:-2].strip()))
|
|
621
|
+
elif s.endswith('mm'):
|
|
622
|
+
return Mm(float(s[:-2].strip()))
|
|
623
|
+
elif s.endswith('pt'):
|
|
624
|
+
return Pt(float(s[:-2].strip()))
|
|
625
|
+
else:
|
|
626
|
+
return Inches(float(s))
|
|
627
|
+
except ValueError:
|
|
628
|
+
return None
|
|
629
|
+
|
|
630
|
+
def _apply_page_settings(doc, page_conf: dict):
|
|
631
|
+
if not page_conf:
|
|
632
|
+
return
|
|
633
|
+
|
|
634
|
+
for s in doc.sections:
|
|
635
|
+
if 'orientation' in page_conf:
|
|
636
|
+
orient = page_conf['orientation'].lower()
|
|
637
|
+
from docx.enum.section import WD_ORIENT
|
|
638
|
+
if orient == 'landscape':
|
|
639
|
+
s.orientation = WD_ORIENT.LANDSCAPE
|
|
640
|
+
if s.page_width < s.page_height:
|
|
641
|
+
s.page_width, s.page_height = s.page_height, s.page_width
|
|
642
|
+
elif orient == 'portrait':
|
|
643
|
+
s.orientation = WD_ORIENT.PORTRAIT
|
|
644
|
+
if s.page_width > s.page_height:
|
|
645
|
+
s.page_width, s.page_height = s.page_height, s.page_width
|
|
646
|
+
|
|
647
|
+
if 'size' in page_conf or 'paper_size' in page_conf:
|
|
648
|
+
size_name = str(page_conf.get('size') or page_conf.get('paper_size')).lower()
|
|
649
|
+
if size_name == 'a4':
|
|
650
|
+
s.page_width = Mm(210)
|
|
651
|
+
s.page_height = Mm(297)
|
|
652
|
+
elif size_name == 'letter':
|
|
653
|
+
s.page_width = Inches(8.5)
|
|
654
|
+
s.page_height = Inches(11)
|
|
655
|
+
elif size_name == 'legal':
|
|
656
|
+
s.page_width = Inches(8.5)
|
|
657
|
+
s.page_height = Inches(14)
|
|
658
|
+
|
|
659
|
+
# Margins
|
|
660
|
+
if 'margin' in page_conf or 'margins' in page_conf:
|
|
661
|
+
m = _parse_length(page_conf.get('margin') or page_conf.get('margins'))
|
|
662
|
+
if m:
|
|
663
|
+
s.top_margin = m
|
|
664
|
+
s.bottom_margin = m
|
|
665
|
+
s.left_margin = m
|
|
666
|
+
s.right_margin = m
|
|
667
|
+
|
|
668
|
+
if 'margin_top' in page_conf or 'top_margin' in page_conf:
|
|
669
|
+
m = _parse_length(page_conf.get('margin_top') or page_conf.get('top_margin'))
|
|
670
|
+
if m: s.top_margin = m
|
|
671
|
+
if 'margin_bottom' in page_conf or 'bottom_margin' in page_conf:
|
|
672
|
+
m = _parse_length(page_conf.get('margin_bottom') or page_conf.get('bottom_margin'))
|
|
673
|
+
if m: s.bottom_margin = m
|
|
674
|
+
if 'margin_left' in page_conf or 'left_margin' in page_conf:
|
|
675
|
+
m = _parse_length(page_conf.get('margin_left') or page_conf.get('left_margin'))
|
|
676
|
+
if m: s.left_margin = m
|
|
677
|
+
if 'margin_right' in page_conf or 'right_margin' in page_conf:
|
|
678
|
+
m = _parse_length(page_conf.get('margin_right') or page_conf.get('right_margin'))
|
|
679
|
+
if m: s.right_margin = m
|
|
680
|
+
|
|
681
|
+
def _apply_docx_config(doc: Document, config: dict):
|
|
682
|
+
"""Applies global settings from pamd.conf to the Word document."""
|
|
683
|
+
if not config:
|
|
684
|
+
return
|
|
685
|
+
|
|
686
|
+
normalized_config = {k.lower(): v for k, v in config.items() if isinstance(v, dict)}
|
|
687
|
+
|
|
688
|
+
# 1. Page settings
|
|
689
|
+
page_conf = normalized_config.get('page') or normalized_config.get('margins') or normalized_config.get('document')
|
|
690
|
+
if page_conf:
|
|
691
|
+
_apply_page_settings(doc, page_conf)
|
|
692
|
+
|
|
693
|
+
# 2. Metadata
|
|
694
|
+
meta_conf = normalized_config.get('metadata')
|
|
695
|
+
if meta_conf:
|
|
696
|
+
_apply_metadata(doc, meta_conf)
|
|
697
|
+
|
|
698
|
+
# 3. Header & Footer
|
|
699
|
+
header_conf = normalized_config.get('header')
|
|
700
|
+
footer_conf = normalized_config.get('footer')
|
|
701
|
+
|
|
702
|
+
for section in doc.sections:
|
|
703
|
+
if header_conf:
|
|
704
|
+
_configure_header_or_footer(section.header, header_conf, section)
|
|
705
|
+
if footer_conf:
|
|
706
|
+
_configure_header_or_footer(section.footer, footer_conf, section)
|
|
707
|
+
|
|
708
|
+
def docx_writer(blocks: list, styles: dict, output_file: str, config: dict = None):
|
|
429
709
|
"""
|
|
430
710
|
Translates parsed markdown blocks into a Word document and applies CSS styling.
|
|
431
711
|
|
|
@@ -438,6 +718,7 @@ def docx_writer(blocks: list, styles: dict, output_file: str):
|
|
|
438
718
|
blocks (list): The list of parsed Markdown block tuples (from `markdown_parser`).
|
|
439
719
|
styles (dict): The nested dictionary of CSS rules (from `style_parser`).
|
|
440
720
|
output_file (str): The absolute or relative file path where the resulting .docx should be saved.
|
|
721
|
+
config (dict): Optional dictionary of global settings parsed from pamd.conf (header, footer, metadata).
|
|
441
722
|
|
|
442
723
|
Outputs:
|
|
443
724
|
None: This function does not return a value, but it writes a binary .docx file to disk.
|
|
@@ -445,6 +726,10 @@ def docx_writer(blocks: list, styles: dict, output_file: str):
|
|
|
445
726
|
doc = Document()
|
|
446
727
|
current_ordered_numId = None
|
|
447
728
|
|
|
729
|
+
# Global document settings from pamd.conf
|
|
730
|
+
if config:
|
|
731
|
+
_apply_docx_config(doc, config)
|
|
732
|
+
|
|
448
733
|
# Global body style
|
|
449
734
|
if 'body' in styles:
|
|
450
735
|
normal_style = doc.styles['Normal']
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyact-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.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
|
|
@@ -86,11 +86,30 @@ Run the compiler from your terminal to generate your final Markdown document:
|
|
|
86
86
|
pyact path/to/your_file.pamd -o output.md
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
###
|
|
89
|
+
### 5. Compile to Word (DOCX)
|
|
90
90
|
You can also compile straight to a native Microsoft Word Document, complete with CSS styling!
|
|
91
91
|
```bash
|
|
92
92
|
pyact path/to/your_file.pamd -o output.md --docx output.docx
|
|
93
93
|
```
|
|
94
94
|
*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!*
|
|
95
95
|
|
|
96
|
+
### 6. Configure Word Export via `pamd.conf`
|
|
97
|
+
Control document metadata, running headers, and footers with a `pamd.conf` file:
|
|
98
|
+
|
|
99
|
+
```ini
|
|
100
|
+
[Header]
|
|
101
|
+
left = My Project
|
|
102
|
+
right = 2026
|
|
103
|
+
|
|
104
|
+
[Footer]
|
|
105
|
+
left = Confidential
|
|
106
|
+
center = Page {page} of {pages}
|
|
107
|
+
|
|
108
|
+
[Metadata]
|
|
109
|
+
author = Jane Doe
|
|
110
|
+
title = Monthly Report
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
PyAct will automatically detect `pamd.conf` in your directory, inject dynamic page numbers (`{page}` / `{pages}`), configure tab stops, and populate document metadata!
|
|
114
|
+
|
|
96
115
|
That's it! You now have documents ready to be shared, published, or distributed.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.6.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|