pyact-cli 0.9.0__tar.gz → 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyact-cli
3
- Version: 0.9.0
3
+ Version: 1.1.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
@@ -1,5 +1,6 @@
1
1
  import inspect
2
2
  from typing import Callable, List, Any
3
+ from enum import Enum
3
4
  from pyact.py2tex import py2tex
4
5
 
5
6
  def equation(func: Callable, values: List[Any]) -> str:
@@ -17,10 +18,13 @@ def equation(func: Callable, values: List[Any]) -> str:
17
18
  Outputs:
18
19
  str: A Markdown math string formatted as `$$ Name = Equation = Value $$`.
19
20
  """
21
+ print("Function 'equation()' is deprecated and will be removed, please use Calculation() class.")
20
22
  source = inspect.getsource(func)
21
23
  value = func(*values)
22
24
  name, sep, tail = source.rpartition("=")
25
+ print(name, tail)
23
26
  head, sep, equ = tail.rpartition(":")
27
+ print(head, equ)
24
28
 
25
29
  name = name.strip()
26
30
  if "_" in name:
@@ -139,6 +143,7 @@ def table(headers: List[str], values: List[List[Any]], caption: str, add_no: boo
139
143
  Outputs:
140
144
  str: A formatted Markdown string containing the caption and the table.
141
145
  """
146
+ print("Function 'table()' is deprecated and will be removed, please use Table() class.")
142
147
  if add_no:
143
148
  headers_string = f"|No.|"
144
149
  breaker_line = "|---|"
@@ -192,6 +197,7 @@ def unordered_list(items: List[str]) -> str:
192
197
  Outputs:
193
198
  str: A Markdown string where each item is prefixed with `* `.
194
199
  """
200
+ print("Function 'unordered_list()' is deprecated and will be removed, please use CList() class.")
195
201
  list_string = ""
196
202
  for i in items:
197
203
  list_string += f"* {i}\n"
@@ -210,6 +216,7 @@ def ordered_list(items: List[str]) -> str:
210
216
  Outputs:
211
217
  str: A Markdown string where each item is numbered sequentially (1. 2. 3.).
212
218
  """
219
+ print("Function 'ordered_list()' is deprecated and will be removed, please use CList() class.")
213
220
  list_string = ""
214
221
  for i, item in enumerate(items):
215
222
  list_string += f"{i+1}. {item}\n"
@@ -228,6 +235,7 @@ def checklist(items: List[str]) -> str:
228
235
  Outputs:
229
236
  str: A Markdown string where each item is prefixed with `- [ ] `.
230
237
  """
238
+ print("Function 'checklist()' is deprecated and will be removed, please use CList() class.")
231
239
  list_string = ""
232
240
  for i in items:
233
241
  list_string += f"- [ ] {i}\n"
@@ -282,6 +290,7 @@ def table_to_dxf(headers: List[str], values: List[List[Any]], filename: str, cel
282
290
  Outputs:
283
291
  str: A string confirming the export, formatted as a Markdown link.
284
292
  """
293
+ print("Function 'table_to_dfx()' is deprecated and will be removed, please use Table() class.")
285
294
  try:
286
295
  import ezdxf
287
296
  except ImportError:
@@ -319,4 +328,207 @@ def table_to_dxf(headers: List[str], values: List[List[Any]], filename: str, cel
319
328
  ).set_placement((x, y), align=ezdxf.enums.TextEntityAlignment.MIDDLE_CENTER)
320
329
 
321
330
  doc.saveas(filename)
