Skip to content

Commit

Permalink
Merge pull request #97 from posit-dev/docs-interlinks
Browse files Browse the repository at this point in the history
Add interlinks throughout documentation site
  • Loading branch information
rich-iannone authored Dec 19, 2023
2 parents 6d09a5e + 6d3c606 commit c907d8d
Show file tree
Hide file tree
Showing 13 changed files with 438 additions and 106 deletions.
3 changes: 3 additions & 0 deletions docs/_extensions/machow/interlinks/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.html
*.pdf
*_files/
7 changes: 7 additions & 0 deletions 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 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
}
}
48 changes: 32 additions & 16 deletions docs/_quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,21 @@ website:
format:
html:
theme: cosmo
# css: styles.css
css: styles.css
toc: true

filters:
- interlinks

interlinks:
sources:
numpy:
url: https://numpy.org/doc/stable/
python:
url: https://docs.python.org/3/

html-table-processing: none

# tell quarto to read the generated sidebar
metadata-files:
- _sidebar.yml
Expand All @@ -51,21 +63,22 @@ quartodoc:
package: great_tables
sidebar: _sidebar.yml
dynamic: true
render_interlinks: true
sections:
- title: Table Creation
desc: >
All tables created in **Great Tables** begin by using `GT()`. Here we supply the input
data table and some basic options for creating a stub (`rowname_col`) and row groups
(`groupname_col`).
All tables created in **Great Tables** begin by using [`GT()`](`great_tables.GT`). With this
class, we supply the input data table and some basic options for creating a stub and row
groups (with the `rowname_col` and `groupname_col` arguments).
contents:
- GT
- title: Creating or modifying parts of a table
desc: >
A table can contain a few useful components for conveying additional information. These
include a header (with a titles and subtitle), a footer (with footnotes and source notes),
and additional areas for labels (row group labels, column spanner labels, the stubhead
label). We can modify the look of table parts more generally with `tab_options()` and
perform styling on targeted table locations with `tab_style()`.
label). We can perform styling on targeted table locations with the
[`tab_style()`](`great_tables.GT.tab_style`) method.
contents:
- GT.tab_header
- GT.tab_spanner
Expand All @@ -77,7 +90,7 @@ quartodoc:
Columns of data can be formatted with the `fmt_*()` methods. We can specify the rows of
these columns quite precisely with the `rows` argument. We get to apply these methods
exactly once to each data cell (last call wins). Need to do custom formatting? Use the
`fmt()` method and define your own formatter within.
[`fmt()`](`great_tables.GT.fmt`) method and define your own formatter.
contents:
- GT.fmt_number
- GT.fmt_integer
Expand All @@ -93,8 +106,9 @@ quartodoc:
- title: Modifying columns
desc: >
The `cols_*()` methods allow for modifications that act on entire columns. This includes
alignment of the data in columns (`cols_align()`), hiding columns from view (`cols_hide()`),
re-labeling the column labels (`cols_label()`), and moving columns around (the
alignment of the data in columns ([`cols_align()`](`great_tables.GT.cols_align`)), hiding
columns from view ([`cols_hide()`](`great_tables.GT.cols_hide`)), re-labeling the column
labels ([`cols_label()`](`great_tables.GT.cols_label`)), and moving columns around (with the
`cols_move*()` methods).
contents:
- GT.cols_align
Expand All @@ -106,20 +120,22 @@ quartodoc:
- title: Location Targeting and Styling Classes
desc: >
Location targeting is a powerful feature of **Great Tables**. It allows for the precise
selection of table locations for styling (using the `tab_style()` method). The styling
classes allow for the specification of the styling properties to be applied to the targeted
locations.
selection of table locations for styling (using the
[`tab_style()`](`great_tables.GT.tab_style`) method). The styling classes allow for the
specification of the styling properties to be applied to the targeted locations.
contents:
- loc.body
- style.fill
- style.text
- style.borders
- title: Helper functions
desc: >
An assortment of helper functions is available in the **Great Tables** package. The `md()`
and `html()` helpers can used be during label creation with the `tab_header()`,
`tab_footnote()`, `tab_spanner()`, `tab_stubhead_label()`, and `tab_source_note()`
methods.
An assortment of helper functions is available in the **Great Tables** package. The
[`md()`](`great_tables.md`) and [`html()`](`great_tables.html`) helper functions can used
during label creation with the [`tab_header()`](`great_tables.GT.tab_header`),
[`tab_spanner()`](`great_tables.GT.tab_spanner`),
[`tab_stubhead()`](`great_tables.GT.tab_stubhead`), and
[`tab_source_note()`](`great_tables.GT.tab_source_note`) methods.
contents:
- md
- html
Expand Down
Loading

0 comments on commit c907d8d

Please sign in to comment.