Skip to content
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

Switch to quarto for docs #799

Merged
merged 14 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions .github/workflows/ci_python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ jobs:
poetry install --only=main --only=lint
- name: black
run: |
poetry run black --diff pysrc pytests docs/source
epinzur marked this conversation as resolved.
Show resolved Hide resolved
poetry run black --diff pysrc pytests docs
- name: flake8
run: |
poetry run flake8 pysrc pytests docs/source
poetry run flake8 pysrc pytests docs
- name: isort
run: |
poetry run isort --filter-files --diff pysrc pytests docs/source
poetry run isort --filter-files --diff pysrc pytests docs
- name: pydocstyle
run: |
poetry run pydocstyle pysrc
Expand Down Expand Up @@ -227,28 +227,33 @@ jobs:
echo "::endgroup::"
deactivate
done
- name: Setup QT
# Needed by sphinx-social-cards.
# https://github.com/2bndy5/sphinx-social-cards/blob/main/.github/workflows/build.yml#L54
- name: Lint reference docs
run: |
sudo apt-get install -y libgl1-mesa-dev libxkbcommon-x11-0
echo "QT_QPA_PLATFORM=offscreen" >> "$GITHUB_ENV"
poetry env use 3.11
source $(poetry env info --path)/bin/activate
poetry install --with=docs
pip install ${WHEEL} --force-reinstall
epinzur marked this conversation as resolved.
Show resolved Hide resolved
cd docs
python _scripts/lint_reference.py
deactivate
- name: Set up Quarto
uses: quarto-dev/quarto-actions/setup@v2
- name: Build docs
# ablog doesn't currently indicate whether it supports parallel reads,
# leading to a warning.
# when possible, add `"-j", "auto",` to do parallel builds (and in nox).
run: |
poetry env use 3.11
source $(poetry env info --path)/bin/activate
poetry install --with=docs
pip install ${WHEEL} --force-reinstall
sphinx-build docs/source docs/_build -W # -j auto
cd docs
python _scripts/gen_reference.py
python -m quartodoc interlinks
quarto render --output-dir _site
deactivate
- name: Upload docs
uses: actions/upload-pages-artifact@v2
with:
# Automatically uploads an artifact from the './_site' directory by default
path: ${{ github.workspace }}/python/docs/_build
path: ${{ github.workspace }}/python/docs/_site

rust-format:
runs-on: ubuntu-20.04
Expand Down
38 changes: 38 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,41 @@ Alternatively, install nox and run the tests inside an isolated environment:
```shell
nox
```


## Previewing Docs

* Install `quarto-cli` on your machine. Also consider installing an IDE extension.

See: https://quarto.org/docs/get-started/

* Generate reference docs

```shell
nox -s docs-gen
```

You should re-run this after making any updates to the `pysrc` docstrings.
If _Preview Docs_ is running in another shell, the system should auto-refresh with your changes.
epinzur marked this conversation as resolved.
Show resolved Hide resolved

* Preview docs (with auto-refresh on edit)

```shell
nox -s docs
```

* Cleanup generated and cached docs

```shell
nox -s docs-clean
```

Try this if you see something unexpected (especially after deleting or renaming).

* Builds docs to `docs/_site`

```shell
nox -s docs-build
```

This is primarily used in CI.
26 changes: 21 additions & 5 deletions python/docs/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
_build
.jupyter_cache
jupyter_execute
source/reference/apidocs
source/iframe_figures
# quarto build cache
/.quarto/

# quartodoc interlink artifacts
/_inv/
/objects.json

# generated API docs
/reference/

