1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
|
from docutils import nodes
from docutils.parsers.rst import Directive
from sphinx.util import logging
import csv
from io import StringIO
import re
import shlex
logger = logging.getLogger(__name__)
class VersionReference(nodes.Inline, nodes.TextElement):
"""Represents an inline version reference."""
pass
class VersionSetDirective(Directive):
"""Directive for setting version references within a page scope."""
required_arguments = 2 # name and value
optional_arguments = 0
def run(self):
env = self.state.document.settings.env
if not hasattr(env, 'doc_version_refs'):
env.doc_version_refs = {}
current_doc = env.docname
if current_doc not in env.doc_version_refs:
env.doc_version_refs[current_doc] = {}
name, value = self.arguments
if name.lower() == 'latest':
logger.warning('Cannot override the "latest" keyword with version-set')
return []
# Handle 'latest' value by getting the actual version
if value.lower() == 'latest':
data = getattr(env, 'compatibility_matrix', None)
if data:
latest_version = get_latest_rocm_version(data)
if latest_version:
value = latest_version
env.doc_version_refs[current_doc][name] = value
return []
def clean_library_name(name):
"""Extract library name from RST formatting."""
# Handle :doc: format
doc_match = re.search(r':doc:`([^<]+)(?:\s+<[^>]+>)?`', name)
if doc_match:
return doc_match.group(1).strip()
# Handle other link formats
link_match = re.search(r'`([^<]+)(?:\s+<[^>]+>)?`_?', name)
if link_match:
return link_match.group(1).strip()
return name.strip()
def get_latest_rocm_version(data):
"""Get the latest ROCm version from the matrix headers."""
if not data or len(data) == 0:
return None
# Get all column names except 'ROCm Version'
columns = [col for col in data[0].keys() if col != 'ROCm Version']
# Return the first column name (assumed to be the latest version)
return columns[0] if columns else None
def version_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
"""
Role function to print version value.
Usage: :version:`version_name`
"""
try:
version_name = text.strip()
env = inliner.document.settings.env
if hasattr(env, 'doc_version_refs'):
current_doc = env.docname
if current_doc in env.doc_version_refs:
doc_refs = env.doc_version_refs[current_doc]
if version_name in doc_refs:
version = doc_refs[version_name]
node = nodes.Text(version)
return [node], []
msg = inliner.reporter.warning(
f'No version defined for name {version_name}',
line=lineno
)
return [], [msg]
except Exception as e:
msg = inliner.reporter.error(
f'Error looking up version: {str(e)}',
line=lineno
)
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]
def version_ref_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
"""
Role function for version references.
Usage: :version-ref:`library_name release`
:version-ref:`"library name" release`
:version-ref:`library_name latest`
:version-ref:`rocm latest`
"""
try:
# Parse the text - handle both quoted and unquoted formats
if '"' in text:
parts = shlex.split(text)
else:
parts = text.split()
if len(parts) != 2:
msg = inliner.reporter.error(
'Version reference must be in format "library_name release" or "\\"library name\\" release"',
line=lineno
)
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]
library_name, release = parts
env = inliner.document.settings.env
# Check if release is a version reference in current document
if hasattr(env, 'doc_version_refs'):
current_doc = env.docname
if current_doc in env.doc_version_refs:
doc_refs = env.doc_version_refs[current_doc]
if release in doc_refs:
release = doc_refs[release]
# Handle special case for "rocm latest"
if library_name.lower() == 'rocm' and release.lower() == 'latest':
data = getattr(env, 'compatibility_matrix', None)
if not data:
raise ValueError("Compatibility matrix not found in environment")
latest_version = get_latest_rocm_version(data)
if latest_version:
node = VersionReference()
node += nodes.Text(latest_version)
return [node], []
else:
msg = inliner.reporter.warning(
'No ROCm versions found in compatibility matrix',
line=lineno
)
return [], [msg]
version = lookup_version(inliner, library_name, release)
if version:
node = VersionReference()
node += nodes.Text(version)
return [node], []
else:
msg = inliner.reporter.warning(
f'No version found for library {library_name} in release {release}',
line=lineno
)
return [], [msg]
except Exception as e:
msg = inliner.reporter.error(
f'Error looking up version: {str(e)}',
line=lineno
)
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]
def lookup_version(inliner, library_name, release):
"""Look up the version in the compatibility matrix."""
env = inliner.document.settings.env
data = getattr(env, 'compatibility_matrix', None)
if not data:
raise ValueError("Compatibility matrix not found in environment")
# Handle the 'latest' keyword
if release.lower() == 'latest':
latest_version = get_latest_rocm_version(data)
if not latest_version:
return None
release = latest_version
# For ROCm, check if the version exists in column headers
if library_name.lower() == 'rocm':
columns = [col for col in data[0].keys() if col != 'ROCm Version']
if release in columns:
return release
return None
# Find the library version
for row in data:
row_lib_name = clean_library_name(row['ROCm Version'])
if row_lib_name == library_name:
# Get the version, removing any whitespace
version = row.get(release, '').strip()
if version:
return version
# If not found, try a case-insensitive search
for row in data:
row_lib_name = clean_library_name(row['ROCm Version'])
if row_lib_name.lower() == library_name.lower():
version = row.get(release, '').strip()
if version:
return version
return None
def visit_version_reference(self, node):
self.body.append(f'<span class="version-reference">')
def depart_version_reference(self, node):
self.body.append('</span>')
def load_compatibility_matrix(app):
"""Load the compatibility matrix content from CSV."""
if not app.config.compatibility_matrix_file:
logger.warning('No compatibility matrix file configured')
return
try:
with open(app.config.compatibility_matrix_file, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
app.env.compatibility_matrix = list(reader)
logger.info('Successfully loaded compatibility matrix')
# Debug: print first few rows with their library names
for row in list(app.env.compatibility_matrix)[:5]:
if 'ROCm Version' in row:
lib_name = clean_library_name(row['ROCm Version'])
logger.debug(f"Loaded library: {lib_name}")
except Exception as e:
logger.error(f'Error loading compatibility matrix: {str(e)}')
def purge_version_refs(app, env, docname):
"""Remove version references for a document when it is purged"""
if hasattr(env, 'doc_version_refs'):
if docname in env.doc_version_refs:
del env.doc_version_refs[docname]
def setup(app):
app.add_node(VersionReference,
html=(visit_version_reference, depart_version_reference))
app.add_role('version-ref', version_ref_role)
app.add_role('version', version_role)
app.add_directive('version-set', VersionSetDirective)
# Add a config value for the compatibility matrix file path
app.add_config_value('compatibility_matrix_file', None, 'env')
# Connect to the builder-inited event to load the matrix
app.connect('builder-inited', load_compatibility_matrix)
# Connect to env-purge-doc event to clean up document-specific version refs
app.connect('env-purge-doc', purge_version_refs)
return {
'parallel_read_safe': True,
'parallel_write_safe': True,
}
|