Skip to content

Commit dcdeb41

Browse files
authored
Merge pull request #253 from machow/feat-interlinks-fast
Feat interlinks fast
2 parents e02bcee + c822d4f commit dcdeb41

File tree

5 files changed

+131
-16
lines changed

5 files changed

+131
-16
lines changed

_extensions/interlinks/_extension.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
title: Interlinks
22
author: Michael Chow
3-
version: 1.0.0
3+
version: 1.1.0
44
quarto-required: ">=1.2.0"
55
contributes:
66
filters:

_extensions/interlinks/interlinks.lua

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,77 @@
1+
local function read_inv_text(filename)
2+
-- read file
3+
local file = io.open(filename, "r")
4+
if file == nil then
5+
return nil
6+
end
7+
local str = file:read("a")
8+
file:close()
9+
10+
11+
local project = str:match("# Project: (%S+)")
12+
local version = str:match("# Version: (%S+)")
13+
14+
local data = {project = project, version = version, items = {}}
15+
16+
local ptn_data =
17+
"^" ..
18+
"(.-)%s+" .. -- name
19+
"([%S:]-):" .. -- domain
20+
"([%S]+)%s+" .. -- role
21+
"(%-?%d+)%s+" .. -- priority
22+
"(%S*)%s+" .. -- uri
23+
"(.-)\r?$" -- dispname
24+
25+
26+
-- Iterate through each line in the file content
27+
for line in str:gmatch("[^\r\n]+") do
28+
if not line:match("^#") then
29+
-- Match each line against the pattern
30+
local name, domain, role, priority, uri, dispName = line:match(ptn_data)
31+
32+
-- if name is nil, raise an error
33+
if name == nil then
34+
error("Error parsing line: " .. line)
35+
end
36+
37+
data.items[#data.items + 1] = {
38+
name = name,
39+
domain = domain,
40+
role = role,
41+
priority = priority,
42+
uri = uri,
43+
dispName = dispName
44+
}
45+
end
46+
end
47+
return data
48+
end
49+
150
local function read_json(filename)
51+
252
local file = io.open(filename, "r")
353
if file == nil then
454
return nil
555
end
656
local str = file:read("a")
757
file:close()
8-
return quarto.json.decode(str)
58+
59+
local decoded = quarto.json.decode(str)
60+
return decoded
61+
end
62+
63+
local function read_inv_text_or_json(base_name)
64+
local file = io.open(base_name .. ".txt", "r")
65+
if file then
66+
-- TODO: refactors so we don't just close the file immediately
67+
io.close(file)
68+
json = read_inv_text(base_name .. ".txt")
69+
70+
else
71+
json = read_json(base_name .. ".json")
72+
end
73+
74+
return json
975
end
1076