322
- return f"*[Successfully exported table to {filename}]({filename})*"
331
+ return f"*[Successfully exported table to {filename}]({filename})*"
332
+
333
+ class Calculation:
334
+ def __init__(self, func: Callable, values: List[float|int], repl: bool = False, precision: int = 3):
335
+ self.func = func
336
+ self.values = values
337
+ self.replace = repl
338
+ self.precision = precision
339
+ self.value = round(func(*values), self.precision)
340
+ self.name = None
341
+ self.vars = None
342
+ self.repr = self.get_representation()
343
+
344
+ def __repr__(self):
345
+ return self.repr
346
+
347
+ def recalculate(self, values, precision: int = None):
348
+ if precision is None:
349
+ return Calculation(self.func, values, self.replace, self.precision)
350
+ else:
351
+ return Calculation(self.func, values, self.replace, precision)
352
+
353
+ def get_representation(self):
354
+ source = inspect.getsource(self.func)
355
+
356
+ name, sep, tail = source.rpartition("=")
357
+
358
+ head, sep, equ = tail.rpartition(":")
359
+ self.vars = head.replace("lambda", "").replace(" ", "").split(",")
360
+
361
+ name = name.strip()
362
+ self.name = f"$ {py2tex(name)} $"
363
+ if "_" in name:
364
+ parts = name.split("_", 1)
365
+ name = f"{parts[0]}_{{{parts[1]}}}"
366
+
367
+ import re
368
+ greek_pattern = r'(?<![a-zA-Z])(alpha|beta|gamma|delta|epsilon|zeta|eta|theta|iota|kappa|lambda|mu|nu|xi|omicron|pi|rho|sigma|tau|upsilon|phi|chi|psi|omega|Alpha|Beta|Gamma|Delta|Epsilon|Zeta|Eta|Theta|Iota|Kappa|Lambda|Mu|Nu|Xi|Omicron|Pi|Rho|Sigma|Tau|Upsilon|Phi|Chi|Psi|Omega)(?![a-zA-Z])'
369
+ name = re.sub(greek_pattern, r'\\\1', name)
370
+ texified = py2tex(equ.replace(chr(10), ''))
371
+ replaced = texified
372
+ for i, vr in enumerate(self.vars):
373
+ replaced = replaced.replace(py2tex(vr.replace(",", "")), f"{self.values[i]}")
374
+
375
+ if self.replace:
376
+ return f"$$ {name} = {texified} = {replaced} = {self.value} $$"
377
+ else:
378
+ return f"$$ {name} = {texified} = {self.value} $$"
379
+
380
+ def _extract(self, other) -> float:
381
+ return float(other.value) if isinstance(other, Calculation) else float(other)
382
+
383
+ def __add__(self, other):
384
+ return float(self.value) + self._extract(other)
385
+
386
+ def __radd__(self, other):
387
+ return self._extract(other) + float(self.value)
388
+
389
+ def __sub__(self, other):
390
+ return float(self.value) - self._extract(other)
391
+
392
+ def __rsub__(self, other):
393
+ return self._extract(other) - float(self.value)
394
+
395
+ def __mul__(self, other):
396
+ return float(self.value) * self._extract(other)
397
+
398
+ def __rmul__(self, other):
399
+ return self._extract(other) * float(self.value)
400
+
401
+ def __truediv__(self, other):
402
+ return float(self.value) / self._extract(other)
403
+
404
+ def __rtruediv__(self, other):
405
+ return self._extract(other) / float(self.value)
406
+
407
+ def __pow__(self, power):
408
+ return float(self.value) ** self._extract(power)
409
+
410
+ def __neg__(self):
411
+ return -float(self.value)
412
+
413
+ def __abs__(self):
414
+ return abs(float(self.value))
415
+
416
+ class Table():
417
+ def __init__(self, headers: List[str], values: List[List[Any]], caption: str, add_no: bool = False, starting_number: int = 1):
418
+ self.headers = headers,
419
+ self.values = values
420
+ self.caption = caption
421
+ self.add_no = add_no
422
+ self.starting_number = starting_number
423
+ self.repr = self.get_representation()
424
+
425
+ def __repr__(self):
426
+ return self.repr
427
+
428
+ def get_representation(self):
429
+ if self.add_no:
430
+ headers_string = f"|No.|"
431
+ breaker_line = "|---|"
432
+ else:
433
+ headers_string = f"|"
434
+ breaker_line = "|"
435
+
436
+ for i in self.headers:
437
+ headers_string += f"{i}|"
438
+ breaker_line += "-"*len(i)+"|"
439
+
440
+ table_content = ""
441
+ for i, row in enumerate(self.values):
442
+ if self.add_no:
443
+ table_content += f"|{i+self.starting_number}|"
444
+ else:
445
+ table_content += "|"
446
+ for value in row:
447
+ table_content += f"{value}|"
448
+ table_content += "\n"
449
+
450
+ caption_html = f"<span style='text-align: center; display: block; font-style: italic;'>{self.caption}</span>"
451
+ return f"{caption_html} \n {headers_string}\n{breaker_line}\n{table_content}"
452
+
453
+ def toDXF(self, filename: str, cell_width: float = 50.0, cell_height: float = 10.0):
454
+ try:
455
+ import ezdxf
456
+ except ImportError:
457
+ return f"*(Error: ezdxf library is not installed. Cannot generate {filename})*"
458
+
459
+ doc = ezdxf.new('R2010')
460
+ msp = doc.modelspace()
461
+
462
+ # Construct full grid data
463
+ rows = [self.headers] + self.values
464
+ num_rows = len(rows)
465
+ num_cols = len(self.headers)
466
+
467
+ # Draw horizontal grid lines
468
+ for i in range(num_rows + 1):
469
+ y = -i * cell_height
470
+ msp.add_line((0, y), (num_cols * cell_width, y))
471
+
472
+ # Draw vertical grid lines
473
+ for j in range(num_cols + 1):
474
+ x = j * cell_width
475
+ msp.add_line((x, 0), (x, -num_rows * cell_height))
476
+
477
+ # Draw text centered in each cell
478
+ for i, row in enumerate(rows):
479
+ y = -i * cell_height - (cell_height / 2)
480
+ for j, cell_val in enumerate(row):
481
+ if j >= num_cols: continue # Safety bounds
482
+ x = j * cell_width + (cell_width / 2)
483
+ msp.add_text(
484
+ str(cell_val),
485
+ dxfattribs={
486
+ 'height': cell_height * 0.4,
487
+ }
488
+ ).set_placement((x, y), align=ezdxf.enums.TextEntityAlignment.MIDDLE_CENTER)
489
+
490
+ doc.saveas(filename)
491
+ return f"*[Successfully exported table to {filename}]({filename})*"
492
+
493
+ class ListType(Enum):
494
+ ORDERED = 1
495
+ UNORDERED = 2
496
+ CHECKLIST = 3
497
+
498
+ class CList():
499
+ def __init__(self, items: List[Any], type: ListType):
500
+ self.items = items
501
+ self.type = type
502
+ self.repr = self.get_representation()
503
+
504
+ def __repr__(self):
505
+ return self.repr
506
+
507
+ def get_representation(self):
508
+ if self.type == ListType.ORDERED:
509
+ return self.get_ordered()
510
+ elif self.type == ListType.UNORDERED:
511
+ return self.get_unordered()
512
+ elif self.type == ListType.CHECKLIST:
513
+ return self.get_checklist()
514
+
515
+ def get_ordered(self):
516
+ list_string = ""
517
+ for i, item in enumerate(self.items):
518
+ list_string += f"{i+1}. {item}\n"
519
+ return list_string
520
+ def get_unordered(self):
521
+ list_string = ""
522
+ for i in self.items:
523
+ list_string += f"* {i}\n"
524
+ return list_string
525
+ def get_checklist(self):
526
+ list_string = ""
527
+ for i in self.items:
528
+ list_string += f"- [ ] {i}\n"
529
+ return list_string
530
+
531
+ if __name__ == "__main__":
532
+ func_w = lambda x_1, y, z: x_1+y+z
533
+ func_eq = Calculation(func_w, [1,2,3], True)
534
+ print(func_eq.vars)
@@ -0,0 +1 @@
1
+ __version__ = "1.1.0"
@@ -423,8 +423,8 @@ def _process_inline(paragraph, text, styles, tag):
423
423
  _add_internal_hyperlink(paragraph, f"[{fn_id}]", f"footnote_{fn_id}")
