-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add API to get available/current version from JavaScript #9875
Comments
Hey David, We actually have an extension that does most of this: https://github.com/humitos/sphinx-version-warning -- it hasn't been heavily used, but it uses our existing |
Cool! I could have sworn there wasn't a version API last time I checked, but then again that might have been a few years ago now 😅 Opened this because we had discussed it, without actually checking the API again, sorry. We do have a custom domain, so I'll need to verify that CORS works. |
Ideally, this will be supported with the new addons we are building at https://github.com/readthedocs/addons |
For what it's worth, I ended up writing some JavaScript that only relies on the version configured in Sphinx and the versions fetched from the PyPI API. This assumes normalized PEP 440 version markers (a more complicated regex could be used if needed), and discards non-final versions (a, b, rc, post, dev). If the docs have the version "2.2" or "2.2.x", everything under 2.2 like 2.2.1 will count towards 2.2, only 2.3 would be considered newer to show the banner. If the docs version is higher than any released version on PyPI, then it is considered the dev version. Implementationconst versionRe = new RegExp([
"^",
"(?:(?<epoch>[1-9][0-9]*)!)?)",
"(?<version>(?:0|[1-9][0-9]*)(?:\\.(?:0|[1-9][0-9]*))*)",
"(?:(?<preL>a|b|rc)(?<preN>0|[1-9][0-9]*))?",
"(?:\\.post(?<postN>0|[1-9][0-9]*))?",
"(?:\\.dev(?<devN>0|[1-9][0-9]*))?",
"$",
].join(""))
function parseVersion(value) {
const {groups: {epoch, version, preL, preN, postN, devN}} = versionRe.exec(value)
const parts = []
let keep = false
version.split(".").map(Number.parseInt).reverse().forEach(p => {
if (keep) {
parts.push(p)
} else if (p > 0) {
parts.push(p)
keep = true
}
})
parts.push(Number.parseInt(epoch) || 0)
return {
version: parts.reverse(),
isPre: Boolean(preL),
preL: preL || "",
preN: Number.parseInt(preN) || 0,
isPost: Boolean(postN),
postN: Number.parseInt(postN) || 0,
isDev: Boolean(devN),
devN: Number.parseInt(devN) || 0,
}
}
function compareVersions(a, b) {
for (const [i, an] of a.entries()) {
const bn = i < b.length ? b[i] : 0
if (an < bn) {
return -1
} else if (an > bn) {
return 1
}
}
if (a.length < b.length) {
return -1
}
return 0
}
async function getReleasedVersions(name) {
const response = await fetch(
`https://pypi.org/simple/${name}/`,
{"headers": {"Accept": "application/vnd.pypi.simple.v1+json"}}
)
const data = await response.json()
return data["versions"]
.map(parseVersion)
.filter(v => !(v.isPre || v.isDev))
.map(v => v.version)
.sort(compareVersions)
.reverse()
}
async function describeVersion(name, value) {
if (value.endsWith(".x")) {
value = value.slice(0, -2)
}
const currentVersion = parseVersion(value).version
const releasedVersions = await getReleasedVersions(name)
if (releasedVersions.length === 0) {
return "dev"
}
const latestVersion = releasedVersions[0]
while (latestVersion.length < currentVersion.length) {
latestVersion.push(0)
}
const compared = compareVersions(currentVersion, latestVersion)
if (compared === 1) {
return ["dev", latestVersion]
}
if (currentVersion.every((n, i) => n === latestVersion[i])) {
return ["latest", latestVersion]
}
return ["old", latestVersion]
} An example: async function addVersionBanner() {
const [desc, latestVersion] = await describeVersion("flask", "2.2.x")
if (desc === "old") {
// show "This is an old version" banner.
} else if (desc === "dev") {
// show "This is the dev version" banner.
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", addVersionBanner)
} else {
addVersionBanner()
} |
I did an initial implementation to expose all the Read the Docs data used to generate the flyout as a JavaScript object in readthedocs/addons#64. I'd like to receive feedback about it since I should be useful to solve this problem 👍🏼 |
With the implementation of #11069 this will be configurable per project and even with a customized pattern. Let me know if that will help here as well. |
In Flask's docs, I show a banner that floats at the top of the window with a warning if it is an old version or the development version, with a link to the page for the most recent version. However, it is currently rendered while building the docs, which means that all versions need to be rebuilt when a new version is released.
I would like to be able to implement this version banner in JavaScript, so that it always points to the latest version without requiring rebuilds. To do this, I need to be able to fetch the list of available versions and current version, just like I can with the info injected into the context during build.
The text was updated successfully, but these errors were encountered: