1212from cadquery .occ_impl .assembly import toJSON
1313from cadquery .occ_impl .jupyter_tools import DEFAULT_COLOR
1414from docutils .parsers .rst import directives
15+ from sphinx .application import Sphinx
1516
1617from cadquery .cq_directive import cq_directive_vtk , template_vtk , rendering_code
1718
4546
4647"""
4748
49+ theapp = None
50+
4851
4952class cadscript_directive (cq_directive_vtk ):
5053
@@ -90,7 +93,7 @@ def run(self):
9093 pre_script , script , text = self .get_source_file (content , options .get ("steps" , None ), "text_from_comment" in options )
9194 else :
9295 # else use inline code
93- script = "\n " .join (script )
96+ script = "\n " .join (script )
9497
9598 # add the prefix and postfix
9699 plot_code = prefix + "\n " + pre_script + "\n " + script + "\n " + postfix .format (result_var = result_var )
@@ -106,7 +109,7 @@ def run(self):
106109 def get_source_file (self , content , steps , text_from_comment ):
107110
108111 content = re .sub (r'^cadscript.show\(.*$' , '' , content , flags = re .MULTILINE ) # remove show() calls
109- text = ""
112+ text = []
110113 pre_script = []
111114 content = content + "\n " # fix problem with last line
112115 ellipsis = False
@@ -134,13 +137,15 @@ def get_source_file(self, content, steps, text_from_comment):
134137 if text_from_comment :
135138 # extract the text from the comment
136139 newscript = []
137- last_comment = ""
140+ last_comment = []
138141 for part in script :
139- last_comment = ""
142+ last_comment = []
140143 new_part = ""
141144 for line in part .split ("\n " )[:- 1 ]: # skip last empty item with [:-1]
142- if line .startswith ("#" ):
143- last_comment += line [1 :].strip () + " "
145+ if line .startswith ("# " ):
146+ last_comment .append (line [2 :])
147+ elif line .startswith ("#" ):
148+ last_comment .append (line [1 :])
144149 else :
145150 new_part += line + "\n "
146151 newscript .append (new_part )
@@ -160,23 +165,24 @@ def generate_output(self, plot_code, text, script, options):
160165 lines = []
161166
162167 if len (text ):
163- lines .extend (["" , text , "" ])
164-
165- if "side-by-side" in options :
166- lines .append (".. raw:: html" )
167168 lines .append ("" )
168- lines .append ( " <div class= \" side-by-side \" ><div class= \" leftside \" >" )
169+ lines .extend ( text )
169170 lines .append ("" )
170171
171- lines .extend (["" , "::" , "" ])
172- lines .extend ([" %s" % row .rstrip () for row in script .split ("\n " )])
172+ lines .append (".. raw:: html" )
173173 lines .append ("" )
174-
175174 if "side-by-side" in options :
176- lines .append (".. raw:: html" )
177- lines .append ("" )
178- lines .append (" </div>" )
179- lines .append ("" )
175+ lines .append (" <div class=\" cq-side-by-side\" ><div class=\" leftside\" >" )
176+ else :
177+ lines .append (" <div class=\" cq-top-bottom\" ><div class=\" cq-top\" >" )
178+ lines .append ("" )
179+
180+ lines .extend (self .generate_code_block (script ))
181+
182+ lines .append (".. raw:: html" )
183+ lines .append ("" )
184+ lines .append (" </div>" )
185+ lines .append ("" )
180186
181187 try :
182188 result = cqgi .parse (plot_code ).build ()
@@ -210,11 +216,10 @@ def generate_output(self, plot_code, text, script, options):
210216 traceback .print_exc ()
211217 assy = Assembly (Compound .makeText ("CQGI error" , 10 , 5 ))
212218
213- if "side-by-side" in options :
214- lines .append ("" )
215- lines .append (".. raw:: html" )
216- lines .append ("" )
217- lines .append (" </div>" )
219+ lines .append ("" )
220+ lines .append (".. raw:: html" )
221+ lines .append ("" )
222+ lines .append (" </div>" )
218223 lines .append ("" )
219224
220225 return lines
@@ -290,6 +295,74 @@ def render_image(self, assy, options):
290295 ).splitlines ()
291296
292297
298+ def generate_code_block (self , code ):
299+ """
300+ Generate a code block with links to the documentation for method calls.
301+ """
302+ highlighter = theapp .builder .highlighter
303+
304+ body_prefixes = ["body" , "box" , "result" , "extr" ]
305+ sketch_prefixes = ["sketch" , "s" ]
306+ cadscript_prefixes = ["cad" , "cadscript" ]
307+
308+ # Regular expression to find method calls like obj.method(
309+ # in html the string looks like
310+ # <span class="n">cadscript</span><span class="o">.</span><span class="n">make_box</span><span class="p">(
311+
312+ method_call_pattern = re .compile (
313+ r'\<span\s+class\=\"n\"\>' # <span class="n">
314+ r'\s*(\w+)\s*' # methodName
315+ r'\<\/span\>\s*' # </span>
316+ r'\<span\s+class\=\"o\"\>' # <span class="o">
317+ r'\s*\.\s*' # .
318+ r'\<\/span\>\s*' # </span>
319+ r'\<span\s+class\=\"n\"\>' # <span class="n">
320+ r'\s*(\w+)\s*' # methodName
321+ r'\<\/span\>\s*' # </span>
322+ r'\<span\s+class\=\"p\"\>' # <span class="p">
323+ r'\s*\(' # (
324+ )
325+
326+ # Function to replace method calls with links if they are documented
327+ # class="n">(\w+)\.(\w+)\((.*?)\)')
328+ def replace_method_call (match ):
329+ obj_name , method_name = match .groups ()
330+
331+ # Determine which document to link based on the prefix
332+ # todo: could be done by examining env.domaindata['py']['objects']
333+ if any (obj_name .startswith (prefix ) for prefix in body_prefixes ):
334+ link_target = f'ref_body.html#cadscript.Body.{ method_name } '
335+ elif any (obj_name .startswith (prefix ) for prefix in sketch_prefixes ):
336+ link_target = f'ref_sketch.html#cadscript.Sketch.{ method_name } '
337+ elif any (obj_name .startswith (prefix ) for prefix in cadscript_prefixes ):
338+ link_target = f'ref_module_functions.html#cadscript.{ method_name } '
339+ else :
340+ return match .group (0 ) # No replacement if prefix does not match
341+
342+ link = (
343+ f'<span class="n">{ obj_name } </span>'
344+ f'<span class="o">.</span>'
345+ f'<span class="n"><a class="codelink" href="{ link_target } ">{ method_name } </a></span>'
346+ f'<span class="p">('
347+ )
348+ return link
349+
350+
351+ # Use Sphinx highlighter to generate HTML
352+ highlighted_code = highlighter .highlight_block (code , 'python' )
353+
354+ # Replace method calls with links
355+ linked_code = method_call_pattern .sub (replace_method_call , highlighted_code )
356+
357+ lines = []
358+ lines .append (".. raw:: html" )
359+ lines .append ("" )
360+ lines .extend ([" %s" % line .rstrip () for line in linked_code .split ("\n " )])
361+ lines .append ("" )
362+
363+ return lines
364+
365+
293366
294367class cadscript_auto_directive (cadscript_directive ):
295368
@@ -351,6 +424,12 @@ def setup(app):
351424 app .add_js_file ("vtk.js" )
352425 app .add_js_file (None , body = rendering_code )
353426
427+ app .connect ("builder-inited" , remember_app )
428+
429+
430+ def remember_app (app : Sphinx ) -> None :
431+ global theapp # bad hack to remember the app object
432+ theapp = app
354433
355434
356435if __name__ == "__main__" :
@@ -367,5 +446,5 @@ def setup(app):
367446 setup (app )
368447 except Exception :
369448 pass
370- pre_script , script , text = c .get_source_file (open (c .get_file ('./examples/bracket.py' )).read (), "2-14 " , True )
449+ pre_script , script , text = c .get_source_file (open (c .get_file ('./examples/bracket.py' )).read (), "2" , True )
371450 pre_script , script , text = c .get_source_file (open (c .get_file ('./examples/getting_started.py' )).read (), None , None )
0 commit comments