22from dataclasses import Field
33from typing import Any , Callable , Dict , Iterable , List , Optional , Sequence , Tuple , Union
44
5+ import yaml
56from docutils import frontend , nodes
67from docutils .core import default_description , publish_cmdline
78from docutils .parsers .rst import Parser as RstParser
@@ -58,32 +59,39 @@ def __bool__(self):
5859"""Sentinel for arguments not set through docutils.conf."""
5960
6061
61- DOCUTILS_EXCLUDED_ARGS = (
62- # docutils.conf can't represent callables
63- "heading_slug_func" ,
64- # docutils.conf can't represent dicts
65- "html_meta" ,
66- "substitutions" ,
67- # we can't add substitutions so not needed
68- "sub_delimiters" ,
69- # sphinx only options
70- "heading_anchors" ,
71- "ref_domains" ,
72- "update_mathjax" ,
73- "mathjax_classes" ,
74- )
75- """Names of settings that cannot be set in docutils.conf."""
62+ def _create_validate_yaml (field : Field ):
63+ """Create a deserializer/validator for a json setting."""
64+
65+ def _validate_yaml (
66+ setting , value , option_parser , config_parser = None , config_section = None
67+ ):
68+ """Check/normalize a key-value pair setting.
69+
70+ Items delimited by `,`, and key-value pairs delimited by `=`.
71+ """
72+ try :
73+ output = yaml .safe_load (value )
74+ except Exception :
75+ raise ValueError ("Invalid YAML string" )
76+ if "validator" in field .metadata :
77+ field .metadata ["validator" ](None , field , output )
78+ return output
79+
80+ return _validate_yaml
7681
7782
7883def _attr_to_optparse_option (at : Field , default : Any ) -> Tuple [dict , str ]:
79- """Convert a field into a Docutils optparse options dict."""
84+ """Convert a field into a Docutils optparse options dict.
85+
86+ :returns: (option_dict, default)
87+ """
8088 if at .type is int :
81- return {"metavar" : "<int>" , "validator" : _validate_int }, f" (default: { default } )"
89+ return {"metavar" : "<int>" , "validator" : _validate_int }, str (default )
8290 if at .type is bool :
8391 return {
8492 "metavar" : "<boolean>" ,
8593 "validator" : frontend .validate_boolean ,
86- }, f" (default: { default } )"
94+ }, str (default )
8795 if at .type is str :
8896 return {
8997 "metavar" : "<str>" ,
@@ -96,28 +104,32 @@ def _attr_to_optparse_option(at: Field, default: Any) -> Tuple[dict, str]:
96104 "metavar" : f"<{ '|' .join (repr (a ) for a in args )} >" ,
97105 "type" : "choice" ,
98106 "choices" : args ,
99- }, f" (default: { default !r } )"
107+ }, repr (default )
100108 if at .type in (Iterable [str ], Sequence [str ]):
101109 return {
102110 "metavar" : "<comma-delimited>" ,
103111 "validator" : frontend .validate_comma_separated_list ,
104- }, f"(default: ' { ',' .join (default )} ')"
112+ }, "," .join (default )
105113 if at .type == Tuple [str , str ]:
106114 return {
107115 "metavar" : "<str,str>" ,
108116 "validator" : _create_validate_tuple (2 ),
109- }, f"(default: ' { ',' .join (default )} ')"
117+ }, "," .join (default )
110118 if at .type == Union [int , type (None )]:
111119 return {
112120 "metavar" : "<null|int>" ,
113121 "validator" : _validate_int ,
114- }, f" (default: { default } )"
122+ }, str (default )
115123 if at .type == Union [Iterable [str ], type (None )]:
116- default_str = "," .join (default ) if default else ""
117124 return {
118125 "metavar" : "<null|comma-delimited>" ,
119126 "validator" : frontend .validate_comma_separated_list ,
120- }, f"(default: { default_str !r} )"
127+ }, "," .join (default ) if default else ""
128+ if get_origin (at .type ) is dict :
129+ return {
130+ "metavar" : "<yaml-dict>" ,
131+ "validator" : _create_validate_yaml (at ),
132+ }, str (default ) if default else ""
121133 raise AssertionError (
122134 f"Configuration option { at .name } not set up for use in docutils.conf."
123135 )
@@ -133,34 +145,33 @@ def attr_to_optparse_option(
133145 name = f"{ prefix } { attribute .name } "
134146 flag = "--" + name .replace ("_" , "-" )
135147 options = {"dest" : name , "default" : DOCUTILS_UNSET }
136- at_options , type_str = _attr_to_optparse_option (attribute , default )
148+ at_options , default_str = _attr_to_optparse_option (attribute , default )
137149 options .update (at_options )
138150 help_str = attribute .metadata .get ("help" , "" ) if attribute .metadata else ""
139- return (f"{ help_str } { type_str } " , [flag ], options )
151+ if default_str :
152+ help_str += f" (default: { default_str } )"
153+ return (help_str , [flag ], options )
140154
141155
142- def create_myst_settings_spec (
143- excluded : Sequence [str ], config_cls = MdParserConfig , prefix : str = "myst_"
144- ):
156+ def create_myst_settings_spec (config_cls = MdParserConfig , prefix : str = "myst_" ):
145157 """Return a list of Docutils setting for the docutils MyST section."""
146158 defaults = config_cls ()
147159 return tuple (
148160 attr_to_optparse_option (at , getattr (defaults , at .name ), prefix )
149161 for at in config_cls .get_fields ()
150- if at .name not in excluded
162+ if ( not at .metadata . get ( "sphinx_only" , False ))
151163 )
152164
153165
154166def create_myst_config (
155167 settings : frontend .Values ,
156- excluded : Sequence [str ],
157168 config_cls = MdParserConfig ,
158169 prefix : str = "myst_" ,
159170):
160171 """Create a configuration instance from the given settings."""
161172 values = {}
162173 for attribute in config_cls .get_fields ():
163- if attribute .name in excluded :
174+ if attribute .metadata . get ( "sphinx_only" , False ) :
164175 continue
165176 setting = f"{ prefix } { attribute .name } "
166177 val = getattr (settings , setting , DOCUTILS_UNSET )
@@ -178,7 +189,7 @@ class Parser(RstParser):
178189 settings_spec = (
179190 "MyST options" ,
180191 None ,
181- create_myst_settings_spec (DOCUTILS_EXCLUDED_ARGS ),
192+ create_myst_settings_spec (),
182193 * RstParser .settings_spec ,
183194 )
184195 """Runtime settings specification."""
@@ -209,7 +220,7 @@ def parse(self, inputstring: str, document: nodes.document) -> None:
209220
210221 # create parsing configuration from the global config
211222 try :
212- config = create_myst_config (document .settings , DOCUTILS_EXCLUDED_ARGS )
223+ config = create_myst_config (document .settings )
213224 except Exception as exc :
214225 error = document .reporter .error (f"Global myst configuration invalid: { exc } " )
215226 document .append (error )
0 commit comments