424
424
 
425
425
  else:
426
- # Handle plain text + bold/italic
427
- subparts = re.split(r'(\*\*.*?\*\*|\*.*?\*)', part)
426
+ # Handle plain text + bold/italic + inline code
427
+ subparts = re.split(r'(\*\*.*?\*\*|\*.*?\*|`[^`]+`)', part)
428
428
  for subpart in subparts:
429
429
  if not subpart: continue
430
430
  if subpart.startswith('**') and subpart.endswith('**'):
@@ -435,6 +435,13 @@ def _process_inline(paragraph, text, styles, tag):
435
435
  run = paragraph.add_run(subpart[1:-1])
436
436
  run.italic = True
437
437
  if tag in styles: _apply_css_to_run(run, styles[tag])
438
+ elif subpart.startswith('`') and subpart.endswith('`') and len(subpart) >= 2:
439
+ run = paragraph.add_run(subpart[1:-1])
440
+ run.font.name = "Consolas"
441
+ run.font.size = Pt(9.5)
442
+ rPr = run._r.get_or_add_rPr()
443
+ rPr.append(parse_xml(f'<w:shd {nsdecls("w")} w:fill="F0F2F5"/>'))
444
+ if 'code' in styles: _apply_css_to_run(run, styles['code'])
438
445
  else:
