"""Bootstrap-based sphinx theme from the PyData community."""importjsonfromfunctoolsimportpartialfrompathlibimportPathfromtypingimportDictfromurllib.parseimporturlparseimportrequestsfromrequests.exceptionsimportConnectionError,HTTPError,RetryErrorfromsphinx.applicationimportSphinxfromsphinx.builders.dirhtmlimportDirectoryHTMLBuilderfromsphinx.errorsimportExtensionErrorfrom.importedit_this_page,logo,pygments,short_link,toctree,translator,utils
[文档]defupdate_config(app):"""Update config with new default values and handle deprecated keys."""# By the time `builder-inited` happens, `app.builder.theme_options` already exists.# At this point, modifying app.config.html_theme_options will NOT update the# page's HTML context (e.g. in jinja, `theme_keyword`).# To do this, you must manually modify `app.builder.theme_options`.theme_options=utils.get_theme_options_dict(app)warning=partial(utils.maybe_warn,app)# TODO: DEPRECATE after v1.0themes=["light","dark"]forthemeinthemes:ifstyle:=theme_options.get(f"pygment_{theme}_style"):theme_options[f"pygments_{theme}_style"]=stylewarning(f'The parameter "pygment_{theme}_style" was renamed to 'f'"pygments_{theme}_style" (note the "s" on "pygments").')# Validate icon linksifnotisinstance(theme_options.get("icon_links",[]),list):raiseExtensionError("`icon_links` must be a list of dictionaries, you provided "f"type {type(theme_options.get('icon_links'))}.")# Set the anchor link default to be # if the user hasn't provided their ownifnotutils.config_provided_by_user(app,"html_permalinks_icon"):app.config.html_permalinks_icon="#"# check the validity of the theme switcher fileis_dict=isinstance(theme_options.get("switcher"),dict)should_test=theme_options.get("check_switcher",True)ifis_dictandshould_test:theme_switcher=theme_options.get("switcher")# raise an error if one of these compulsory keys is missingjson_url=theme_switcher["json_url"]theme_switcher["version_match"]# try to read the json file. If it's a url we use request,# else we simply read the local file from the source directory# display a log warning if the file cannot be reachedreading_error=Noneifurlparse(json_url).schemein["http","https"]:try:request=requests.get(json_url)request.raise_for_status()content=request.textexcept(ConnectionError,HTTPError,RetryError)ase:reading_error=repr(e)else:try:content=Path(app.srcdir,json_url).read_text()exceptFileNotFoundErrorase:reading_error=repr(e)ifreading_errorisnotNone:warning(f'The version switcher "{json_url}" file cannot be read due to 'f"the following error:\n{reading_error}")else:# check that the json file is not illformed,# throw a warning if the file is ill formed and an error if it's not jsonswitcher_content=json.loads(content)missing_url=any(["url"notineforeinswitcher_content])missing_version=any(["version"notineforeinswitcher_content])ifmissing_urlormissing_version:warning(f'The version switcher "{json_url}" file is malformed; ''at least one of the items is missing the "url" or "version" key')# Add an analytics ID to the site if providedanalytics=theme_options.get("analytics",{})ifanalytics:# Plausible analyticsplausible_domain=analytics.get("plausible_analytics_domain")plausible_url=analytics.get("plausible_analytics_url")# Ref: https://plausible.io/docs/plausible-scriptifplausible_domainandplausible_url:kwargs={"loading_method":"defer","data-domain":plausible_domain,"filename":plausible_url,}app.add_js_file(**kwargs)# Google Analyticsgid=analytics.get("google_analytics_id")ifgid:gid_js_path=f"https://www.googletagmanager.com/gtag/js?id={gid}"gid_script=f""" window.dataLayer = window.dataLayer || []; function gtag(){{ dataLayer.push(arguments); }} gtag('js', new Date()); gtag('config', '{gid}'); """# Link the JS filesapp.add_js_file(gid_js_path,loading_method="async")app.add_js_file(None,body=gid_script)# Update ABlog configuration default if presentfa_provided=utils.config_provided_by_user(app,"fontawesome_included")if"ablog"inapp.config.extensionsandnotfa_provided:app.config.fontawesome_included=True# Handle icon link shortcutsshortcuts=[("twitter_url","fa-brands fa-square-twitter","Twitter"),("bitbucket_url","fa-brands fa-bitbucket","Bitbucket"),("gitlab_url","fa-brands fa-square-gitlab","GitLab"),("github_url","fa-brands fa-square-github","GitHub"),]# Add extra icon links entries if there were shortcuts present# TODO: Deprecate this at some point in the future?icon_links=theme_options.get("icon_links",[])forurl,icon,nameinshortcuts:iftheme_options.get(url):# This defaults to an empty list so we can always inserticon_links.insert(0,{"url":theme_options.get(url),"icon":icon,"name":name,"type":"fontawesome",},)theme_options["icon_links"]=icon_links# Prepare the logo config dictionarytheme_logo=theme_options.get("logo")ifnottheme_logo:# In case theme_logo is an empty stringtheme_logo={}ifnotisinstance(theme_logo,dict):raiseValueError(f"Incorrect logo config type: {type(theme_logo)}")theme_logo_link=theme_options.get("theme_logo_link")iftheme_logo_link:theme_logo["link"]=theme_logo_linktheme_options["logo"]=theme_logo
[文档]defupdate_and_remove_templates(app:Sphinx,pagename:str,templatename:str,context,doctree)->None:"""Update template names and assets for page build."""# Allow for more flexibility in template namestemplate_sections=["theme_navbar_start","theme_navbar_center","theme_navbar_persistent","theme_navbar_end","theme_article_header_start","theme_article_header_end","theme_article_footer_items","theme_content_footer_items","theme_footer_start","theme_footer_center","theme_footer_end","theme_primary_sidebar_end","sidebars",]forsectionintemplate_sections:ifcontext.get(section):context[section]=utils._update_and_remove_templates(app=app,context=context,templates=context.get(section,[]),section=section,templates_skip_empty_check=["sidebar-nav-bs.html","navbar-nav.html"],)# Remove a duplicate entry of the theme CSS. This is because it is in both:# - theme.conf# - manually linked in `webpack-macros.html`if"css_files"incontext:theme_css_name="_static/styles/pydata-sphinx-theme.css"foriinrange(len(context["css_files"])):asset=context["css_files"][i]# TODO: eventually the contents of context['css_files'] etc should probably# only be _CascadingStyleSheet etc. For now, assume mixed with strings.asset_path=getattr(asset,"filename",str(asset))ifasset_path==theme_css_name:delcontext["css_files"][i]break# Add links for favicons in the topbarforfaviconincontext.get("theme_favicons",[]):icon_type=Path(favicon["href"]).suffix.strip(".")opts={"rel":favicon.get("rel","icon"),"sizes":favicon.get("sizes","16x16"),"type":f"image/{icon_type}",}if"color"infavicon:opts["color"]=favicon["color"]# Sphinx will auto-resolve href if it's a local fileapp.add_css_file(favicon["href"],**opts)# Add metadata to DOCUMENTATION_OPTIONS so that we can re-use later# Pagename to current pageapp.add_js_file(None,body=f"DOCUMENTATION_OPTIONS.pagename = '{pagename}';")ifisinstance(context.get("theme_switcher"),dict):theme_switcher=context["theme_switcher"]json_url=theme_switcher["json_url"]version_match=theme_switcher["version_match"]# Add variables to our JavaScript for re-use in our main JS scriptjs=f""" DOCUMENTATION_OPTIONS.theme_version = '{__version__}'; DOCUMENTATION_OPTIONS.theme_switcher_json_url = '{json_url}'; DOCUMENTATION_OPTIONS.theme_switcher_version_match = '{version_match}'; DOCUMENTATION_OPTIONS.show_version_warning_banner ={str(context["theme_show_version_warning_banner"]).lower()}; """app.add_js_file(None,body=js)# Update version number for the "made with version..." componentcontext["theme_version"]=__version__
[文档]def_fix_canonical_url(app:Sphinx,pagename:str,templatename:str,context:dict,doctree)->None:"""Fix the canonical URL when using the dirhtml builder. Sphinx builds a canonical URL if ``html_baseurl`` config is set. However, it builds a URL ending with ".html" when using the dirhtml builder, which is incorrect. Detect this and generate the correct URL for each page. Workaround for https://github.com/sphinx-doc/sphinx/issues/9730; can be removed when that is fixed, released, and available in our minimum supported Sphinx version. """if(notapp.config.html_baseurlornotisinstance(app.builder,DirectoryHTMLBuilder)ornotcontext["pageurl"]ornotcontext["pageurl"].endswith(".html")):returntarget=app.builder.get_target_uri(pagename)context["pageurl"]=app.config.html_baseurl+target
[文档]defsetup(app:Sphinx)->Dict[str,str]:"""Setup the Sphinx application."""here=Path(__file__).parent.resolve()theme_path=here/"theme"/"pydata_sphinx_theme"app.add_html_theme("pydata_sphinx_theme",str(theme_path))app.add_post_transform(short_link.ShortenLinkTransform)app.connect("builder-inited",translator.setup_translators)app.connect("builder-inited",update_config)app.connect("html-page-context",_fix_canonical_url)app.connect("html-page-context",edit_this_page.setup_edit_url)app.connect("html-page-context",toctree.add_toctree_functions)app.connect("html-page-context",update_and_remove_templates)app.connect("html-page-context",logo.setup_logo_path)app.connect("html-page-context",utils.set_secondary_sidebar_items)app.connect("build-finished",pygments.overwrite_pygments_css)app.connect("build-finished",logo.copy_logo_images)# https://www.sphinx-doc.org/en/master/extdev/i18n.html#extension-internationalization-i18n-and-localization-l10n-using-i18n-apiapp.add_message_catalog("sphinx",here/"locale")# Include component templatesapp.config.templates_path.append(str(theme_path/"components"))return{"parallel_read_safe":True,"parallel_write_safe":True}