diff --git a/Makefile b/Makefile index b36f3e6a..f1611b43 100644 --- a/Makefile +++ b/Makefile @@ -18,3 +18,6 @@ help: # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +# To make the version_management module visible while building the documentation +export PYTHONPATH := $(SOURCEDIR):$(PYTHONPATH) diff --git a/_static/css/furo-phoenix.css b/_static/css/furo-phoenix.css index a548cca2..26d65c46 100644 --- a/_static/css/furo-phoenix.css +++ b/_static/css/furo-phoenix.css @@ -148,13 +148,104 @@ img { border-right: none !important; box-sizing: border-box; display: flex; - justify-content: flex-start !important; - width: 17em !important + justify-content: flex-end; + flex-direction: column !important; + height: 100vh !important; + width: 17em !important; + position: fixed !important } .sidebar-container { box-sizing: border-box; - width: 17em !important + width: inherit !important; + overflow-y: auto !important; + flex-grow: 1 !important +} + +.sidebar-sticky { + height: 100% !important; + overflow-y: hidden !important +} + +.version-container { + width: inherit !important; + border-top: 1px solid #c2cbcb !important; + background-color: #f1f3f3 !important; + font-family: 'Open Sans', sans-serif !important; + font-weight: 600 !important; + color: var(--color-sidebar-link-text--top-level) !important +} + +.default-version { + display: flex !important; + justify-content: space-between !important; + cursor: pointer !important +} + +.default-version:hover { + background-color: #ea5b22 !important; + color: white !important +} + +.default-version > label { + display: flex !important; + align-items: center !important; + justify-content: center !important; + cursor: pointer !important; + width: var(--sidebar-expander-width) !important; + height: var(--sidebar-item-height) !important +} + +.version-top { + flex: 1 !important; + display: flex !important; + justify-content: space-between !important; + align-items: center !important; + padding-left: 10px !important +} + +.version-container.shift-up > .default-version label > .icon > svg { + transform: rotate(-90deg) !important +} + +.default-version label > .icon > svg { + transform: rotate(90deg) !important +} + +.version-container.shift-up .other-versions { + display: block !important +} + +.other-versions { + margin-right: 10px !important; + margin-left: 10px !important; + box-sizing: border-box !important; + display: none !important +} + +.other-versions > dl { + margin-top: 7px !important +} + +.other-versions > dl > dd { + display: inline-block !important; + margin: 0 !important; + padding: 5px !important +} + +.other-versions > dl > dd > a { + text-decoration: none !important; + font-weight: 500 !important; + color: var(--color-sidebar-link-text--top-level) !important +} + +.other-versions > dl > dd > a:hover { + font-weight: 500 !important; + color: #ea5b22 !important +} + +.main { + margin-left: 17em !important } /* Hide sidebar brand text */ diff --git a/_static/js/functions.js b/_static/js/functions.js index 612417de..650aeee7 100644 --- a/_static/js/functions.js +++ b/_static/js/functions.js @@ -5,25 +5,33 @@ function findRelativePath() if (window.location.origin === 'null') { // Local run - let fullPath = window.location.href; - let index = fullPath.indexOf('_build/html'); + const fullPath = window.location.href; + const index = fullPath.indexOf('_build/html'); currentPath = fullPath.substring(index); path = '_build/html/libc/functions/index.html'; } else { // Server run - currentPath = window.location.href; - path = window.location.origin + '/libc/functions/index.html'; + currentPath = window.location.pathname; + const pathSegments = currentPath.split('/').filter(segment => segment !== ''); + const firstItem = pathSegments[0] || null; + + if (firstItem === 'latest' || /^[0-9]+\.[0-9]+\.[0-9]+$/.test(firstItem)) { + path = firstItem + '/libc/functions/index.html'; + } + else { + path = '/libc/functions/index.html'; + } } - let fromSegments = currentPath.split('/'); - let toSegments = path.split('/'); + const fromSegments = currentPath.split('/'); + const toSegments = path.split('/'); if (fromSegments.join() === toSegments.join()) { if (window.location.origin === 'null') { // Local run - let fullPath = window.location.href; - let index = fullPath.indexOf('_build/html'); + const fullPath = window.location.href; + const index = fullPath.indexOf('_build/html'); return fullPath.substring(0, index) + path; } @@ -50,11 +58,11 @@ function findRelativePath() function getElementsByTagNameWithDepth(element, tagName, depthLimit, currentDepth = 0) { - var elements = []; - var children = element.children; + let elements = []; + const children = element.children; for (let i = 0; i < children.length; i++) { - let child = children[i]; + const child = children[i]; if (child.tagName.toLowerCase() === tagName.toLowerCase()) { elements.push(child); } @@ -126,13 +134,13 @@ headersLi.forEach(function(list) { } }); - let anchor = list.querySelector('a'); + const anchor = list.querySelector('a'); // Slashes and dots in headers are replaced with '-' - let textContent = anchor.textContent + const textContent = anchor.textContent .replace(/\//g, '-') .replace(/\.h/g, '-h'); - let relativePath = findRelativePath() + '#' + textContent; + const relativePath = findRelativePath() + '#' + textContent; anchor.setAttribute('href', relativePath); diff --git a/_static/js/versions.js b/_static/js/versions.js new file mode 100644 index 00000000..8450211a --- /dev/null +++ b/_static/js/versions.js @@ -0,0 +1,30 @@ +const curr_version = document.querySelector('.curr-version'); +const currentPath = window.location.pathname; +const pathSegments = currentPath.split('/').filter(segment => segment !== ''); +const firstItem = pathSegments[0] || null; + +if (firstItem !== null && + (/^[0-9]+\.[0-9]+\.[0-9]+$/.test(firstItem))) +{ + curr_version.textContent = 'ver. ' + firstItem; +} +// version directories not available - marking the current version as latest +else { + curr_version.textContent = 'ver. latest'; +} + +const versions = document.querySelector('.other-versions'); + +versions.querySelectorAll('a').forEach(anchor => { + anchor.addEventListener('click', function() { + baseTag.href = window.location.origin + anchor.textContent + '/'; + }); +}); + +const triggerElement = document.querySelector('.default-version'); +const versionContainer = document.querySelector('.version-container'); +const sidebarContainer = document.querySelector('.sidebar-container'); + +triggerElement.addEventListener('click', function() { + versionContainer.classList.toggle('shift-up'); +}); diff --git a/_templates/page.html b/_templates/page.html index b338caba..8e51ce77 100644 --- a/_templates/page.html +++ b/_templates/page.html @@ -57,6 +57,7 @@ {% endblock left_sidebar %} + {% include "versions.html" %}
diff --git a/_templates/versions.html b/_templates/versions.html new file mode 100644 index 00000000..40da8689 --- /dev/null +++ b/_templates/versions.html @@ -0,0 +1,19 @@ +
+
+ + Phoenix-RTOS + ver. latest + + +
+
+
+ Document Release Versions: + {% for name, url in versions %} +
{{ name }}
+ {% endfor %} +
+
+
diff --git a/conf.py b/conf.py index 8be6429a..d630793a 100644 --- a/conf.py +++ b/conf.py @@ -6,73 +6,75 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = '' -copyright = '2024, Phoenix Systems' -author = 'Phoenix Systems' +from version_management import get_version_context + + +project = "" +copyright = "2024, Phoenix Systems" +author = "Phoenix Systems" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = [ - 'myst_parser', - 'sphinx_copybutton' -] +extensions = ["myst_parser", "sphinx_copybutton"] -templates_path = ['_templates'] -exclude_patterns = ['README.md', '_build', 'Thumbs.db', '.DS_Store'] +templates_path = ["_templates"] +exclude_patterns = ["README.md", "_build", "Thumbs.db", ".DS_Store"] myst_heading_anchors = 3 -pygments_dark_style = 'tango' +pygments_dark_style = "tango" # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_title = 'Phoenix-RTOS Documentation' -html_favicon = '_images/RTOS_sign.png' -html_theme = 'furo' -html_js_files = ['js/functions.js'] -html_style = ['css/furo-phoenix.css', 'css/furo-extensions-phoenix.css'] -html_static_path = ['_static', '_images'] +html_title = "Phoenix-RTOS Documentation" +html_favicon = "_images/RTOS_sign.png" +html_theme = "furo" +html_js_files = ["js/functions.js", "js/versions.js"] +html_style = ["css/furo-phoenix.css", "css/furo-extensions-phoenix.css"] +html_static_path = ["_static", "_images"] +html_baseurl = "https://docs.phoenix-rtos.com/latest/" +html_context = {"versions": get_version_context()} + # TODO: add dark mode support html_theme_options = { - "light_logo": "light_logo.png", - "light_css_variables": { - "sidebar-caption-font-size": "100%", - "sidebar-item-font-size": "90%", - "sidebar-item-spacing-vertical": "0.4rem", - "sidebar-item-line-height": "1.1rem", - "color-sidebar-search-icon": "#EA5B22", - "sidebar-search-space-above": "0.1rem", - "sidebar-caption-space-above": "0.1rem", - "sidebar-tree-space-above": "0.5rem", - "sidebar-search-input-font-size": "95%", - "font-stack": "'Open Sans', sans-serif", - "color-sidebar-background": "#0F1724", - "color-sidebar-link-text--top-level": "#273149", - "color-sidebar-item-background--current": "F1F3F3", - "color-foreground-primary": "#273149", - "color-brand-content": "#1890d7", - "color-header-background": "#0F1724", - "color-header-text": "white" - }, - - "dark_logo": "light_logo.png", - "dark_css_variables": { - "sidebar-caption-font-size": "100%", - "sidebar-item-font-size": "90%", - "sidebar-item-spacing-vertical": "0.4rem", - "sidebar-item-line-height": "1.1rem", - "color-sidebar-search-icon": "#EA5B22", - "sidebar-search-space-above": "0.1rem", - "sidebar-caption-space-above": "0.1rem", - "sidebar-tree-space-above": "0.5rem", - "sidebar-search-input-font-size": "95%", - "font-stack": "'Open Sans', sans-serif", - "color-sidebar-background": "#0F1724", - "color-sidebar-link-text--top-level": "#273149", - "color-sidebar-item-background--current": "#0F1F3F3", - "color-foreground-primary": "#273149", - "color-brand-content": "#1890d7", - "color-header-background": "#0F1724", - "color-header-text": "white" - } + "light_logo": "light_logo.png", + "light_css_variables": { + "sidebar-caption-font-size": "100%", + "sidebar-item-font-size": "90%", + "sidebar-item-spacing-vertical": "0.4rem", + "sidebar-item-line-height": "1.1rem", + "color-sidebar-search-icon": "#EA5B22", + "sidebar-search-space-above": "0.1rem", + "sidebar-caption-space-above": "0.1rem", + "sidebar-tree-space-above": "0.5rem", + "sidebar-search-input-font-size": "95%", + "font-stack": "'Open Sans', sans-serif", + "color-sidebar-background": "#0F1724", + "color-sidebar-link-text--top-level": "#273149", + "color-sidebar-item-background--current": "F1F3F3", + "color-foreground-primary": "#273149", + "color-brand-content": "#1890d7", + "color-header-background": "#0F1724", + "color-header-text": "white", + }, + "dark_logo": "light_logo.png", + "dark_css_variables": { + "sidebar-caption-font-size": "100%", + "sidebar-item-font-size": "90%", + "sidebar-item-spacing-vertical": "0.4rem", + "sidebar-item-line-height": "1.1rem", + "color-sidebar-search-icon": "#EA5B22", + "sidebar-search-space-above": "0.1rem", + "sidebar-caption-space-above": "0.1rem", + "sidebar-tree-space-above": "0.5rem", + "sidebar-search-input-font-size": "95%", + "font-stack": "'Open Sans', sans-serif", + "color-sidebar-background": "#0F1724", + "color-sidebar-link-text--top-level": "#273149", + "color-sidebar-item-background--current": "#0F1F3F3", + "color-foreground-primary": "#273149", + "color-brand-content": "#1890d7", + "color-header-background": "#0F1724", + "color-header-text": "white", + }, } diff --git a/version_management.py b/version_management.py new file mode 100644 index 00000000..9af07f01 --- /dev/null +++ b/version_management.py @@ -0,0 +1,75 @@ +import subprocess +import re +from packaging import version +from typing import List, Tuple + + +class VersionManager: + def __init__(self, min_version: str, max_versions: int = 3): + self.min_version = min_version + self.max_versions = max_versions + # Pattern to match versioning (with or without 'v' prefix) + self.version_pattern = re.compile(r"^v?\d+\.\d+\.\d+$") + + def get_git_tags(self) -> List[str]: + """Get all git tags from the repository.""" + try: + result = subprocess.run( + ["git", "tag"], capture_output=True, text=True, check=True + ) + return result.stdout.strip().split("\n") + except subprocess.CalledProcessError: + return [] + + def is_valid_version(self, tag: str) -> bool: + """ + Check if the tag matches versioning pattern. + """ + return bool(self.version_pattern.match(tag)) + + def filter_and_sort_versions(self, tags: List[str]) -> List[str]: + """ + Filter versions >= min_version and sort them in descending order. + Removes 'v' prefix if present and ignores non-semantic versioning tags. + """ + valid_versions = [] + + for tag in tags: + if not self.is_valid_version(tag): + continue + + clean_version = tag.lstrip("v") + + try: + if version.parse(clean_version) >= version.parse(self.min_version): + valid_versions.append(clean_version) + except version.InvalidVersion: + continue + + return sorted(valid_versions, key=version.parse, reverse=True) + + def generate_version_tuples(self, versions: List[str]) -> List[Tuple[str, str]]: + """ + Generate version tuples for html_context in Sphinx configuration. + Always includes 'latest' and up to max_versions recent versions. + """ + result = [("latest", "/")] + + for ver in versions[: self.max_versions]: + result.append((ver, f"/{ver}/")) + + return result + + def get_version_context(self) -> tuple: + """ + Generate the version context for Sphinx configuration. + """ + tags = self.get_git_tags() + filtered_versions = self.filter_and_sort_versions(tags) + version_tuples = self.generate_version_tuples(filtered_versions) + return tuple(version_tuples) + + +def get_version_context(min_version: str = "3.3.0", max_versions: int = 3) -> tuple: + manager = VersionManager(min_version, max_versions) + return manager.get_version_context()