439
446
  run = paragraph.add_run(subpart)
440
447
  if tag in styles: _apply_css_to_run(run, styles[tag])
@@ -964,6 +971,237 @@ def _add_table_of_contents(doc, title="Table of Contents", headings=None, styles
964
971
  fld_end.set(qn('w:fldCharType'), 'end')
965
972
  r_end._r.append(fld_end)
966
973
 
974
+ def _parse_border_component(rules: dict, prefix: str = 'border'):
975
+ """
976
+ Parses border component from rules dict (e.g. prefix='border' or 'border-top').
977
+ Returns (val, sz, color) or None if not specified.
978
+ """
979
+ val = None
980
+ sz = None
981
+ color = None
982
+
983
+ shorthand = rules.get(prefix, '')
984
+ if shorthand:
985
+ for p in shorthand.split():
986
+ p_low = p.lower()
987
+ if p_low in ('none', 'hidden'):
988
+ val = 'none'
989
+ elif p_low in ('solid', 'dashed', 'dotted', 'double'):
990
+ val = 'single' if p_low == 'solid' else p_low
991
+ elif '#' in p:
992
+ hex_c = p.replace('#', '').strip()
993
+ if len(hex_c) == 3: hex_c = ''.join([c*2 for c in hex_c])
994
+ if len(hex_c) == 6: color = hex_c.upper()
995
+ elif 'pt' in p_low or 'px' in p_low or p_low.isdigit():
996
+ num = float(re.sub(r'[^\d.]', '', p_low) or '1')
997
+ sz = str(max(1, int(num * 8)))
998
+
999
+ style_key = f"{prefix}-style" if prefix != 'border' else 'border-style'
1000
+ if style_key in rules:
1001
+ bs = rules[style_key].lower()
1002
+ if bs in ('none', 'hidden'): val = 'none'
1003
+ elif bs == 'solid': val = 'single'
1004
+ elif bs in ('dashed', 'dotted', 'double'): val = bs
1005
+
1006
+ color_key = f"{prefix}-color" if prefix != 'border' else 'border-color'
1007
+ if color_key in rules:
1008
+ hex_c = rules[color_key].replace('#', '').strip()
1009
+ if len(hex_c) == 3: hex_c = ''.join([c*2 for c in hex_c])
1010
+ if len(hex_c) == 6: color = hex_c.upper()
1011
+
1012
+ width_key = f"{prefix}-width" if prefix != 'border' else 'border-width'
1013
+ if width_key in rules:
1014
+ num = float(re.sub(r'[^\d.]', '', rules[width_key]) or '1')
1015
+ sz = str(max(1, int(num * 8)))
1016
+
1017
+ return val, sz, color
1018
+
1019
+ def _apply_table_styling(table, table_lines: list, styles: dict):
1020
+ """
1021
+ Applies CSS styling to a python-docx table, including borders, cell padding,
1022
+ header shading, alignments, and Word table properties.
1023
+ """
1024
+ t_styles = styles.get('table', {})
1025
+ th_styles = styles.get('th', styles.get('table_header', {}))
1026
+ td_styles = styles.get('td', styles.get('table_cell', {}))
1027
+ zebra_styles = styles.get('tr:nth-child(even)', styles.get('table_row:nth-child(even)', {}))
1028
+
1029
+ # 1. Built-in Word table style if specified (e.g. style: "Table Grid")
1030
+ if 'style' in t_styles:
1031
+ s_name = t_styles['style'].strip("'\"")
1032
+ try:
1033
+ table.style = s_name
1034
+ except Exception:
1035
+ pass
1036
+
1037
+ tblPr = table._tbl.tblPr
1038
+
1039
+ # 2. Table Width
1040
+ width_val = t_styles.get('width', '100%').strip().lower()
1041
+ if width_val in ('100%', 'full'):
1042
+ tblPr.append(parse_xml(f'<w:tblW {nsdecls("w")} w:w="5000" w:type="pct"/>'))
1043
+ elif width_val == 'auto':
1044
+ tblPr.append(parse_xml(f'<w:tblW {nsdecls("w")} w:w="0" w:type="auto"/>'))
1045
+ else:
1046
+ m = re.search(r'([\d.]+)\s*(in|cm|mm|pt)?', width_val)
1047
+ if m:
1048
+ val, unit = float(m.group(1)), m.group(2)
1049
+ if unit == 'in': dxa = int(val * 1440)
1050
+ elif unit == 'cm': dxa = int(val * 567)
1051
+ elif unit == 'mm': dxa = int(val * 56.7)
1052
+ elif unit == 'pt': dxa = int(val * 20)
1053
+ else: dxa = int(val * 1440)
1054
+ tblPr.append(parse_xml(f'<w:tblW {nsdecls("w")} w:w="{dxa}" w:type="dxa"/>'))
1055
+
1056
+ # 3. Table Alignment (center, left, right)
1057
+ align_val = t_styles.get('text-align', t_styles.get('align', 'center')).strip().lower()
1058
+ if align_val in ('center', 'left', 'right'):
1059
+ tblPr.append(parse_xml(f'<w:jc {nsdecls("w")} w:val="{align_val}"/>'))
1060
+
1061
+ # 4. Table Borders
1062
+ def_val = "single"
1063
+ def_sz = "4" # 0.5 pt
1064
+ def_col = "D0D7DE"
1065
+
1066
+ b_val, b_sz, b_col = _parse_border_component(t_styles, 'border')
1067
+ if b_val is None: b_val = def_val
1068
+ if b_sz is None: b_sz = def_sz
1069
+ if b_col is None: b_col = def_col
1070
+
1071
+ sides = {}
1072
+ for side in ('top', 'left', 'bottom', 'right', 'insideH', 'insideV'):
1073
+ css_key = f"border-{side.lower()}"
1074
+ s_val, s_sz, s_col = _parse_border_component(t_styles, css_key)
1075
+ sides[side] = {
1076
+ 'val': s_val if s_val is not None else b_val,
1077
+ 'sz': s_sz if s_sz is not None else b_sz,
1078
+ 'color': s_col if s_col is not None else b_col,
1079
+ }
1080
+
1081
+ border_elements = []
1082
+ for side, cfg in sides.items():
1083
+ if cfg['val'] == 'none':
1084
+ border_elements.append(f'<w:{side} w:val="none"/>')
1085
+ else:
1086
+ border_elements.append(f'<w:{side} w:val="{cfg["val"]}" w:sz="{cfg["sz"]}" w:space="0" w:color="{cfg["color"]}"/>')
1087
+
1088
+ tblBorders = parse_xml(f'''
1089
+ <w:tblBorders {nsdecls("w")}>
1090
+ {''.join(border_elements)}
1091
+ </w:tblBorders>
1092
+ ''')
1093
+ tblPr.append(tblBorders)
1094
+
1095
+ # 5. Cell Margins / Padding
1096
+ pad_val = t_styles.get('padding', t_styles.get('cell-padding', '')).strip().lower()
1097
+ top_dxa, bot_dxa, left_dxa, right_dxa = 120, 120, 160, 160
1098
+ if pad_val:
1099
+ nums = [float(n) for n in re.findall(r'[\d.]+', pad_val)]
1100
+ if len(nums) == 1:
1101
+ d = int(nums[0] * 20)
1102
+ top_dxa, bot_dxa, left_dxa, right_dxa = d, d, d, d
1103
+ elif len(nums) >= 2:
1104
+ tb = int(nums[0] * 20)
1105
+ lr = int(nums[1] * 20)
1106
+ top_dxa, bot_dxa, left_dxa, right_dxa = tb, tb, lr, lr
1107
+
1108
+ tblCellMar = parse_xml(f'''
1109
+ <w:tblCellMar {nsdecls("w")}>
1110
+ <w:top w:w="{top_dxa}" w:type="dxa"/>
1111
+ <w:bottom w:w="{bot_dxa}" w:type="dxa"/>
1112
+ <w:left w:w="{left_dxa}" w:type="dxa"/>
1113
+ <w:right w:w="{right_dxa}" w:type="dxa"/>
1114
+ </w:tblCellMar>
1115
+ ''')
1116
+ tblPr.append(tblCellMar)
1117
+
1118
+ # 6. Parse Markdown Column Alignments
1119
+ col_aligns = []
1120
+ for line in table_lines:
1121
+ if '---' in line:
1122
+ parts = [c.strip() for c in line.strip('|').split('|')]
1123
+ for p in parts:
1124
+ if p.startswith(':') and p.endswith(':'):
1125
+ col_aligns.append(WD_ALIGN_PARAGRAPH.CENTER)
1126
+ elif p.endswith(':'):
1127
+ col_aligns.append(WD_ALIGN_PARAGRAPH.RIGHT)
1128
+ elif p.startswith(':'):
1129
+ col_aligns.append(WD_ALIGN_PARAGRAPH.LEFT)
1130
+ else:
1131
+ col_aligns.append(None)
1132
+ break
1133
+
1134
+ # 7. Rows: Repeat header on pages & prevent row splitting
1135
+ if len(table.rows) > 0:
1136
+ table.rows[0]._tr.get_or_add_trPr().append(parse_xml(f'<w:tblHeader {nsdecls("w")}/>'))
1137
+ for row in table.rows:
1138
+ row._tr.get_or_add_trPr().append(parse_xml(f'<w:cantSplit {nsdecls("w")}/>'))
1139
+
1140
+ # 8. Header background color
1141
+ th_bg = th_styles.get('background-color', th_styles.get('background', '')).replace('#', '').strip()
1142
+ if not th_bg and 'background-color' in styles.get('table_header', {}):
1143
+ th_bg = styles['table_header']['background-color'].replace('#', '').strip()
1144
+ if not th_bg:
1145
+ th_bg = 'F2F4F8'
1146
+ if len(th_bg) == 3: th_bg = ''.join([c*2 for c in th_bg])
1147
+
1148
+ # Apply to Header Row
1149
+ if len(table.rows) > 0:
1150
+ for c_idx, cell in enumerate(table.rows[0].cells):
1151
+ tcPr = cell._tc.get_or_add_tcPr()
1152
+ if len(th_bg) == 6:
1153
+ tcPr.append(parse_xml(f'<w:shd {nsdecls("w")} w:fill="{th_bg.upper()}"/>'))
1154
+ p = cell.paragraphs[0]
1155
+ if c_idx < len(col_aligns) and col_aligns[c_idx] is not None:
1156
+ p.alignment = col_aligns[c_idx]
1157
+ elif 'text-align' in th_styles:
1158
+ ta = th_styles['text-align'].lower()
1159
+ if ta == 'center': p.alignment = WD_ALIGN_PARAGRAPH.CENTER
1160
+ elif ta == 'right': p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
1161
+ elif ta == 'left': p.alignment = WD_ALIGN_PARAGRAPH.LEFT
1162
+ else:
1163
+ p.alignment = WD_ALIGN_PARAGRAPH.CENTER
1164
+
1165
+ for r in p.runs:
1166
+ if 'font-weight' not in th_styles or th_styles['font-weight'].lower() == 'bold':
1167
+ r.bold = True
1168
+ if 'color' in th_styles:
1169
+ hc = th_styles['color'].replace('#', '').strip()
1170
+ if len(hc) == 6:
1171
+ r.font.color.rgb = RGBColor(int(hc[0:2],16), int(hc[2:4],16), int(hc[4:6],16))
1172
+ if 'font-family' in th_styles:
1173
+ r.font.name = th_styles['font-family'].strip("'\"")
1174
+ if 'font-size' in th_styles:
1175
+ sz_num = re.sub(r'[^\d.]', '', th_styles['font-size'])
1176
+ if sz_num: r.font.size = Pt(float(sz_num))
1177
+
1178
+ # 9. Apply to Body Rows
1179
+ zebra_bg = zebra_styles.get('background-color', zebra_styles.get('background', '')).replace('#', '').strip()
1180
+ if len(zebra_bg) == 3: zebra_bg = ''.join([c*2 for c in zebra_bg])
1181
+
1182
+ for r_idx in range(1, len(table.rows)):
1183
+ is_even = (r_idx % 2 == 0)
1184
+ row_bg = zebra_bg if (is_even and len(zebra_bg) == 6) else None
1185
+
1186
+ for c_idx, cell in enumerate(table.rows[r_idx].cells):
1187
+ tcPr = cell._tc.get_or_add_tcPr()
1188
+ if row_bg:
1189
+ tcPr.append(parse_xml(f'<w:shd {nsdecls("w")} w:fill="{row_bg.upper()}"/>'))
1190
+ elif 'background-color' in td_styles:
1191
+ td_bg = td_styles['background-color'].replace('#', '').strip()
1192
+ if len(td_bg) == 3: td_bg = ''.join([c*2 for c in td_bg])
1193
+ if len(td_bg) == 6:
1194
+ tcPr.append(parse_xml(f'<w:shd {nsdecls("w")} w:fill="{td_bg.upper()}"/>'))
1195
+
1196
+ p = cell.paragraphs[0]
1197
+ if c_idx < len(col_aligns) and col_aligns[c_idx] is not None:
1198
+ p.alignment = col_aligns[c_idx]
1199
+ elif 'text-align' in td_styles:
1200
+ ta = td_styles['text-align'].lower()
1201
+ if ta == 'center': p.alignment = WD_ALIGN_PARAGRAPH.CENTER
1202
+ elif ta == 'right': p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
1203
+ elif ta == 'left': p.alignment = WD_ALIGN_PARAGRAPH.LEFT
1204
+
967
1205
  def docx_writer(blocks: list, styles: dict, output_file: str, config: dict = None):
968
1206
  """
969
1207
  Translates parsed markdown blocks into a Word document and applies CSS styling.
@@ -1084,9 +1322,14 @@ def docx_writer(blocks: list, styles: dict, output_file: str, config: dict = Non
1084
1322
  table = doc.add_table(rows=len(rows), cols=len(rows[0]))
1085
1323
  for r_idx, row in enumerate(rows):
1086
1324
  for c_idx, val in enumerate(row):
1087
- table.cell(r_idx, c_idx).text = val
1088
- if 'table' in styles:
1089
- pass
1325
+ cell = table.cell(r_idx, c_idx)
1326
+ p = cell.paragraphs[0]
1327
+ p.text = ""
1328
+ p.paragraph_format.space_before = Pt(0)
1329
+ p.paragraph_format.space_after = Pt(0)
1330
+ tag = 'th' if r_idx == 0 else 'td'
1331
+ _process_inline(p, val, styles, tag)
1332
+ _apply_table_styling(table, table_lines, styles)
1090
1333
 
1091
1334
  elif btype == 'toc':
1092
1335
  toc_seen = True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyact-cli
3
- Version: 0.9.0
3
+ Version: 1.1.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
@@ -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.9.0",
8
+ version="1.1.0",
9
9
  author="Abstergo2003",
10
10
  author_email="",
11
11
  description="A CLI tool to compile .pamd files to Markdown.",
@@ -1 +0,0 @@
1
- __version__ = "0.9.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes