5Core methods to extract options information from ConfigBlock classes and merge with
6output variables metadata from a YAML file.
13from typing
import Any, Dict, List, Type, Optional, Union
15from AthenaCommon.Utils.unixtools
import find_datafile
17logger = logging.getLogger(
"AutogenDocumentation")
21 Load output variables metadata from YAML file.
26 description: Variable description
27 toggled_by: Optional condition description
30 yaml_filepath: Path to the YAML file
33 Dictionary mapping block class names to their output variables
35 with open(yaml_filepath,
"r")
as f:
36 data = yaml.safe_load(f)
37 return data
if data
else {}
42 Extract options information from a ConfigBlock subclass.
45 block_class: A class that inherits from ConfigBlock
48 A dictionary containing the class name and its options
51 instance = block_class()
54 options_dict = instance.getOptions()
58 for option_name, option_obj
in options_dict.items():
60 if option_name
in [
"groupName",
"propertyOverrides",
"ignoreDependencies"]:
64 "type": option_obj.type.__name__
if option_obj.type
is not None else "None",
65 "default": option_obj.default,
66 "info": option_obj.info,
67 "required": option_obj.required,
68 "noneAction": option_obj.noneAction,
73 hasattr(instance,
"_expertModeSettings")
74 and option_name
in instance._expertModeSettings
76 expert_rule = instance._expertModeSettings[option_name]
77 if not isinstance(expert_rule, list):
78 expert_rule = [expert_rule]
81 option_info[
"expertMode"] = expert_rule
82 options_list.append(option_info)
85 "class": block_class.__name__,
86 "module": block_class.__module__,
87 "docstring": inspect.getdoc(block_class),
88 "options": options_list,
94 Extract a physical unit from an info string.
95 Currently looks for energy units like MeV or GeV.
98 info: The information string from an option.
101 The detected unit as a string ("MeV", "GeV", etc.) or None if no unit is found.
121 for pattern
in patterns:
122 if re.search(pattern, info):
125 elif "GeV" in pattern:
127 elif "mm" in pattern:
134 Scan an info string for backtick-enclosed substrings of the form `A::B`.
135 Turn them into a link to the appropriate module/files.
138 info: The input info string.
141 The processed string with Markdown links where applicable.
147 pattern =
r"`([^`]+)::([^`]+)`"
149 def replace_match(match):
150 A, B = match.group(1), match.group(2)
151 if A ==
"CP" or A ==
"ORUtils":
152 url = f
"https://acode-browser1.usatlas.bnl.gov/lxr/search?%21v=head&_filestring=**{B}**&_string="
153 return f
"[`{A}::{B}`]({url})"
154 elif A ==
"xAOD" or A ==
"AthOnnx":
155 url = f
"https://acode-browser1.usatlas.bnl.gov/lxr/ident?v=head&_i={B}&_identdefonly=1&_remember=1"
160 return re.sub(pattern, replace_match, info)
165 Convert JIRA ticket references in a string to Markdown links.
167 - JIRA tickets are of the form: all-caps letters, a dash, then digits (e.g., ATLASG-2358)
168 - Converted to Markdown links: [ATLASG-2358](https://its.cern.ch/jira/browse/ATLASG-2358)
171 info: Input string that may contain JIRA tickets.
174 The string with JIRA tickets converted to Markdown links.
180 pattern =
r"\b([A-Z]+-\d+)\b"
182 def replace_match(match):
183 ticket = match.group(1)
184 url = f
"https://its.cern.ch/jira/browse/{ticket}"
185 return f
"[{ticket}]({url})"
187 return re.sub(pattern, replace_match, info)
191 block_classes: List[Type], output_vars_yaml: Optional[Union[str, List[str]]] =
None
192) -> List[Dict[str, Any]]:
194 Extract options information from a list of ConfigBlock classes and merge
195 with output variables metadata.
198 block_classes: List of classes that inherit from ConfigBlock
199 output_vars_yaml: Optional path to YAML file or list of paths to YAML files
200 containing output variables. If multiple files provided,
201 their contents will be merged. Files are located using
205 List of dictionaries, each containing information about a block class
213 if isinstance(output_vars_yaml, str)
214 else output_vars_yaml
218 for yaml_file
in yaml_files:
220 resolved_path = find_datafile(yaml_file)
221 if resolved_path
is None:
222 raise FileNotFoundError(f
"Could not locate YAML file: {yaml_file}")
226 for class_name, variables
in file_vars.items():
227 if class_name
in output_vars_map:
229 output_vars_map[class_name].extend(variables)
231 output_vars_map[class_name] = variables
234 for block_class
in block_classes:
237 class_name = block_class.__name__
238 info[
"output_variables"] = output_vars_map.get(class_name, [])
246 """Save extracted data as YAML."""
247 with open(filepath,
"w")
as f:
248 yaml.dump(data, f, default_flow_style=
False, sort_keys=
False)
249 logger.info(f
"Saved YAML to {filepath}")
254 Generate Markdown documentation for a single block.
257 block_info: Dictionary containing block information with keys:
258 - class: Block class name
259 - module: Module containing the block
260 - options: List of option dictionaries
261 - output_variables: List of output variable dictionaries
264 Markdown string for this block
269 if block_info.get(
"options"):
270 for opt
in block_info[
"options"]:
274 if name
in [
"skipOnData",
"skipOnMC"]:
275 if not opt[
"default"]
is True:
278 if name
in [
"onlyForDSIDs"]:
279 if not opt[
"default"]
is []:
283 label = f
"`{opt['label']}` ({opt['type']})"
284 if opt[
"expertMode"]
is not None:
285 expertOptions = list(opt[
"expertMode"])
286 label += f
" **[expert-only options: {','.join(['`' + str(x) + '`' for x in expertOptions])}]**"
287 if opt[
"required"]
is True or opt[
"noneAction"] !=
"ignore":
288 label +=
" **[REQUIRED]**"
290 markdown += f
"{label}\n"
291 info_string = opt[
"info"]
294 markdown += f
": {info_string}"
296 if opt.get(
"default") !=
"":
297 default_val = opt[
"default"]
298 default_str = repr(default_val)
301 unit = opt.get(
"physicalUnit")
302 if unit
is None or default_val
is None:
303 default_display = f
"`{default_str}`"
305 default_display = f
"`{default_str}` GeV"
309 if isinstance(default_val, (list, tuple)):
310 converted = [float(x) / 1000
for x
in default_val]
312 "[" +
", ".join(f
"{x}" for x
in converted) +
"]"
315 converted = float(default_val) / 1000
316 converted_str = f
"{converted}"
317 except (TypeError, ValueError):
319 default_display = f
"`{default_str}` MeV (`{converted_str}` GeV)"
321 default_display = f
"`{default_str}` {unit}"
323 markdown += f
" Default: {default_display}."
328 if block_info.get(
"output_variables"):
333 for var
in block_info[
"output_variables"]:
334 if var.get(
"toggled_by"):
335 condition = var[
"toggled_by"]
336 if condition
not in toggled_vars:
337 toggled_vars[condition] = []
338 toggled_vars[condition].append(var)
340 always_saved.append(var)
344 markdown +=
'!!! success "Registers the following variables:"\n'
345 for var
in always_saved:
346 var_name = var.get(
"name",
"N/A")
347 var_desc = var.get(
"description",
"")
348 markdown += f
" - `{var_name}`: {var_desc}\n"
352 for condition, vars_list
in toggled_vars.items():
354 f
'!!! success "Additional variables toggled by `{condition}`:"\n'
356 for var
in vars_list:
357 var_name = var.get(
"name",
"N/A")
358 var_desc = var.get(
"description",
"")
359 markdown += f
" - `{var_name}`: {var_desc}\n"
363 f
"Block {block_info.get('class')} didn't register any output variables."
370 input_filepath: str, output_filepath: str, block_data: List[Dict[str, Any]]
373 Process an input markdown file, replacing AUTOGEN<BlockName> markers
374 with generated block documentation.
376 Looks for lines of the form "AUTOGEN<BlockName>" and replaces them
377 with the generated markdown for that block. All other content is
381 input_filepath: Path to the input markdown file
382 output_filepath: Path to write the processed output
383 block_data: List of extracted block information dictionaries
386 FileNotFoundError: If input file does not exist
387 ValueError: If a referenced block is not found in block_data
390 block_markdown_map = {
395 with open(input_filepath,
"r")
as f:
396 lines = f.readlines()
400 stripped = line.strip()
403 if stripped.startswith(
"AUTOGEN<")
and stripped.endswith(
">"):
405 block_name = stripped[8:-1]
407 if block_name
in block_markdown_map:
408 output_lines.append(block_markdown_map[block_name])
411 f
"Block '{block_name}' not found in extracted block data"
414 output_lines.append(line)
417 with open(output_filepath,
"w")
as f:
418 f.writelines(output_lines)
420 logger.info(f
"Processed markdown saved to {output_filepath}.")
Dict[str, Any] extract_block_options(Type block_class)
Optional[str] interpret_physical_unit(str info)
str process_info_links(str info)
List[Dict[str, Any]] extract_from_classes(List[Type] block_classes, Optional[Union[str, List[str]]] output_vars_yaml=None)
None process_markdown_with_autogen(str input_filepath, str output_filepath, List[Dict[str, Any]] block_data)
Dict[str, List[Dict[str, Any]]] load_output_variables(str yaml_filepath)
str link_jira_tickets(str info)
None save_as_yaml(List[Dict[str, Any]] data, str filepath)
str generate_block_markdown(Dict[str, Any] block_info)