# files that might remain if a quarto build is interrupted
index.html
/blog/*.html
/examples/*.html
/guide/*.html
index-listing.json
data_types-listing.json
/site_libs/

# output of the site from `nox -s docs-build`
/_site/
3 changes: 3 additions & 0 deletions python/docs/_extensions/machow/interlinks/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.html
epinzur marked this conversation as resolved.
Show resolved Hide resolved
*.pdf
*_files/
7 changes: 7 additions & 0 deletions python/docs/_extensions/machow/interlinks/_extension.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
title: Interlinks
author: Michael Chow
version: 1.1.0
quarto-required: ">=1.2.0"
contributes:
filters:
- interlinks.lua
254 changes: 254 additions & 0 deletions python/docs/_extensions/machow/interlinks/interlinks.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
local function read_inv_text(filename)
-- read file
local file = io.open(filename, "r")
if file == nil then
return nil
end
local str = file:read("a")
file:close()


local project = str:match("# Project: (%S+)")
local version = str:match("# Version: (%S+)")

local data = {project = project, version = version, items = {}}

local ptn_data =
"^" ..
"(.-)%s+" .. -- name
"([%S:]-):" .. -- domain
"([%S]+)%s+" .. -- role
"(%-?%d+)%s+" .. -- priority
"(%S*)%s+" .. -- uri
"(.-)\r?$" -- dispname


-- Iterate through each line in the file content
for line in str:gmatch("[^\r\n]+") do
if not line:match("^#") then
-- Match each line against the pattern
local name, domain, role, priority, uri, dispName = line:match(ptn_data)

-- if name is nil, raise an error
if name == nil then
error("Error parsing line: " .. line)
end

data.items[#data.items + 1] = {
name = name,
domain = domain,
role = role,
priority = priority,
uri = uri,
dispName = dispName
}
end
end
return data
end

local function read_json(filename)

local file = io.open(filename, "r")
if file == nil then
return nil
end
local str = file:read("a")
file:close()

local decoded = quarto.json.decode(str)
return decoded
end

local function read_inv_text_or_json(base_name)
local file = io.open(base_name .. ".txt", "r")
if file then
-- TODO: refactors so we don't just close the file immediately
io.close(file)
json = read_inv_text(base_name .. ".txt")

else
json = read_json(base_name .. ".json")
end

return json
end

local inventory = {}

local function lookup(search_object)

local results = {}
for _, inv in ipairs(inventory) do
for _, item in ipairs(inv.items) do
-- e.g. :external+<inv_name>:<domain>:<role>:`<name>`
if item.inv_name and item.inv_name ~= search_object.inv_name then
goto continue
end

if item.name ~= search_object.name then
goto continue
end

if search_object.role and item.role ~= search_object.role then
goto continue
end

if search_object.domain and item.domain ~= search_object.domain then
goto continue
else
if search_object.domain or item.domain == "py" then
table.insert(results, item)
end

goto continue
end

::continue::
end
end

if #results == 1 then
return results[1]
end
if #results > 1 then
quarto.log.warning("Found multiple matches for " .. search_object.name .. ", using the first match.")
return results[1]
end
if #results == 0 then
quarto.log.warning("Found no matches for object:\n", search_object)
end

return nil
end

local function mysplit (inputstr, sep)
if sep == nil then
sep = "%s"
end
local t={}
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
table.insert(t, str)
end
return t
end

local function normalize_role(role)
if role == "func" then
return "function"
end
return role
end

local function build_search_object(str)
local starts_with_colon = str:sub(1, 1) == ":"
local search = {}
if starts_with_colon then
local t = mysplit(str, ":")
if #t == 2 then
-- e.g. :py:func:`my_func`
search.role = normalize_role(t[1])
search.name = t[2]:match("%%60(.*)%%60")
elseif #t == 3 then
-- e.g. :py:func:`my_func`
search.domain = t[1]
search.role = normalize_role(t[2])
search.name = t[3]:match("%%60(.*)%%60")
elseif #t == 4 then
-- e.g. :ext+inv:py:func:`my_func`
search.external = true

search.inv_name = t[1]:match("external%+(.*)")
search.domain = t[2]
search.role = normalize_role(t[3])
search.name = t[4]:match("%%60(.*)%%60")
else
quarto.log.warning("couldn't parse this link: " .. str)
return {}
end
else
search.name = str:match("%%60(.*)%%60")
end

if search.name == nil then
quarto.log.warning("couldn't parse this link: " .. str)
return {}
end

if search.name:sub(1, 1) == "~" then
search.shortened = true
search.name = search.name:sub(2, -1)
end
return search
end

local function report_broken_link(link, search_object, replacement)
-- TODO: how to unescape html elements like [?
return pandoc.Code(pandoc.utils.stringify(link.content))
end

function Link(link)
-- do not process regular links ----
if not link.target:match("%%60") then
return link
end

-- lookup item ----
local search = build_search_object(link.target)
local item = lookup(search)

-- determine replacement, used if no link text specified ----
local original_text = pandoc.utils.stringify(link.content)
local replacement = search.name
if search.shortened then
local t = mysplit(search.name, ".")
replacement = t[#t]
end

-- set link text ----
if original_text == "" and replacement ~= nil then
link.content = pandoc.Code(replacement)
end

-- report broken links ----
if item == nil then
return report_broken_link(link, search)
end
link.target = item.uri:gsub("%$$", search.name)


return link
end

local function fixup_json(json, prefix)
for _, item in ipairs(json.items) do
item.uri = prefix .. item.uri
end
table.insert(inventory, json)
end

return {
{
Meta = function(meta)
local json
local prefix
if meta.interlinks and meta.interlinks.sources then
for k, v in pairs(meta.interlinks.sources) do
local base_name = quarto.project.offset .. "/_inv/" .. k .. "_objects"
json = read_inv_text_or_json(base_name)
prefix = pandoc.utils.stringify(v.url)
if json ~= nil then
fixup_json(json, prefix)
end
end
end
json = read_inv_text_or_json(quarto.project.offset .. "/objects")
if json ~= nil then
fixup_json(json, "/")
end
end
},
{
Link = Link
}
}
Loading
Loading