update package and add myst extensions , update convert2md script

This commit is contained in:
hoangvv 2025-02-28 00:14:45 +07:00
parent 637a4dd9fd
commit fb57c796ee
3 changed files with 447 additions and 228 deletions

524
conf.py
View File

@ -13,33 +13,33 @@ from sphinx.util import logging
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
#=== General configuration ===# # === General configuration ===#
# General information about the project. # General information about the project.
project = 'Odoo' project = "Odoo"
copyright = 'Odoo S.A.' copyright = "Odoo S.A."
# `version` is the version info for the project being documented, acts as replacement for |version|, # `version` is the version info for the project being documented, acts as replacement for |version|,
# also used in various other places throughout the built documents. # also used in various other places throughout the built documents.
# `release` is the full version, including alpha/beta/rc tags. Acts as replacement for |release|. # `release` is the full version, including alpha/beta/rc tags. Acts as replacement for |release|.
version = release = '18.0' version = release = "18.0"
# `current_branch` is the technical name of the current branch. # `current_branch` is the technical name of the current branch.
# E.g., saas-15.4 -> saas-15.4; 12.0 -> 12.0, master -> master (*). # E.g., saas-15.4 -> saas-15.4; 12.0 -> 12.0, master -> master (*).
current_branch = version current_branch = version
# `current_version` is the Odoo version linked to the current branch. # `current_version` is the Odoo version linked to the current branch.
# E.g., saas-15.4 -> 15.4; 12.0 -> 12; master -> master (*). # E.g., saas-15.4 -> 15.4; 12.0 -> 12; master -> master (*).
current_version = current_branch.replace('saas-', '').replace('.0', '') current_version = current_branch.replace("saas-", "").replace(".0", "")
# `current_major_branch` is the technical name of the major branch before the current branch. # `current_major_branch` is the technical name of the major branch before the current branch.
# E.g., saas-15.4 -> 15.0; 12.0 -> 12.0; master -> master (*). # E.g., saas-15.4 -> 15.0; 12.0 -> 12.0; master -> master (*).
current_major_branch = re.sub(r'\.\d', '.0', current_branch.replace('saas-', '')) current_major_branch = re.sub(r"\.\d", ".0", current_branch.replace("saas-", ""))
# `current_major_version` is the Odoo version linked to the current major branch. # `current_major_version` is the Odoo version linked to the current major branch.
# E.g., saas-15.4 -> 15; 12.0 -> 12; master -> master (*). # E.g., saas-15.4 -> 15; 12.0 -> 12; master -> master (*).
current_major_version = current_major_branch.replace('.0', '') current_major_version = current_major_branch.replace(".0", "")
# (*): We don't care for master. # (*): We don't care for master.
# The minimal Sphinx version required to build the documentation. # The minimal Sphinx version required to build the documentation.
needs_sphinx = '3.0.0' needs_sphinx = "3.0.0"
# The default language in which the documentation is written. It is set to `None` because Sphinx # The default language in which the documentation is written. It is set to `None` because Sphinx
# considers that no language means 'en'. # considers that no language means 'en'.
@ -47,26 +47,28 @@ language = None
# The suffix of source filenames. # The suffix of source filenames.
source_suffix = { source_suffix = {
'.rst': 'restructuredtext', ".rst": "restructuredtext",
'.md': 'markdown', ".md": "markdown",
} }
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = "index"
# List of patterns, relative to source directory, that match files and directories to ignore when # List of patterns, relative to source directory, that match files and directories to ignore when
# looking for source files. # looking for source files.
exclude_patterns = [ exclude_patterns = [
'locale', "locale",
'README.*', "README.*",
'bin', 'include', 'lib', "bin",
'odoo', "include",
"lib",
"odoo",
] ]
# The RST text role to use when the role is not specified. E.g.: `example`. # The RST text role to use when the role is not specified. E.g.: `example`.
# We use 'literal' as default role for markdown compatibility: `foo` behaves like ``foo``. # We use 'literal' as default role for markdown compatibility: `foo` behaves like ``foo``.
# See https://docutils.sourceforge.io/docs/ref/rst/roles.html#standard-roles for other roles. # See https://docutils.sourceforge.io/docs/ref/rst/roles.html#standard-roles for other roles.
default_role = 'literal' default_role = "literal"
html_copy_source = False html_copy_source = False
# Whether scaled down images should be be wrapped in a `<a/>` tag linking to the image file or not. # Whether scaled down images should be be wrapped in a `<a/>` tag linking to the image file or not.
@ -75,28 +77,28 @@ html_scaled_image_link = False
# If true, '()' will be appended to :func: etc. cross-reference text # If true, '()' will be appended to :func: etc. cross-reference text
add_function_parentheses = True add_function_parentheses = True
#=== Extensions configuration ===# # === Extensions configuration ===#
source_read_replace_vals = { source_read_replace_vals = {
'BRANCH': current_branch, "BRANCH": current_branch,
'CURRENT_BRANCH': current_branch, "CURRENT_BRANCH": current_branch,
'CURRENT_VERSION': current_version, "CURRENT_VERSION": current_version,
'CURRENT_MAJOR_BRANCH': current_major_branch, "CURRENT_MAJOR_BRANCH": current_major_branch,
'CURRENT_MAJOR_VERSION': current_major_version, "CURRENT_MAJOR_VERSION": current_major_version,
'GITHUB_PATH': f'https://github.com/odoo/odoo/blob/{version}', "GITHUB_PATH": f"https://github.com/odoo/odoo/blob/{version}",
'GITHUB_ENT_PATH': f'https://github.com/odoo/enterprise/blob/{version}', "GITHUB_ENT_PATH": f"https://github.com/odoo/enterprise/blob/{version}",
'OWL_PATH': f'https://github.com/odoo/owl/blob/master', "OWL_PATH": f"https://github.com/odoo/owl/blob/master",
} }
# Add extensions directory to PYTHONPATH # Add extensions directory to PYTHONPATH
extension_dir = Path('extensions') extension_dir = Path("extensions")
sys.path.insert(0, str(extension_dir.absolute())) sys.path.insert(0, str(extension_dir.absolute()))
# Search for the directory of odoo sources to know whether autodoc should be used on the dev doc # Search for the directory of odoo sources to know whether autodoc should be used on the dev doc
# odoo_sources_candidate_dirs = (Path('Odoo18'), Path('../Odoo18')) odoo_sources_candidate_dirs = (Path("Odoo18"), Path("../Odoo18"))
odoo_sources_candidate_dirs = (Path('odoo'), Path('../odoo')) # odoo_sources_candidate_dirs = (Path('odoo'), Path('../odoo'))
odoo_sources_dirs = [ odoo_sources_dirs = [
d for d in odoo_sources_candidate_dirs if d.is_dir() and (d / 'odoo-bin').exists() d for d in odoo_sources_candidate_dirs if d.is_dir() and (d / "odoo-bin").exists()
] ]
odoo_dir_in_path = False odoo_dir_in_path = False
@ -107,20 +109,30 @@ if not odoo_sources_dirs:
"The 'Developer' documentation will be built but autodoc directives will be skipped.\n" "The 'Developer' documentation will be built but autodoc directives will be skipped.\n"
"In order to fully build the 'Developer' documentation, clone the repository with " "In order to fully build the 'Developer' documentation, clone the repository with "
"`git clone https://github.com/odoo/odoo` or create a symbolic link.", "`git clone https://github.com/odoo/odoo` or create a symbolic link.",
{'dir_list': '\n'.join([f'\t- {d.resolve()}' for d in odoo_sources_candidate_dirs])}, {
"dir_list": "\n".join(
[f"\t- {d.resolve()}" for d in odoo_sources_candidate_dirs]
)
},
) )
else: else:
if (3, 6) < sys.version_info < (3, 7): if (3, 6) < sys.version_info < (3, 7):
# Running odoo needs python 3.7 min but monkey patch version_info to be compatible with 3.6. # Running odoo needs python 3.7 min but monkey patch version_info to be compatible with 3.6.
sys.version_info = (3, 7, 0) sys.version_info = (3, 7, 0)
odoo_dir = odoo_sources_dirs[0].resolve() odoo_dir = odoo_sources_dirs[0].resolve()
source_read_replace_vals['ODOO_RELPATH'] = '/../' + str(odoo_sources_dirs[0]) source_read_replace_vals["ODOO_RELPATH"] = "/../" + str(odoo_sources_dirs[0])
sys.path.insert(0, str(odoo_dir)) sys.path.insert(0, str(odoo_dir))
import odoo.addons import odoo.addons
odoo.addons.__path__.append(str(odoo_dir) + '/addons')
from odoo import release as odoo_release # Don't collide with Sphinx's 'release' config option odoo.addons.__path__.append(str(odoo_dir) + "/addons")
odoo_version = '.'.join(str(s) for s in odoo_release.version_info[:2]).replace('~', '-') # Change saas~XX.Y to saas-XX.Y from odoo import (
odoo_version = 'master' if 'alpha' in odoo_release.version else odoo_version release as odoo_release,
) # Don't collide with Sphinx's 'release' config option
odoo_version = ".".join(str(s) for s in odoo_release.version_info[:2]).replace(
"~", "-"
) # Change saas~XX.Y to saas-XX.Y
odoo_version = "master" if "alpha" in odoo_release.version else odoo_version
if release != odoo_version: if release != odoo_version:
_logger.warning( _logger.warning(
"Found Odoo sources in %(directory)s but with version '%(odoo_version)s' incompatible " "Found Odoo sources in %(directory)s but with version '%(odoo_version)s' incompatible "
@ -128,17 +140,23 @@ else:
"The 'Developer' documentation will be built but autodoc directives will be skipped.\n" "The 'Developer' documentation will be built but autodoc directives will be skipped.\n"
"In order to fully build the 'Developer' documentation, checkout the matching branch" "In order to fully build the 'Developer' documentation, checkout the matching branch"
" with `cd odoo && git checkout %(doc_version)s`.", " with `cd odoo && git checkout %(doc_version)s`.",
{'directory': odoo_dir, 'odoo_version': odoo_version, 'doc_version': version}, {
"directory": odoo_dir,
"odoo_version": odoo_version,
"doc_version": version,
},
) )
else: else:
_logger.info( _logger.info(
"Found Odoo sources in %(directory)s matching documentation version '%(version)s'.", "Found Odoo sources in %(directory)s matching documentation version '%(version)s'.",
{'directory': odoo_dir, 'version': release}, {"directory": odoo_dir, "version": release},
) )
odoo_dir_in_path = True odoo_dir_in_path = True
if odoo_dir_in_path: if odoo_dir_in_path:
upgrade_util_dir = next(filter(Path.exists, [Path('upgrade-util'), Path('../upgrade-util')]), None) upgrade_util_dir = next(
filter(Path.exists, [Path("upgrade-util"), Path("../upgrade-util")]), None
)
if not upgrade_util_dir: if not upgrade_util_dir:
_logger.warning( _logger.warning(
"Could not find Upgrade Utils sources directory in `upgrade_util`.\n" "Could not find Upgrade Utils sources directory in `upgrade_util`.\n"
@ -150,153 +168,165 @@ if odoo_dir_in_path:
else: else:
_logger.info( _logger.info(
"Found Upgrade Util sources in %(directory)s", "Found Upgrade Util sources in %(directory)s",
{'directory': upgrade_util_dir.resolve()}, {"directory": upgrade_util_dir.resolve()},
) )
from odoo import upgrade from odoo import upgrade
upgrade.__path__.append(str((upgrade_util_dir / 'src').resolve()))
upgrade.__path__.append(str((upgrade_util_dir / "src").resolve()))
# Mapping between odoo models related to master data and the declaration of the # Mapping between odoo models related to master data and the declaration of the
# data. This is used to point users to available xml_ids when giving values for # data. This is used to point users to available xml_ids when giving values for
# a field with the autodoc_field extension. # a field with the autodoc_field extension.
model_references = { model_references = {
'account.account.type': 'addons/account/data/data_account_type.xml', "account.account.type": "addons/account/data/data_account_type.xml",
'res.country': 'odoo/addons/base/data/res_country_data.xml', "res.country": "odoo/addons/base/data/res_country_data.xml",
'res.currency': 'odoo/addons/base/data/res_currency_data.xml', "res.currency": "odoo/addons/base/data/res_currency_data.xml",
} }
# The Sphinx extensions to use, as module names. # The Sphinx extensions to use, as module names.
# They can be extensions coming with Sphinx (named 'sphinx.ext.*') or custom ones. # They can be extensions coming with Sphinx (named 'sphinx.ext.*') or custom ones.
extensions = [ extensions = [
# Link sources in other projects (used to build the reference doc) # Link sources in other projects (used to build the reference doc)
'sphinx.ext.intersphinx', "sphinx.ext.intersphinx",
# Support the specialized to-do directives # Support the specialized to-do directives
'sphinx.ext.todo', "sphinx.ext.todo",
# Custom Odoo theme # Custom Odoo theme
'odoo_theme', "odoo_theme",
# Youtube and Vimeo videos integration (youtube, vimeo directives) # Youtube and Vimeo videos integration (youtube, vimeo directives)
'embedded_video', "embedded_video",
"custom_admonitions",
'custom_admonitions',
# Redirection generator # Redirection generator
'redirects', "redirects",
# Content tabs # Content tabs
'sphinx_tabs.tabs', "sphinx_tabs.tabs",
# Cards # Cards
'cards', "cards",
# Spoilers # Spoilers
'spoilers', "spoilers",
# Strange html domain logic used in memento pages # Strange html domain logic used in memento pages
'html_domain', "html_domain",
"myst_parser",
'myst_parser',
] ]
myst_enable_extensions = [
"amsmath",
"colon_fence",
"deflist",
"dollarmath",
"fieldlist",
"html_admonition",
"html_image",
"linkify",
"replacements",
"smartquotes",
"strikethrough",
"substitution",
"tasklist",
]
if odoo_dir_in_path: if odoo_dir_in_path:
# GitHub links generation # GitHub links generation
extensions += [ extensions += [
'sphinx.ext.linkcode', "sphinx.ext.linkcode",
'github_link', "github_link",
# Parse Python docstrings (autodoc, automodule, autoattribute directives) # Parse Python docstrings (autodoc, automodule, autoattribute directives)
'sphinx.ext.autodoc', "sphinx.ext.autodoc",
'autodoc_field', "autodoc_field",
] ]
else: else:
extensions += [ extensions += [
'autodoc_placeholder', "autodoc_placeholder",
] ]
extensions.append('sphinx.ext.graphviz' if shutil.which('dot') else 'graphviz_placeholder') extensions.append(
"sphinx.ext.graphviz" if shutil.which("dot") else "graphviz_placeholder"
)
todo_include_todos = False todo_include_todos = False
intersphinx_mapping = { intersphinx_mapping = {
'pillow': ('https://pillow.readthedocs.io/en/stable/', None), "pillow": ("https://pillow.readthedocs.io/en/stable/", None),
'python': ('https://docs.python.org/3/', None), "python": ("https://docs.python.org/3/", None),
'werkzeug': ('https://werkzeug.palletsprojects.com/en/2.3.x/', None), "werkzeug": ("https://werkzeug.palletsprojects.com/en/2.3.x/", None),
} }
github_user = 'odoo' github_user = "odoo"
github_project = 'documentation' github_project = "documentation"
locale_dirs = ['../locale/'] locale_dirs = ["../locale/"]
templates_path = ['../extensions'] templates_path = ["../extensions"]
# custom docname_to_domain to divide the translations of applications in subdirectories # custom docname_to_domain to divide the translations of applications in subdirectories
sphinx.transforms.i18n.docname_to_domain = ( sphinx.transforms.i18n.docname_to_domain = sphinx.util.i18n.docname_to_domain = (
sphinx.util.i18n.docname_to_domain lambda docname, compact: docname.split("/")[
) = lambda docname, compact: docname.split('/')[1 if docname.startswith('applications/') else 0] 1 if docname.startswith("applications/") else 0
]
)
# The version names that should be shown in the version switcher, if the config option `versions` # The version names that should be shown in the version switcher, if the config option `versions`
# is populated. If a version is passed to `versions` but is not listed here, it will not be shown. # is populated. If a version is passed to `versions` but is not listed here, it will not be shown.
versions_names = { versions_names = {
'master': "Master", "master": "Master",
'saas-18.1': "Odoo Online", "saas-18.1": "Odoo Online",
'18.0': "18.0", "18.0": "18.0",
'saas-17.4': "Odoo Online", "saas-17.4": "Odoo Online",
'saas-17.2': "Odoo Online", "saas-17.2": "Odoo Online",
'17.0': "Odoo 17", "17.0": "Odoo 17",
'16.0': "Odoo 16", "16.0": "Odoo 16",
'15.0': "Odoo 15", "15.0": "Odoo 15",
} }
# The language names that should be shown in the language switcher, if the config option `languages` # The language names that should be shown in the language switcher, if the config option `languages`
# is populated. If a language is passed to `languages` but is not listed here, it will not be shown. # is populated. If a language is passed to `languages` but is not listed here, it will not be shown.
languages_names = { languages_names = {
'de': 'DE', "de": "DE",
'en': 'EN', "en": "EN",
'es': 'ES', "es": "ES",
'es_419': 'ES (LATAM)', "es_419": "ES (LATAM)",
'fr': 'FR', "fr": "FR",
'id': 'ID', "id": "ID",
'it': 'IT', "it": "IT",
'ja': 'JA', "ja": "JA",
'ko': 'KR', "ko": "KR",
'nl': 'NL', "nl": "NL",
'pt_BR': 'PT', "pt_BR": "PT",
'ro': 'RO', "ro": "RO",
'sv': 'SV', "sv": "SV",
'th': 'TH', "th": "TH",
'uk': 'UA', "uk": "UA",
'vi': 'VI', "vi": "VI",
'zh_CN': 'ZH (CN)', "zh_CN": "ZH (CN)",
'zh_TW': 'ZH (TW)' "zh_TW": "ZH (TW)",
} }
# The directory in which files holding redirect rules used by the 'redirects' extension are listed. # The directory in which files holding redirect rules used by the 'redirects' extension are listed.
redirects_dir = 'redirects/' redirects_dir = "redirects/"
sphinx_tabs_disable_tab_closing = True sphinx_tabs_disable_tab_closing = True
sphinx_tabs_disable_css_loading = True sphinx_tabs_disable_css_loading = True
# Autodoc ordering # Autodoc ordering
autodoc_member_order = 'bysource' autodoc_member_order = "bysource"
#=== Options for HTML output ===# # === Options for HTML output ===#
html_theme = 'odoo_theme' html_theme = "odoo_theme"
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
# See extensions/odoo_theme/pygments_override.py # See extensions/odoo_theme/pygments_override.py
pygments_style = 'odoo' pygments_style = "odoo"
# The paths that contain custom themes, relative to this directory. # The paths that contain custom themes, relative to this directory.
html_theme_path = ['extensions'] html_theme_path = ["extensions"]
# The name of an image file (within the static path) to use as favicon of the docs. # The name of an image file (within the static path) to use as favicon of the docs.
# This file should be a Windows icon file (.ico) being 16x16 or 32x32 pixels large. # This file should be a Windows icon file (.ico) being 16x16 or 32x32 pixels large.
html_favicon = os.path.join(html_theme_path[0], html_theme, 'static', 'img', 'favicon.ico') html_favicon = os.path.join(
html_theme_path[0], html_theme, "static", "img", "favicon.ico"
)
# The paths that contain custom static files, relative to this directory. # The paths that contain custom static files, relative to this directory.
# They are copied after the builtin static files, so a file named "default.css" will overwrite the # They are copied after the builtin static files, so a file named "default.css" will overwrite the
# builtin "default.css". # builtin "default.css".
html_static_path = ['static'] html_static_path = ["static"]
html_permalinks = True html_permalinks = True
# Additional JS & CSS files that can be imported with the 'custom-js' and 'custom-css' metadata. # Additional JS & CSS files that can be imported with the 'custom-js' and 'custom-css' metadata.
@ -306,72 +336,131 @@ html_css_files = []
# PHP lexer option to not require <?php # PHP lexer option to not require <?php
highlight_options = { highlight_options = {
'php': {'startinline': True}, "php": {"startinline": True},
} }
#=== Options for LaTeX output ===# # === Options for LaTeX output ===#
latex_elements = { latex_elements = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
'papersize': 'a4paper', "papersize": "a4paper",
# Additional stuff for the LaTeX preamble. # Additional stuff for the LaTeX preamble.
'preamble': r'\usepackage{odoo}', "preamble": r"\usepackage{odoo}",
'tableofcontents': '', # no TOC "tableofcontents": "", # no TOC
# Output manually in latex docs # Output manually in latex docs
'releasename': release, "releasename": release,
} }
latex_additional_files = ['static/latex/odoo.sty'] latex_additional_files = ["static/latex/odoo.sty"]
# Grouping the document tree into LaTeX files. List of tuples: # Grouping the document tree into LaTeX files. List of tuples:
# (source start file, target name, title, author, documentclass [howto, manual, or own class]). # (source start file, target name, title, author, documentclass [howto, manual, or own class]).
latex_documents = [ latex_documents = [
('legal/terms/enterprise_tex', 'odoo_enterprise_agreement.tex', (
'Odoo Enterprise Subscription Agreement', '', 'howto'), "legal/terms/enterprise_tex",
('legal/terms/partnership_tex', "odoo_enterprise_agreement.tex",
'odoo_partnership_agreement.tex', 'Odoo Partnership Agreement', '', 'howto'), "Odoo Enterprise Subscription Agreement",
('legal/terms/terms_of_sale', "",
'terms_of_sale.tex', 'Odoo Terms of Sale', '', 'howto'), "howto",
),
('legal/terms/i18n/enterprise_tex_fr', 'odoo_enterprise_agreement_fr.tex', (
'Odoo Enterprise Subscription Agreement (FR)', '', 'howto'), "legal/terms/partnership_tex",
('legal/terms/i18n/partnership_tex_fr', "odoo_partnership_agreement.tex",
'odoo_partnership_agreement_fr.tex', 'Odoo Partnership Agreement (FR)', '', 'howto'), "Odoo Partnership Agreement",
('legal/terms/i18n/terms_of_sale_fr', 'terms_of_sale_fr.tex', "",
'Conditions Générales de Vente Odoo', '', 'howto'), "howto",
),
('legal/terms/i18n/enterprise_tex_nl', 'odoo_enterprise_agreement_nl.tex', (
'Odoo Enterprise Subscription Agreement (NL)', '', 'howto'), "legal/terms/terms_of_sale",
"terms_of_sale.tex",
('legal/terms/i18n/enterprise_tex_de', 'odoo_enterprise_agreement_de.tex', "Odoo Terms of Sale",
'Odoo Enterprise Subscription Agreement (DE)', '', 'howto'), "",
('legal/terms/i18n/terms_of_sale_de', 'terms_of_sale_de.tex', "howto",
'Allgemeine Verkaufsbedingungen Odoo', '', 'howto'), ),
(
('legal/terms/i18n/enterprise_tex_es', 'odoo_enterprise_agreement_es.tex', "legal/terms/i18n/enterprise_tex_fr",
'Odoo Enterprise Subscription Agreement (ES)', '', 'howto'), "odoo_enterprise_agreement_fr.tex",
('legal/terms/i18n/partnership_tex_es', "Odoo Enterprise Subscription Agreement (FR)",
'odoo_partnership_agreement_es.tex', 'Odoo Partnership Agreement (ES)', '', 'howto'), "",
('legal/terms/i18n/terms_of_sale_es', 'terms_of_sale_es.tex', "howto",
'Términos Generales de Venta Odoo', '', 'howto'), ),
(
('legal/terms/i18n/enterprise_tex_pt_BR', 'odoo_enterprise_agreement_pt_BR.tex', "legal/terms/i18n/partnership_tex_fr",
'Odoo Enterprise Subscription Agreement (PT)', '', 'howto'), "odoo_partnership_agreement_fr.tex",
"Odoo Partnership Agreement (FR)",
"",
"howto",
),
(
"legal/terms/i18n/terms_of_sale_fr",
"terms_of_sale_fr.tex",
"Conditions Générales de Vente Odoo",
"",
"howto",
),
(
"legal/terms/i18n/enterprise_tex_nl",
"odoo_enterprise_agreement_nl.tex",
"Odoo Enterprise Subscription Agreement (NL)",
"",
"howto",
),
(
"legal/terms/i18n/enterprise_tex_de",
"odoo_enterprise_agreement_de.tex",
"Odoo Enterprise Subscription Agreement (DE)",
"",
"howto",
),
(
"legal/terms/i18n/terms_of_sale_de",
"terms_of_sale_de.tex",
"Allgemeine Verkaufsbedingungen Odoo",
"",
"howto",
),
(
"legal/terms/i18n/enterprise_tex_es",
"odoo_enterprise_agreement_es.tex",
"Odoo Enterprise Subscription Agreement (ES)",
"",
"howto",
),
(
"legal/terms/i18n/partnership_tex_es",
"odoo_partnership_agreement_es.tex",
"Odoo Partnership Agreement (ES)",
"",
"howto",
),
(
"legal/terms/i18n/terms_of_sale_es",
"terms_of_sale_es.tex",
"Términos Generales de Venta Odoo",
"",
"howto",
),
(
"legal/terms/i18n/enterprise_tex_pt_BR",
"odoo_enterprise_agreement_pt_BR.tex",
"Odoo Enterprise Subscription Agreement (PT)",
"",
"howto",
),
] ]
# List of languages that have legal translations (excluding EN). The keys must be in # List of languages that have legal translations (excluding EN). The keys must be in
# `languages_names`. These translations will have a link to their versions of the legal # `languages_names`. These translations will have a link to their versions of the legal
# contracts, instead of the default EN one. The main legal documents are not part of the # contracts, instead of the default EN one. The main legal documents are not part of the
# translations since they have legal meaning. # translations since they have legal meaning.
legal_translations = ['de', 'es', 'fr', 'nl', 'pt_BR'] legal_translations = ["de", "es", "fr", "nl", "pt_BR"]
# The name of an image file (relative to this directory) to place at the top of the title page. # The name of an image file (relative to this directory) to place at the top of the title page.
latex_logo = 'static/img/odoo_logo.png' latex_logo = "static/img/odoo_logo.png"
# If true, show URL addresses after external links. # If true, show URL addresses after external links.
latex_show_urls = 'True' latex_show_urls = "True"
# https://github.com/sphinx-doc/sphinx/issues/4054#issuecomment-329097229 # https://github.com/sphinx-doc/sphinx/issues/4054#issuecomment-329097229
def source_read_replace(app, docname, source): def source_read_replace(app, docname, source):
@ -389,36 +478,42 @@ def source_read_replace(app, docname, source):
result = result.replace(f"{{{key}}}", app.config.source_read_replace_vals[key]) result = result.replace(f"{{{key}}}", app.config.source_read_replace_vals[key])
source[0] = result source[0] = result
def setup(app): def setup(app):
# Generate all alternate URLs for each document # Generate all alternate URLs for each document
app.add_config_value('project_root', None, 'env') app.add_config_value("project_root", None, "env")
app.add_config_value('canonical_version', None, 'env') app.add_config_value("canonical_version", None, "env")
app.add_config_value('versions', None, 'env') app.add_config_value("versions", None, "env")
app.add_config_value('languages', None, 'env') app.add_config_value("languages", None, "env")
app.add_config_value('is_remote_build', None, 'env') # Whether the build is remotely deployed app.add_config_value(
app.add_config_value('source_read_replace_vals', {}, 'env') "is_remote_build", None, "env"
app.connect('source-read', source_read_replace) ) # Whether the build is remotely deployed
app.add_config_value("source_read_replace_vals", {}, "env")
app.connect("source-read", source_read_replace)
# TODO uncomment after moving to >= v7.2.5 to also substitute placeholders in included files. # TODO uncomment after moving to >= v7.2.5 to also substitute placeholders in included files.
# See https://github.com/sphinx-doc/sphinx/commit/ff1831 # See https://github.com/sphinx-doc/sphinx/commit/ff1831
# app.connect('include-read', source_read_replace) # app.connect('include-read', source_read_replace)
app.add_lexer('json', JsonLexer) app.add_lexer("json", JsonLexer)
app.add_lexer('xml', XmlLexer) app.add_lexer("xml", XmlLexer)
app.connect('html-page-context', _generate_alternate_urls) app.connect("html-page-context", _generate_alternate_urls)
# Add a `condition` option on directives to ignore them based on config values # Add a `condition` option on directives to ignore them based on config values
app.add_config_value('odoo_dir_in_path', None, 'env') app.add_config_value("odoo_dir_in_path", None, "env")
def context_eval(expr): def context_eval(expr):
return eval(expr, {confval.name: confval.value for confval in app.config}) return eval(expr, {confval.name: confval.value for confval in app.config})
def patch(to_patch): def patch(to_patch):
to_patch.option_spec['condition'] = context_eval to_patch.option_spec["condition"] = context_eval
original_run = to_patch.run original_run = to_patch.run
def new_run(self): def new_run(self):
if not self.options.get('condition', True): if not self.options.get("condition", True):
return [] return []
return original_run(self) return original_run(self)
to_patch.run = new_run to_patch.run = new_run
for to_patch in ( for to_patch in (
@ -429,7 +524,7 @@ def setup(app):
def _generate_alternate_urls(app, pagename, templatename, context, doctree): def _generate_alternate_urls(app, pagename, templatename, context, doctree):
""" Add keys of required alternate URLs for the current document in the rendering context. """Add keys of required alternate URLs for the current document in the rendering context.
Alternate URLS are required for: Alternate URLS are required for:
- The canonical link tag - The canonical link tag
@ -438,7 +533,7 @@ def _generate_alternate_urls(app, pagename, templatename, context, doctree):
""" """
def _canonicalize(): def _canonicalize():
""" Add the canonical URL for the current document in the rendering context. """Add the canonical URL for the current document in the rendering context.
The canonical version is the last released version of the documentation. The canonical version is the last released version of the documentation.
For a given language, the canonical root of a page is in the same language so that web For a given language, the canonical root of a page is in the same language so that web
@ -450,57 +545,74 @@ def _generate_alternate_urls(app, pagename, templatename, context, doctree):
""" """
# If the canonical version is not set, assume that the project has a single version # If the canonical version is not set, assume that the project has a single version
_canonical_version = app.config.canonical_version or app.config.version _canonical_version = app.config.canonical_version or app.config.version
_canonical_lang = 'en' # Always 'en'. Don't take the value of the config option. _canonical_lang = (
context['canonical'] = _build_url(_version=_canonical_version, _lang=_canonical_lang) "en" # Always 'en'. Don't take the value of the config option.
)
context["canonical"] = _build_url(
_version=_canonical_version, _lang=_canonical_lang
)
def _versionize(): def _versionize():
""" Add the pairs of (version, url) for the current document in the rendering context. """Add the pairs of (version, url) for the current document in the rendering context.
The entry 'version' is added by Sphinx in the rendering context. The entry 'version' is added by Sphinx in the rendering context.
""" """
context['version_display_name'] = versions_names[version] context["version_display_name"] = versions_names[version]
# If the list of versions is not set, assume the project has no alternate version # If the list of versions is not set, assume the project has no alternate version
_provided_versions = app.config.versions and app.config.versions.split(',') or [] _provided_versions = (
app.config.versions and app.config.versions.split(",") or []
)
# Map alternate versions to their display names and URLs. # Map alternate versions to their display names and URLs.
context['alternate_versions'] = [] context["alternate_versions"] = []
for _alternate_version, _display_name in versions_names.items(): for _alternate_version, _display_name in versions_names.items():
if _alternate_version in _provided_versions and _alternate_version != version: if (
context['alternate_versions'].append( _alternate_version in _provided_versions
and _alternate_version != version
):
context["alternate_versions"].append(
(_display_name, _build_url(_alternate_version)) (_display_name, _build_url(_alternate_version))
) )
def _localize(): def _localize():
""" Add the pairs of (lang, code, url) for the current document in the rendering context. """Add the pairs of (lang, code, url) for the current document in the rendering context.
E.g.: ('French', 'fr', 'https://.../fr_BE/...') E.g.: ('French', 'fr', 'https://.../fr_BE/...')
The entry 'language' is added by Sphinx in the rendering context. The entry 'language' is added by Sphinx in the rendering context.
""" """
_current_lang = app.config.language or 'en' _current_lang = app.config.language or "en"
# Replace the context value by its upper-cased value ("FR" instead of "fr") # Replace the context value by its upper-cased value ("FR" instead of "fr")
context['language'] = languages_names.get(_current_lang, _current_lang.upper()) context["language"] = languages_names.get(_current_lang, _current_lang.upper())
context['language_code'] = _current_lang context["language_code"] = _current_lang
# If the list of languages is not set, assume that the project has no alternate language # If the list of languages is not set, assume that the project has no alternate language
_provided_languages = app.config.languages and app.config.languages.split(',') or [] _provided_languages = (
app.config.languages and app.config.languages.split(",") or []
)
# Map alternate languages to their display names and URLs. # Map alternate languages to their display names and URLs.
context['alternate_languages'] = [] context["alternate_languages"] = []
for _alternate_lang, _display_name in languages_names.items(): for _alternate_lang, _display_name in languages_names.items():
if _alternate_lang in _provided_languages and _alternate_lang != _current_lang: if (
context['alternate_languages'].append( _alternate_lang in _provided_languages
and _alternate_lang != _current_lang
):
context["alternate_languages"].append(
( (
_display_name, _display_name,
_alternate_lang.split('_')[0] if _alternate_lang != 'en' else 'x-default', (
_alternate_lang.split("_")[0]
if _alternate_lang != "en"
else "x-default"
),
_build_url(_lang=_alternate_lang), _build_url(_lang=_alternate_lang),
) )
) )
# Dynamic generation of localized legal doc links # Dynamic generation of localized legal doc links
context['legal_translations'] = legal_translations context["legal_translations"] = legal_translations
def _build_url(_version=None, _lang=None): def _build_url(_version=None, _lang=None):
# print(f"###################################{app.config.is_remote_build}") # print(f"###################################{app.config.is_remote_build}")
@ -510,34 +622,40 @@ def _generate_alternate_urls(app, pagename, templatename, context, doctree):
_root = app.config.project_root _root = app.config.project_root
else: else:
# Project root like .../documentation/_build/html/14.0/fr # Project root like .../documentation/_build/html/14.0/fr
_root = re.sub(rf'(/{app.config.version})?(/{app.config.language})?$', '', app.outdir) _root = re.sub(
rf"(/{app.config.version})?(/{app.config.language})?$", "", app.outdir
)
# If the canonical version is not set, assume that the project has a single version # If the canonical version is not set, assume that the project has a single version
_canonical_version = app.config.canonical_version or app.config.version _canonical_version = app.config.canonical_version or app.config.version
_version = _version or app.config.version _version = _version or app.config.version
_lang = _lang or app.config.language or 'en' _lang = _lang or app.config.language or "en"
_canonical_page = f'{pagename}.html' _canonical_page = f"{pagename}.html"
# legal translations have different URLs schemes as they are not managed on transifex # legal translations have different URLs schemes as they are not managed on transifex
# e.g. FR translation of /terms/enterprise => /fr/terms/enterprise_fr # e.g. FR translation of /terms/enterprise => /fr/terms/enterprise_fr
if pagename.startswith('legal/terms/'): if pagename.startswith("legal/terms/"):
if _lang in legal_translations and not pagename.endswith(f"_{_lang}"): if _lang in legal_translations and not pagename.endswith(f"_{_lang}"):
# remove language code for current translation, set target one # remove language code for current translation, set target one
_page = re.sub("_[a-z]{2}$", "", pagename) _page = re.sub("_[a-z]{2}$", "", pagename)
if 'terms/i18n' not in _page: if "terms/i18n" not in _page:
_page = _page.replace("/terms/", "/terms/i18n/") _page = _page.replace("/terms/", "/terms/i18n/")
_canonical_page = f'{_page}_{_lang}.html' _canonical_page = f"{_page}_{_lang}.html"
elif _lang == 'en' and pagename.endswith(tuple(f"_{l}" for l in legal_translations)): elif _lang == "en" and pagename.endswith(
tuple(f"_{l}" for l in legal_translations)
):
# remove language code for current translation, link to original EN one # remove language code for current translation, link to original EN one
_page = re.sub("_[a-z]{2}$", "", pagename) _page = re.sub("_[a-z]{2}$", "", pagename)
_canonical_page = f'{_page.replace("/i18n/", "/")}.html' _canonical_page = f'{_page.replace("/i18n/", "/")}.html'
if app.config.is_remote_build: if app.config.is_remote_build:
_canonical_page = _canonical_page.replace('index.html', '') _canonical_page = _canonical_page.replace("index.html", "")
return f'{_root}' \ return (
f'{f"/{_version}" if app.config.versions else ""}' \ f"{_root}"
f'{f"/{_lang}" if _lang != "en" else ""}' \ f'{f"/{_version}" if app.config.versions else ""}'
f'/{_canonical_page}' f'{f"/{_lang}" if _lang != "en" else ""}'
f"/{_canonical_page}"
)
_canonicalize() _canonicalize()
_versionize() _versionize()

View File

@ -1,19 +1,15 @@
#!/bin/bash #!/bin/bash
# Enable stricter error handling # Enable stricter error handling
set -euo pipefail set -euo pipefail
# Check arguments # Check arguments
if [[ $# -ne 2 ]]; then if [[ $# -ne 2 ]]; then
echo "Usage: $0 <source_folder> <target_folder>" >&2 echo "Usage: $0 <source_folder> <target_folder>" >&2
exit 1 exit 1
fi fi
readonly SOURCE_DIR="$1" readonly SOURCE_DIR="$1"
readonly TARGET_DIR="$2" readonly TARGET_DIR="$2"
readonly TEMP_ERROR=$(mktemp) readonly TEMP_ERROR=$(mktemp)
readonly LOG_FILE="/tmp/convert2md_$$.log" readonly LOG_FILE="/tmp/convert2md_$$.log"
# Trap to clean up temp files on exit # Trap to clean up temp files on exit
trap 'rm -f "$TEMP_ERROR" "$LOG_FILE"' EXIT trap 'rm -f "$TEMP_ERROR" "$LOG_FILE"' EXIT
@ -23,12 +19,10 @@ validate_inputs() {
echo "Error: Source directory '$SOURCE_DIR' does not exist." >&2 echo "Error: Source directory '$SOURCE_DIR' does not exist." >&2
exit 1 exit 1
} }
mkdir -p "$TARGET_DIR" || { mkdir -p "$TARGET_DIR" || {
echo "Error: Could not create target directory '$TARGET_DIR'." >&2 echo "Error: Could not create target directory '$TARGET_DIR'." >&2
exit 1 exit 1
} }
command -v rst2myst >/dev/null 2>&1 || { command -v rst2myst >/dev/null 2>&1 || {
echo "Error: rst2myst is not installed. Install with 'pip install rst-to-myst'." >&2 echo "Error: rst2myst is not installed. Install with 'pip install rst-to-myst'." >&2
exit 1 exit 1
@ -42,18 +36,18 @@ process_rst_file() {
local md_file_name="${relative_path%.rst}.md" local md_file_name="${relative_path%.rst}.md"
local target_file="$TARGET_DIR/$md_file_name" local target_file="$TARGET_DIR/$md_file_name"
local target_dir=$(dirname "$target_file") local target_dir=$(dirname "$target_file")
mkdir -p "$target_dir" || { mkdir -p "$target_dir" || {
echo "Error: Could not create directory '$target_dir' for '$rst_file'" >&2 echo "Error: Could not create directory '$target_dir' for '$rst_file'" >&2
return 1 return 1
} }
if rst2myst stream "$rst_file" > "$target_file" 2>>"$TEMP_ERROR"; then if rst2myst stream "$rst_file" > "$target_file" 2>>"$TEMP_ERROR"; then
echo "Converted: $rst_file -> $target_file" | tee -a "$LOG_FILE" echo "Converted: $rst_file -> $target_file" | tee -a "$LOG_FILE"
echo "converted" >> "$TEMP_ERROR"
return 0 return 0
else else
echo "Failed to convert: $rst_file" >&2 echo "Failed to convert: $rst_file" >&2
cat "$TEMP_ERROR" >&2 cat "$TEMP_ERROR" >&2
echo "failed" >> "$TEMP_ERROR"
return 1 return 1
fi fi
} }
@ -64,7 +58,6 @@ copy_non_rst_file() {
local relative_path="${file#$SOURCE_DIR/}" local relative_path="${file#$SOURCE_DIR/}"
local target_file="$TARGET_DIR/$relative_path" local target_file="$TARGET_DIR/$relative_path"
local target_dir=$(dirname "$target_file") local target_dir=$(dirname "$target_file")
mkdir -p "$target_dir" && cp -p "$file" "$target_file" 2>/dev/null && { mkdir -p "$target_dir" && cp -p "$file" "$target_file" 2>/dev/null && {
echo "Copied: $file -> $target_file" | tee -a "$LOG_FILE" echo "Copied: $file -> $target_file" | tee -a "$LOG_FILE"
return 0 return 0
@ -77,36 +70,44 @@ copy_non_rst_file() {
main() { main() {
validate_inputs validate_inputs
echo "Starting conversion process..." | tee "$LOG_FILE" echo "Starting conversion process..." | tee "$LOG_FILE"
local rst_processed=0 rst_failed=0 copied=0 # Count total RST files
readonly TOTAL_RST_FILES=$(find "$SOURCE_DIR" -type f -name "*.rst" | wc -l)
echo "Found $TOTAL_RST_FILES RST files to process" | tee -a "$LOG_FILE"
local parallel_jobs=$(( $(nproc) / 2 )) # Use half the CPU cores to avoid overload local parallel_jobs=$(( $(nproc) / 2 )) # Use half the CPU cores to avoid overload
# Export functions for xargs # Export functions for xargs
export -f process_rst_file copy_non_rst_file export -f process_rst_file copy_non_rst_file
export SOURCE_DIR TARGET_DIR TEMP_ERROR LOG_FILE export SOURCE_DIR TARGET_DIR TEMP_ERROR LOG_FILE
# Process RST files with limited parallelism # Process RST files with limited parallelism
find "$SOURCE_DIR" -type f -name "*.rst" -print0 | xargs -0 -P "$parallel_jobs" -I {} bash -c 'process_rst_file "{}"' || { find "$SOURCE_DIR" -type f -name "*.rst" -print0 | xargs -0 -P "$parallel_jobs" -I {} bash -c 'process_rst_file "{}"' || {
echo "Warning: Some RST processing failed. Check $LOG_FILE for details." >&2 echo "Warning: Some RST processing failed. Check $LOG_FILE for details." >&2
} }
# Process non-RST files with limited parallelism # Process non-RST files with limited parallelism
find "$SOURCE_DIR" -type f ! -name "*.rst" -print0 | xargs -0 -P "$parallel_jobs" -I {} bash -c 'copy_non_rst_file "{}"' || { find "$SOURCE_DIR" -type f ! -name "*.rst" -print0 | xargs -0 -P "$parallel_jobs" -I {} bash -c 'copy_non_rst_file "{}"' || {
echo "Warning: Some file copies failed. Check $LOG_FILE for details." >&2 echo "Warning: Some file copies failed. Check $LOG_FILE for details." >&2
} }
# Count results from log # Count results from log
rst_processed=$(grep -c "^Converted:" "$LOG_FILE" || true) readonly SUCCESSFUL_CONVERSIONS=$(grep -c "^Converted:" "$LOG_FILE")
rst_failed=$(grep -c "^Failed to convert:" "$LOG_FILE" || true) readonly FAILED_CONVERSIONS=$(grep -c "^Failed to convert:" "$LOG_FILE")
copied=$(grep -c "^Copied:" "$LOG_FILE" || true)
# Summary # Summary
echo "Conversion and copy process complete." | tee -a "$LOG_FILE" echo "Conversion and copy process complete." | tee -a "$LOG_FILE"
echo "RST files processed successfully: $rst_processed" | tee -a "$LOG_FILE" echo "Total RST files found: $TOTAL_RST_FILES" | tee -a "$LOG_FILE"
echo "RST files failed: $rst_failed" | tee -a "$LOG_FILE" echo "RST files successfully converted: $SUCCESSFUL_CONVERSIONS" | tee -a "$LOG_FILE"
echo "Non-RST files copied: $copied" | tee -a "$LOG_FILE" echo "RST files failed to convert: $FAILED_CONVERSIONS" | tee -a "$LOG_FILE"
echo "Non-RST files copied: $(grep -c "^Copied:" "$LOG_FILE")" | tee -a "$LOG_FILE"
[[ $rst_failed -gt 0 ]] && {
# Check if all files were processed
if [[ $((TOTAL_RST_FILES)) != $((SUCCESSFUL_CONVERSIONS + FAILED_CONVERSIONS)) ]]; then
echo "Warning: Some RST files might not have been processed!" >&2
exit 1
fi
[[ $FAILED_CONVERSIONS -gt 0 ]] && {
echo "Note: Some RST conversions failed. See $LOG_FILE for details." >&2 echo "Note: Some RST conversions failed. See $LOG_FILE for details." >&2
exit 1 exit 1
} }

View File

@ -11,4 +11,104 @@ sphinxcontrib-qthelp==1.0.3
sphinx-tabs==3.4.5 # Compatibility with docutils==0.17.0 sphinx-tabs==3.4.5 # Compatibility with docutils==0.17.0
myst-parser myst-parser
rst-to-myst[sphinx] rst-to-myst[sphinx]
sphinx-autobuild sphinx-autobuild
# The officially supported versions of the following packages are their
# python3-* equivalent distributed in Ubuntu 24.04 and Debian 12
asn1crypto==1.4.0 ; python_version < '3.11'
asn1crypto==1.5.1 ; python_version >= '3.11'
Babel==2.9.1 ; python_version < '3.11' # min version = 2.6.0 (Focal with security backports)
Babel==2.10.3 ; python_version >= '3.11'
cbor2==5.4.2 ; python_version < '3.12'
cbor2==5.6.2 ; python_version >= '3.12'
chardet==4.0.0 ; python_version < '3.11' # (Jammy)
chardet==5.2.0 ; python_version >= '3.11'
cryptography==3.4.8; python_version < '3.12' # incompatibility between pyopenssl 19.0.0 and cryptography>=37.0.0
cryptography==42.0.8 ; python_version >= '3.12' # (Noble) min 41.0.7, pinning 42.0.8 for security fixes
decorator==4.4.2 ; python_version < '3.11' # (Jammy)
decorator==5.1.1 ; python_version >= '3.11'
freezegun==1.1.0 ; python_version < '3.11' # (Jammy)
freezegun==1.2.1 ; python_version >= '3.11'
geoip2==2.9.0
gevent==21.8.0 ; sys_platform != 'win32' and python_version == '3.10' # (Jammy)
gevent==22.10.2; sys_platform != 'win32' and python_version > '3.10' and python_version < '3.12'
gevent==24.2.1 ; sys_platform != 'win32' and python_version >= '3.12' # (Noble)
greenlet==1.1.2 ; sys_platform != 'win32' and python_version == '3.10' # (Jammy)
greenlet==2.0.2 ; sys_platform != 'win32' and python_version > '3.10' and python_version < '3.12'
greenlet==3.0.3 ; sys_platform != 'win32' and python_version >= '3.12' # (Noble)
idna==2.10 ; python_version < '3.12' # requests 2.25.1 depends on idna<3 and >=2.5
idna==3.6 ; python_version >= '3.12'
Jinja2==3.0.3 ; python_version <= '3.10'
Jinja2==3.1.2 ; python_version > '3.10' # (Noble) Mostly to have a wheel package
lxml==4.8.0 ; python_version <= '3.10'
lxml==4.9.3 ; python_version > '3.10' and python_version < '3.12' # min 4.9.2, pinning 4.9.3 because of missing wheels for darwin in 4.9.3
lxml==5.2.1; python_version >= '3.12' # (Noble - removed html clean)
lxml-html-clean; python_version >= '3.12' # (Noble - removed from lxml, unpinned for futur security patches)
MarkupSafe==2.0.1 ; python_version <= '3.10'
MarkupSafe==2.1.2 ; python_version > '3.10' and python_version < '3.12'
MarkupSafe==2.1.5 ; python_version >= '3.12' # (Noble) Mostly to have a wheel package
num2words==0.5.10 ; python_version < '3.12' # (Jammy / Bookworm)
num2words==0.5.13 ; python_version >= '3.12'
ofxparse==0.21
openpyxl==3.0.9 ; python_version < '3.12'
openpyxl==3.1.2 ; python_version >= '3.12'
passlib==1.7.4 # min version = 1.7.2 (Focal with security backports)
Pillow==9.0.1 ; python_version <= '3.10'
Pillow==9.4.0 ; python_version > '3.10' and python_version < '3.12'
Pillow==10.2.0 ; python_version >= '3.12' # (Noble) Mostly to have a wheel package
polib==1.1.1
psutil==5.9.0 ; python_version <= '3.10'
psutil==5.9.4 ; python_version > '3.10' and python_version < '3.12'
psutil==5.9.8 ; python_version >= '3.12' # (Noble) Mostly to have a wheel package
psycopg2==2.9.2 ; python_version == '3.10' # (Jammy)
psycopg2==2.9.5 ; python_version == '3.11'
psycopg2==2.9.9 ; python_version >= '3.12' # (Noble) Mostly to have a wheel package
pyopenssl==21.0.0 ; python_version < '3.12'
pyopenssl==24.1.0 ; python_version >= '3.12' # (Noble) min 23.2.0, pinned for compatibility with cryptography==42.0.8 and security patches
PyPDF2==1.26.0 ; python_version <= '3.10'
PyPDF2==2.12.1 ; python_version > '3.10'
pypiwin32 ; sys_platform == 'win32'
pyserial==3.5
python-dateutil==2.8.1 ; python_version < '3.11'
python-dateutil==2.8.2 ; python_version >= '3.11'
python-ldap==3.4.0 ; sys_platform != 'win32' and python_version < '3.12' # min version = 3.2.0 (Focal with security backports)
python-ldap==3.4.4 ; sys_platform != 'win32' and python_version >= '3.12' # (Noble) Mostly to have a wheel package
python-stdnum==1.17 ; python_version < '3.11' # (jammy)
python-stdnum==1.19 ; python_version >= '3.11'
pytz # no version pinning to avoid OS perturbations
pyusb==1.2.1
qrcode==7.3.1 ; python_version < '3.11' # (jammy)
qrcode==7.4.2 ; python_version >= '3.11'
reportlab==3.6.8 ; python_version <= '3.10'
reportlab==3.6.12 ; python_version > '3.10' and python_version < '3.12'
reportlab==4.1.0 ; python_version >= '3.12' # (Noble) Mostly to have a wheel package
requests==2.25.1 ; python_version < '3.11' # versions < 2.25 aren't compatible w/ urllib3 1.26. Bullseye = 2.25.1. min version = 2.22.0 (Focal)
requests==2.31.0 ; python_version >= '3.11' # (Noble)
rjsmin==1.1.0 ; python_version < '3.11' # (jammy)
rjsmin==1.2.0 ; python_version >= '3.11'
rl-renderPM==4.0.3 ; sys_platform == 'win32' and python_version >= '3.12' # Needed by reportlab 4.1.0 but included in deb package
urllib3==1.26.5 ; python_version < '3.12' # indirect / min version = 1.25.8 (Focal with security backports)
urllib3==2.0.7 ; python_version >= '3.12' # (Noble) Compatibility with cryptography
vobject==0.9.6.1
Werkzeug==2.0.2 ; python_version <= '3.10'
Werkzeug==2.2.2 ; python_version > '3.10' and python_version < '3.12'
Werkzeug==3.0.1 ; python_version >= '3.12' # (Noble) Avoid deprecation warnings
xlrd==1.2.0 ; python_version < '3.12' # (jammy)
xlrd==2.0.1 ; python_version >= '3.12'
XlsxWriter==3.0.2 ; python_version < '3.12' # (jammy)
XlsxWriter==3.1.9 ; python_version >= '3.12'
xlwt==1.3.0
zeep==4.1.0 ; python_version < '3.11' # (jammy)
zeep==4.2.1 ; python_version >= '3.11'
python-dotenv==1.0.1; python_version > '3.10'
python_docx_replace ; python_version > '3.10'
python-docx ; python_version > '3.10'
html2text ; python_version > '3.10'
docx ; python_version > '3.10'
dropbox ; python_version > '3.10'
pyncclient ; python_version > '3.10'
nextcloud-api-wrapper ; python_version > '3.10'
boto3 ; python_version > '3.10'
paramiko ; python_version > '3.10'
proxmoxer ; python_version > '3.10'
requests ; python_version > '3.10'
google_auth ; python_version > '3.10'