4141from sphinx .errors import SphinxError
4242from sphinx .util import logging
4343
44- from .common import bar_format , get_tqdm
44+ from .common import bar_format , get_tqdm , sanitize_directive_content
4545
4646logger = logging .getLogger (__name__ )
4747
@@ -68,6 +68,13 @@ class MiriValidationError(SphinxError):
6868# Warn mode values
6969WARN_MODES = {"error" , "allow" } # error = fail on warnings (default), allow = permit warnings
7070
71+ # Known directive options for rust-example (used for validation in sanitization)
72+ KNOWN_DIRECTIVE_OPTIONS = {
73+ "ignore" , "compile_fail" , "should_panic" , "no_run" ,
74+ "miri" , "warn" , "edition" , "channel" , "version" ,
75+ "show_hidden" , "name"
76+ }
77+
7178
7279class RustExamplesConfig :
7380 """Configuration loaded from rust_examples_config.toml"""
@@ -479,12 +486,53 @@ def run(self) -> List[nodes.Node]:
479486 config = RustExamplesConfig ()
480487 env .rust_examples_config = config
481488
489+ # Get source location early (needed for error messages)
490+ source , line = self .state_machine .get_source_and_line (self .lineno )
491+
492+ # Parse the code content
493+ raw_code = '\n ' .join (self .content )
494+
495+ # Sanitize content - detect and extract misplaced directive options
496+ # This handles RST indentation issues where Sphinx puts options into content
497+ raw_code , extracted_options , raw_option_lines = sanitize_directive_content (raw_code )
498+
499+ if extracted_options :
500+ # Log a detailed warning about the indentation issue
501+ opt_names = list (extracted_options .keys ())
502+
503+ # Build informative message with context
504+ warning_parts = [
505+ f"{ source } :{ line } : Found directive options in code content "
506+ f"(RST indentation issue): { opt_names } ." ,
507+ "These options were extracted and will be applied, but please fix the source file." ,
508+ "The code content should be indented at least as much as the options."
509+ ]
510+
511+ # Add specific context for version-related options
512+ if 'version' in extracted_options :
513+ extracted_version = extracted_options ['version' ]
514+ warning_parts .append (
515+ f"Note: Extracted :version: { extracted_version } "
516+ f"(config default: { config .version } , "
517+ f"mismatch threshold: { config .version_mismatch_threshold } minor versions)"
518+ )
519+
520+ logger .warning (" " .join (warning_parts ))
521+
522+ # Merge extracted options into self.options
523+ # Sphinx-parsed options take precedence (they were properly formatted)
524+ for opt_name , opt_value in extracted_options .items ():
525+ if opt_name not in self .options :
526+ # Handle flag-style options (empty value means flag is set)
527+ if opt_name in ('ignore' , 'no_run' , 'show_hidden' ) and opt_value == '' :
528+ self .options [opt_name ] = None # Flag style
529+ else :
530+ self .options [opt_name ] = opt_value
531+
482532 # Get configuration for showing hidden lines
483533 show_hidden_global = getattr (env .config , 'rust_examples_show_hidden' , False )
484534 show_hidden = 'show_hidden' in self .options or show_hidden_global
485535
486- # Parse the code content
487- raw_code = '\n ' .join (self .content )
488536 display_code , full_code , hidden_line_numbers = process_hidden_lines (raw_code , show_hidden )
489537
490538 # Determine rustdoc attribute
@@ -509,9 +557,6 @@ def run(self) -> List[nodes.Node]:
509557 miri_pattern = None
510558 has_miri_option = 'miri' in self .options
511559
512- # Store source location (needed for error messages)
513- source , line = self .state_machine .get_source_and_line (self .lineno )
514-
515560 if has_miri_option :
516561 miri_mode , miri_pattern = parse_miri_option (self .options .get ('miri' , '' ))
517562
0 commit comments