1414
1515import importlib .machinery
1616import importlib .util
17+ import json
1718import os
1819import re
1920import shutil
@@ -375,61 +376,98 @@ def strip_html_tags(text: str) -> str:
375376 return stripper .get_data ()
376377
377378
379+ def _extract_svelte_prop (props : str , prop_name : str ):
380+ """
381+ Read the JSON value of a `name={<json>}` svelte attribute out of an opening-tag
382+ attribute string, or return `None` if the prop is absent or malformed.
383+
384+ The JSON is parsed with `raw_decode` so values containing `}` or `>` (type
385+ annotations, defaults, ...) don't confuse the extraction.
386+ """
387+ match = re .search (rf"\b{ prop_name } =\{{" , props )
388+ if match is None :
389+ return None
390+ try :
391+ value , end = json .JSONDecoder ().raw_decode (props , match .end ())
392+ except ValueError :
393+ return None
394+ if not props [end :].lstrip ().startswith ("}" ):
395+ return None
396+ return value
397+
398+
399+ def _extract_section (block : str , tag : str ):
400+ """Return the (stripped) content of `<tag>...</tag>` in `block`, or `None`."""
401+ match = re .search (rf"<{ tag } >(.*?)</{ tag } >" , block , re .DOTALL )
402+ return match .group (1 ).strip () if match else None
403+
404+
378405def extract_docstring_info (docstring_block : str ) -> dict :
379- """Extract information from a docstring block."""
406+ """
407+ Extract information from a `<Docstring ...>...</Docstring>` block.
408+
409+ Metadata (name, anchor, source, signature) comes from the component props, the
410+ markdown-bearing sections (parameters, returns, ...) from the component body.
411+ See `doc_builder.autodoc.get_signature_component_svelte` for the emitted shape.
412+ """
380413 info = {
381414 "name" : None ,
382415 "anchor" : None ,
383416 "source" : None ,
384417 "parameters" : None ,
385418 "paramsdesc" : None ,
419+ "paramsgroups" : [],
386420 "rettype" : None ,
387421 "retdesc" : None ,
388- "description" : None ,
422+ "yieldtype" : None ,
423+ "yielddesc" : None ,
424+ "raisederrors" : None ,
425+ "raises" : None ,
426+ "is_getset_descriptor" : False ,
389427 }
390428
391- # Extract name
392- name_match = re .search (r"<name>(.*?)</name>" , docstring_block , re .DOTALL )
393- if name_match :
394- raw_name = name_match .group (1 ).strip ()
429+ open_tag = re .match (r"<Docstring\s(?P<props>[^\n]*)>" , docstring_block )
430+ props = open_tag .group ("props" ) if open_tag else ""
431+
432+ name = _extract_svelte_prop (props , "name" )
433+ if name :
395434 # Remove "class " or "def " prefix if present
396- cleaned_name = re .sub (r"^(class|def)\s+" , "" , raw_name )
397- info ["name" ] = cleaned_name
398-
399- # Extract anchor
400- anchor_match = re .search (r"<anchor>(.*?)</anchor>" , docstring_block , re .DOTALL )
401- if anchor_match :
402- info ["anchor" ] = anchor_match .group (1 ).strip ()
403-
404- # Extract source
405- source_match = re .search (r"<source>(.*?)</source>" , docstring_block , re .DOTALL )
406- if source_match :
407- info ["source" ] = source_match .group (1 ).strip ()
408-
409- # Extract parameters description
410- paramsdesc_match = re .search (r"<paramsdesc>(.*?)</paramsdesc>" , docstring_block , re .DOTALL )
411- if paramsdesc_match :
412- info ["paramsdesc" ] = paramsdesc_match .group (1 ).strip ()
413-
414- # Extract return type
415- rettype_match = re .search (r"<rettype>(.*?)</rettype>" , docstring_block , re .DOTALL )
416- if rettype_match :
417- info ["rettype" ] = rettype_match .group (1 ).strip ()
418-
419- # Extract return description
420- retdesc_match = re .search (r"<retdesc>(.*?)</retdesc>" , docstring_block , re .DOTALL )
421- if retdesc_match :
422- info ["retdesc" ] = retdesc_match .group (1 ).strip ()
423-
424- # Extract text outside docstring tags but inside the div
425- # This is the description text
426- description_match = re .search (r"</docstring>(.*?)(?:</div>|$)" , docstring_block , re .DOTALL )
427- if description_match :
428- desc_text = description_match .group (1 ).strip ()
429- # Remove any remaining HTML tags
430- desc_text = re .sub (r"<[^>]+>" , "" , desc_text )
431- if desc_text :
432- info ["description" ] = desc_text
435+ info ["name" ] = re .sub (r"^(class|def)\s+" , "" , name .strip ())
436+
437+ anchor = _extract_svelte_prop (props , "anchor" )
438+ # `anchor` is stringified python-side, so a missing anchor arrives as `"None"`.
439+ if anchor and anchor != "None" :
440+ info ["anchor" ] = anchor .strip ()
441+
442+ source = _extract_svelte_prop (props , "source" )
443+ if source :
444+ info ["source" ] = source .strip ()
445+
446+ parameters = _extract_svelte_prop (props , "parameters" )
447+ if isinstance (parameters , list ):
448+ info ["parameters" ] = parameters
449+
450+ info ["is_getset_descriptor" ] = bool (_extract_svelte_prop (props , "isGetSetDescriptor" ))
451+
452+ for key , tag in (
453+ ("paramsdesc" , "paramsdesc" ),
454+ ("rettype" , "rettype" ),
455+ ("retdesc" , "retdesc" ),
456+ ("yieldtype" , "yieldtype" ),
457+ ("yielddesc" , "yielddesc" ),
458+ ("raisederrors" , "raisederrors" ),
459+ ("raises" , "raises" ),
460+ ):
461+ info [key ] = _extract_section (docstring_block , tag )
462+
463+ # Extra parameter groups, e.g. transformers' "Parameters for sequence generation".
464+ for group in re .findall (r"<paramsgroup>(.*?)</paramsgroup>" , docstring_block , re .DOTALL ):
465+ info ["paramsgroups" ].append (
466+ {
467+ "title" : _extract_section (group , "paramsgrouptitle" ),
468+ "desc" : _extract_section (group , "paramsgroupdesc" ),
469+ }
470+ )
433471
434472 return info
435473
@@ -476,6 +514,23 @@ def format_parameters(paramsdesc: str) -> str:
476514 return "\n " .join (formatted_params )
477515
478516
517+ def format_call_signature (name : str , parameters ) -> str :
518+ """
519+ Render the `parameters` prop (a list of `{"name": ..., "val": ...}`) as a call
520+ signature, e.g. ``HfApi.merge_pull_request(discussion_num: int, token = None)``.
521+ """
522+ args = ", " .join (f"{ param .get ('name' , '' )} { param .get ('val' , '' )} " .strip () for param in parameters )
523+ return f"{ name } ({ args } )"
524+
525+
526+ def format_type (value : str ) -> str :
527+ """
528+ Wrap a return/yield/raise type in backticks, unless it already carries markup
529+ (a resolved `[Name](url)` doc link, inline code, ...) that backticks would break.
530+ """
531+ return value if re .search (r"[`\[\]<>]" , value ) else f"`{ value } `"
532+
533+
479534def process_docstring_block (docstring_block : str ) -> str :
480535 """
481536 Process a docstring block by:
@@ -497,18 +552,20 @@ def process_docstring_block(docstring_block: str) -> str:
497552 parts .append (f"#### { info ['name' ]} " )
498553 parts .append ("" )
499554
555+ # Add the call signature (properties are not callable, so they have none)
556+ if info ["parameters" ] is not None and not info ["is_getset_descriptor" ]:
557+ parts .append ("```python" )
558+ parts .append (format_call_signature (info ["name" ], info ["parameters" ]))
559+ parts .append ("```" )
560+ parts .append ("" )
561+
500562 # Add source link if available
501563 if info ["source" ]:
502564 # Strip any HTML from source
503565 source_clean = strip_html_tags (info ["source" ])
504566 parts .append (f"[Source]({ source_clean } )" )
505567 parts .append ("" )
506568
507- # Add description
508- if info ["description" ]:
509- parts .append (info ["description" ])
510- parts .append ("" )
511-
512569 # Add parameters description
513570 if info ["paramsdesc" ]:
514571 parts .append ("**Parameters:**" )
@@ -518,22 +575,31 @@ def process_docstring_block(docstring_block: str) -> str:
518575 parts .append (formatted_params )
519576 parts .append ("" )
520577
521- # Add return type
522- if info ["rettype" ]:
523- parts .append ("**Returns:**" )
578+ # Add the extra parameter groups, if any
579+ for group in info ["paramsgroups" ]:
580+ if not group ["desc" ]:
581+ continue
582+ parts .append (f"**{ group ['title' ] or 'Parameters' } :**" )
524583 parts .append ("" )
525- # Strip HTML tags from return type
526- rettype_clean = strip_html_tags (info ["rettype" ])
527- parts .append (f"`{ rettype_clean } `" )
584+ parts .append (format_parameters (group ["desc" ]))
528585 parts .append ("" )
529586
530- # Add return description
531- if info ["retdesc" ]:
532- if not info ["rettype" ]:
533- parts .append ("**Returns:**" )
534- parts .append ("" )
535- parts .append (info ["retdesc" ])
587+ # Add the returns / yields / raises sections
588+ for type_key , desc_key , label in (
589+ ("rettype" , "retdesc" , "Returns" ),
590+ ("yieldtype" , "yielddesc" , "Yields" ),
591+ ("raisederrors" , "raises" , "Raises" ),
592+ ):
593+ if not info [type_key ] and not info [desc_key ]:
594+ continue
595+ header = f"**{ label } :**"
596+ if info [type_key ]:
597+ header += f" { format_type (info [type_key ])} "
598+ parts .append (header )
536599 parts .append ("" )
600+ if info [desc_key ]:
601+ parts .append (info [desc_key ])
602+ parts .append ("" )
537603
538604 result = "\n " .join (parts )
539605
@@ -611,18 +677,23 @@ def strip_html_from_markdown(content: str) -> str:
611677 Strip HTML from markdown content.
612678
613679 Handles:
614- - Docstring blocks wrapped in <div class="docstring...">...</div>
680+ - `<Docstring ...>...</Docstring>` components emitted by autodoc, which become a
681+ level-4 heading with the signature, then the `**Parameters:**`/`**Returns:**`
682+ sections. The object description follows the component in the source, so it
683+ keeps its place right after those (same order as the rendered HTML page).
615684 - Other HTML tags throughout the document
616685 """
617686 result = content
618687
619- # Process docstring blocks with their wrapping divs
620- # Pattern to match: <div class="docstring...">...<docstring>...</docstring>...</div>
621- docstring_pattern = r'<div[^>]*class="docstring[^"]*"[^>]*> .*?<docstring>.*?</docstring>.*?</div>'
688+ # The opening tag's attribute values are single-line JSON (see
689+ # `get_signature_component_svelte`), so it ends at the last `>` on its own line.
690+ docstring_pattern = r"<Docstring\s[^\n]*>\n .*?</Docstring>"
622691
623692 def replace_docstring (match ):
624693 block = match .group (0 )
625- return process_docstring_block (block )
694+ # Trailing newline: the object description directly follows the component (with a
695+ # single newline in between), and must not end up glued to the last section.
696+ return process_docstring_block (block ) + "\n "
626697
627698 result = re .sub (docstring_pattern , replace_docstring , result , flags = re .DOTALL )
628699
0 commit comments