1177
local inventory = {}
@@ -167,11 +233,12 @@ return {
167233
local json
168234
local prefix
169235
for k, v in pairs(meta.interlinks.sources) do
170-
json = read_json(quarto.project.offset .. "/_inv/" .. k .. "_objects.json")
236+
local base_name = quarto.project.offset .. "/_inv/" .. k .. "_objects"
237+
json = read_inv_text_or_json(base_name)
171238
prefix = pandoc.utils.stringify(v.url)
172239
fixup_json(json, prefix)
173240
end
174-
json = read_json(quarto.project.offset .. "/objects.json")
241+
json = read_inv_text_or_json(quarto.project.offset .. "/objects")
175242
if json ~= nil then
176243
fixup_json(json, "/")
177244
end

docs/get-started/interlinks.qmd

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,22 @@ Notice 2 important pieces in this config:
5454
By default, downloaded inventory files will be saved in the `_inv` folder of your
5555
documentation directory.
5656

57+
### Experimental fast option
58+
59+
Use the experimental `fast: true` option to speed up the interlinks filter.
60+
61+
```yaml
62+
interlinks:
63+
fast: true
64+
sources:
65+
```
66+
67+
By default inventory files are saved as JSON, but this option keeps them as text files,
68+
and attempts to parse them much faster.
69+
70+
:::{.callout-warning}
71+
Be sure to install the latest version of the interlinks filter, using `quarto add machow/quartodoc`.
72+
:::
5773

5874
## Running the interlinks filter
5975

quartodoc/__main__.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -212,30 +212,44 @@ def build(config, filter, dry_run, watch, verbose):
212212
@click.command()
213213
@click.argument("config", default="_quarto.yml")
214214
@click.option("--dry-run", is_flag=True, default=False)
215-
def interlinks(config, dry_run):
215+
@click.option("--fast", is_flag=True, default=False)
216+
def interlinks(config, dry_run, fast):
217+
# config loading ----
216218
cfg = yaml.safe_load(open(config))
217-
interlinks = cfg.get("interlinks", None)
218-
219-
cache = cfg.get("cache", "_inv")
219+
interlinks = cfg.get("interlinks", {})
220220

221221
p_root = Path(config).parent
222222

223-
if interlinks is None:
223+
if not interlinks:
224224
print("No interlinks field found in your quarto config. Quitting.")
225225
return
226226

227+
# interlinks config settings ----
228+
cache = p_root / "_inv"
229+
cfg_fast = interlinks.get("fast", False)
230+
231+
fast = cfg_fast or fast
232+
227233
for k, v in interlinks["sources"].items():
228-
# TODO: user shouldn't need to include their own docs in interlinks
234+
# don't include user's own docs (users don't need to specify their own docs in
235+
# the interlinks config anymore, so this is for backwards compat).
229236
if v["url"] == "/":
230237
continue
231238

232239
url = v["url"] + v.get("inv", "objects.inv")
233240
inv = soi.Inventory(url=url)
234241

235-
p_dst = p_root / cache / f"{k}_objects.json"
242+
p_dst = p_root / cache / f"{k}_objects"
236243
p_dst.parent.mkdir(exist_ok=True, parents=True)
237244

238-
convert_inventory(inv, p_dst)
245+
if fast:
246+
# use sphobjinv to dump inv in txt format
247+
df = inv.data_file()
248+
soi.writebytes(p_dst.with_suffix(".txt"), df)
249+
250+
else:
251+
# old behavior of converting to custom json format
252+
convert_inventory(inv, p_dst.with_suffix(".json"))
239253

240254

241255
cli.add_command(build)

quartodoc/autosummary.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ def __init__(
446446
source_dir: "str | None" = None,
447447
dynamic: bool | None = None,
448448
parser="numpy",
449+
_fast_inventory=False,
449450
):
450451
self.layout = self.load_layout(
451452
sections=sections, package=package, options=options
@@ -467,6 +468,8 @@ def __init__(
467468
self.source_dir = str(Path(source_dir).absolute()) if source_dir else None
468469
self.dynamic = dynamic
469470

471+
self._fast_inventory = _fast_inventory
472+
470473
def load_layout(self, sections: dict, package: str, options=None):
471474
# TODO: currently returning the list of sections, to make work with
472475
# previous code. We should make Layout a first-class citizen of the
@@ -522,7 +525,16 @@ def build(self, filter: str = "*"):
522525

523526
_log.info("Creating inventory file")
524527
inv = self.create_inventory(items)
525-
convert_inventory(inv, self.out_inventory)
528+
if self._fast_inventory:
529+
# dump the inventory file directly as text
530+
# TODO: copied from __main__.py, should add to inventory.py
531+
import sphobjinv as soi
532+
533+
df = inv.data_file()
534+
soi.writebytes(Path(self.out_inventory).with_suffix(".txt"), df)
535+
536+
else:
537+
convert_inventory(inv, self.out_inventory)
526538

527539
# sidebar ----
528540

@@ -647,12 +659,18 @@ def from_quarto_config(cls, quarto_cfg: "str | dict"):
647659

648660
quarto_cfg = yaml.safe_load(open(quarto_cfg))
649661

650-
cfg = quarto_cfg["quartodoc"]
662+
cfg = quarto_cfg.get("quartodoc")
663+
if cfg is None:
664+
raise KeyError("No `quartodoc:` section found in your _quarto.yml.")
651665
style = cfg.get("style", "pkgdown")
652-
653666
cls_builder = cls._registry[style]
654667

655-
return cls_builder(**{k: v for k, v in cfg.items() if k != "style"})
668+
_fast_inventory = quarto_cfg.get("interlinks", {}).get("fast", False)
669+
670+
return cls_builder(
671+
**{k: v for k, v in cfg.items() if k != "style"},
672+
_fast_inventory=_fast_inventory,
673+
)
656674

657675

658676
class BuilderPkgdown(Builder):

0 commit comments

Comments
 (0)