Skip to content

Commit 4f7669f

Browse files
Lutz Grossclaude
andcommitted
Add doc/user/convert_environments.py LaTeX conversion helper
Converts the old Python-documentation LaTeX environments (classdesc, methoddesc, funcdesc, membdesc, datadesc) into standard LaTeX that pandoc can process. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent b0185cc commit 4f7669f

1 file changed

Lines changed: 213 additions & 0 deletions

File tree

doc/user/convert_environments.py

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Convert old Python documentation style LaTeX environments to standard LaTeX.
4+
5+
This script converts:
6+
- \begin{classdesc}{Name}{args}...\end{classdesc}
7+
- \begin{methoddesc}[Class]{method}{args}...\end{methoddesc}
8+
- \begin{funcdesc}{func}{args}...\end{funcdesc}
9+
- \begin{membdesc}[Class]{member}...\end{membdesc}
10+
- \begin{datadesc}{name}...\end{datadesc}
11+
12+
To standard LaTeX that pandoc can understand.
13+
"""
14+
15+
import re
16+
import sys
17+
from pathlib import Path
18+
19+
20+
def convert_classdesc(content):
21+
r"""Convert classdesc environments.
22+
23+
\begin{classdesc}{ClassName}{args} body \end{classdesc}
24+
->
25+
\subsubsection*{class ClassName(args)}
26+
body
27+
"""
28+
def replacer(match):
29+
classname = match.group(1)
30+
args = match.group(2).strip()
31+
body = match.group(3).strip()
32+
33+
if args:
34+
sig = f"{classname}({args})"
35+
else:
36+
sig = classname
37+
38+
return f"\\subsubsection*{{class {sig}}}\n{body}\n"
39+
40+
pattern = r'\\begin\{classdesc\}\{([^}]+)\}\{([^}]*)\}(.*?)\\end\{classdesc\}'
41+
return re.sub(pattern, replacer, content, flags=re.DOTALL)
42+
43+
44+
def convert_methoddesc(content):
45+
r"""Convert methoddesc environments.
46+
47+
\begin{methoddesc}[Class]{method}{args} body \end{methoddesc}
48+
->
49+
\paragraph{Class.method(args)}
50+
body
51+
"""
52+
def replacer(match):
53+
classname = match.group(1) if match.group(1) else ''
54+
method = match.group(2)
55+
args = match.group(3).strip()
56+
body = match.group(4).strip()
57+
58+
if classname:
59+
sig = f"{classname}.{method}({args})"
60+
else:
61+
sig = f"{method}({args})"
62+
63+
return f"\\paragraph{{{sig}}}\n{body}\n"
64+
65+
pattern = r'\\begin\{methoddesc\}(?:\[([^\]]*)\])?\{([^}]+)\}\{([^}]*)\}(.*?)\\end\{methoddesc\}'
66+
return re.sub(pattern, replacer, content, flags=re.DOTALL)
67+
68+
69+
def convert_funcdesc(content):
70+
r"""Convert funcdesc environments.
71+
72+
\begin{funcdesc}{func}{args} body \end{funcdesc}
73+
->
74+
\paragraph{func(args)}
75+
body
76+
"""
77+
def replacer(match):
78+
func = match.group(1)
79+
args = match.group(2).strip()
80+
body = match.group(3).strip()
81+
82+
return f"\\paragraph{{{func}({args})}}\n{body}\n"
83+
84+
pattern = r'\\begin\{funcdesc\}\{([^}]+)\}\{([^}]*)\}(.*?)\\end\{funcdesc\}'
85+
return re.sub(pattern, replacer, content, flags=re.DOTALL)
86+
87+
88+
def convert_membdesc(content):
89+
r"""Convert membdesc environments.
90+
91+
\begin{membdesc}[Class]{member} body \end{membdesc}
92+
->
93+
\paragraph{Class.member}
94+
body
95+
"""
96+
def replacer(match):
97+
classname = match.group(1) if match.group(1) else ''
98+
member = match.group(2)
99+
body = match.group(3).strip()
100+
101+
if classname:
102+
sig = f"{classname}.{member}"
103+
else:
104+
sig = member
105+
106+
return f"\\paragraph{{{sig}}}\n{body}\n"
107+
108+
pattern = r'\\begin\{membdesc\}(?:\[([^\]]*)\])?\{([^}]+)\}(.*?)\\end\{membdesc\}'
109+
return re.sub(pattern, replacer, content, flags=re.DOTALL)
110+
111+
112+
def convert_datadesc(content):
113+
r"""Convert datadesc environments.
114+
115+
\begin{datadesc}{name} body \end{datadesc}
116+
->
117+
\paragraph{name}
118+
body
119+
"""
120+
def replacer(match):
121+
name = match.group(1)
122+
body = match.group(2).strip()
123+
124+
return f"\\paragraph{{{name}}}\n{body}\n"
125+
126+
pattern = r'\\begin\{datadesc\}\{([^}]+)\}(.*?)\\end\{datadesc\}'
127+
return re.sub(pattern, replacer, content, flags=re.DOTALL)
128+
129+
130+
def convert_file(filepath, dry_run=False):
131+
"""Convert a single LaTeX file."""
132+
with open(filepath, 'r', encoding='utf-8') as f:
133+
original = f.read()
134+
135+
content = original
136+
137+
# Count before
138+
counts_before = {
139+
'classdesc': len(re.findall(r'\\begin\{classdesc\}', content)),
140+
'methoddesc': len(re.findall(r'\\begin\{methoddesc\}', content)),
141+
'funcdesc': len(re.findall(r'\\begin\{funcdesc\}', content)),
142+
'membdesc': len(re.findall(r'\\begin\{membdesc\}', content)),
143+
'datadesc': len(re.findall(r'\\begin\{datadesc\}', content)),
144+
}
145+
146+
total_before = sum(counts_before.values())
147+
if total_before == 0:
148+
return 0, {}
149+
150+
# Apply conversions
151+
content = convert_classdesc(content)
152+
content = convert_methoddesc(content)
153+
content = convert_funcdesc(content)
154+
content = convert_membdesc(content)
155+
content = convert_datadesc(content)
156+
157+
# Count after (should be 0)
158+
counts_after = {
159+
'classdesc': len(re.findall(r'\\begin\{classdesc\}', content)),
160+
'methoddesc': len(re.findall(r'\\begin\{methoddesc\}', content)),
161+
'funcdesc': len(re.findall(r'\\begin\{funcdesc\}', content)),
162+
'membdesc': len(re.findall(r'\\begin\{membdesc\}', content)),
163+
'datadesc': len(re.findall(r'\\begin\{datadesc\}', content)),
164+
}
165+
166+
if not dry_run and content != original:
167+
with open(filepath, 'w', encoding='utf-8') as f:
168+
f.write(content)
169+
170+
return total_before, counts_before
171+
172+
173+
def main():
174+
import argparse
175+
176+
parser = argparse.ArgumentParser(description='Convert Python doc style LaTeX to standard LaTeX')
177+
parser.add_argument('--dry-run', '-n', action='store_true',
178+
help='Show what would be changed without modifying files')
179+
parser.add_argument('--file', '-f', type=str, default=None,
180+
help='Convert a single file (default: all .tex files in doc/user/)')
181+
parser.add_argument('--verbose', '-v', action='store_true',
182+
help='Verbose output')
183+
args = parser.parse_args()
184+
185+
if args.file:
186+
files = [Path(args.file)]
187+
else:
188+
# Find all .tex files in the same directory as this script
189+
script_dir = Path(__file__).parent
190+
files = list(script_dir.glob('*.tex'))
191+
192+
total_converted = 0
193+
194+
print(f"{'[DRY RUN] ' if args.dry_run else ''}Converting Python doc style environments to standard LaTeX\n")
195+
196+
for filepath in sorted(files):
197+
count, details = convert_file(filepath, dry_run=args.dry_run)
198+
if count > 0:
199+
print(f"{filepath.name}: {count} environments converted")
200+
if args.verbose:
201+
for env, c in details.items():
202+
if c > 0:
203+
print(f" {env}: {c}")
204+
total_converted += count
205+
206+
print(f"\nTotal: {total_converted} environments {'would be ' if args.dry_run else ''}converted")
207+
208+
if args.dry_run and total_converted > 0:
209+
print("\nRun without --dry-run to apply changes")
210+
211+
212+
if __name__ == '__main__':
213+
main()

0 commit comments

Comments
 (0)