docsets.nix-builtins: init
This commit is contained in:
@@ -60,6 +60,7 @@ in {
|
||||
];
|
||||
sane.programs.docsets.config.pkgs = with pkgs; [
|
||||
# packages which ship docsets natively:
|
||||
docsets.nix-builtins
|
||||
docsets.nixpkgs-lib
|
||||
docsets.rust-std
|
||||
] ++ lib.map
|
||||
|
@@ -4,43 +4,70 @@
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import sqlite3
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ItemType(Enum):
|
||||
# these loosely correspond to docset types: <https://kapeli.com/docsets#supportedentrytypes>
|
||||
SECTION = 1
|
||||
CONSTANT = 2
|
||||
FUNCTION = 3
|
||||
|
||||
@dataclass(unsafe_hash=True, order=True)
|
||||
class Item:
|
||||
attrpath: str
|
||||
type: ItemType
|
||||
|
||||
def parent(self) -> "Item | None":
|
||||
pp = parent_attrpath(self.attrpath)
|
||||
if pp:
|
||||
return Item(attrpath=pp, type=ItemType.SECTION)
|
||||
|
||||
@dataclass
|
||||
class Items:
|
||||
functions: list[str]
|
||||
sections: list[str]
|
||||
items: dict[str, Item]
|
||||
|
||||
def add(self, it: Item) -> bool:
|
||||
""" returns True iff the item was not previously known """
|
||||
if it.attrpath in self.items and self.items[it.attrpath].type.value <= it.type.value:
|
||||
return False # we already have the item, and the new item is no more specific
|
||||
|
||||
self.items[it.attrpath] = it
|
||||
return True
|
||||
|
||||
def sections(self) -> list[Item]:
|
||||
return sorted(i for i in self.items.values() if i.type == ItemType.SECTION)
|
||||
|
||||
def leaves(self) -> list[Item]:
|
||||
return sorted(i for i in self.items.values() if i.type != ItemType.SECTION)
|
||||
|
||||
def all(self) -> list[Item]:
|
||||
return self.sections() + self.leaves()
|
||||
|
||||
def read_items(locations_json: Path) -> Items:
|
||||
# TODO: this seems to yield some items which don't have documentation? like `lib.gvariant.type`
|
||||
items = Items({})
|
||||
with open(locations_json, "r") as f:
|
||||
functions = json.load(f).keys()
|
||||
for attrpath, val in json.load(f).items():
|
||||
if isinstance(val, dict) and "type" in val:
|
||||
# `nix __dump-language` output is structured and only constants can have a type
|
||||
type = ItemType.CONSTANT
|
||||
else:
|
||||
# lacking additional information, assume it's a function
|
||||
type = ItemType.FUNCTION
|
||||
|
||||
sections = set(parent_attrpath(f) for f in functions)
|
||||
while True:
|
||||
expanded_sections = sections.union(parent_attrpath(s) for s in sections)
|
||||
if expanded_sections == sections:
|
||||
break
|
||||
else:
|
||||
sections = expanded_sections
|
||||
item = Item(attrpath=attrpath, type=type)
|
||||
items.add(item)
|
||||
|
||||
# the above algorithm includes a nameless root section `""`: remove that
|
||||
sections = [ s for s in sections if s ]
|
||||
while any(items.add(i.parent()) for i in items.all() if i.parent()):
|
||||
pass
|
||||
|
||||
# for some cases (lib.gvariant.type), both the section and its members show up in `locations.json`
|
||||
functions = [ f for f in functions if f not in sections ]
|
||||
|
||||
return Items(functions=sort_attrpaths(functions), sections=sort_attrpaths(sections))
|
||||
|
||||
def sort_attrpaths(ps: list[str]) -> list[str]:
|
||||
ps_expanded = sorted(p.split(".") for p in ps)
|
||||
return [ ".".join(p) for p in ps_expanded ]
|
||||
return items
|
||||
|
||||
def parent_attrpath(p: str) -> str:
|
||||
""" `lib.foo.bar` -> `lib.foo` """
|
||||
@@ -56,20 +83,35 @@ def init_db(db) -> None:
|
||||
db.execute("CREATE TABLE searchIndex(id INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT);")
|
||||
db.execute("CREATE UNIQUE INDEX anchor ON searchIndex (name, type, path);")
|
||||
|
||||
def register_item(db, attrpath: str, kind: str, path: str) -> None:
|
||||
def insert_row(db, attrpath: str, kind: str, path: str) -> None:
|
||||
logger.debug(f"register ({attrpath}, {kind}, {path})")
|
||||
db.execute("INSERT INTO searchIndex(name, type, path) values (?, ?, ?);", (attrpath, kind, path))
|
||||
|
||||
def register_function(db, attrpath: str) -> None:
|
||||
register_item(db, attrpath, kind="Function", path=f"manual.html#function-library-{attrpath}")
|
||||
|
||||
def register_section(db, attrpath: str) -> None:
|
||||
library = chomp_attrpath(attrpath)
|
||||
if library == "":
|
||||
def insert_item(db, item: Item) -> None:
|
||||
# TODO: stop hardcoding the .html filename; maybe even the anchor prefix
|
||||
if item.attrpath.startswith("lib."):
|
||||
if item.type == ItemType.SECTION:
|
||||
library = chomp_attrpath(item.attrpath)
|
||||
path = f"manual.html#sec-functions-library-{library}"
|
||||
elif item.type == ItemType.CONSTANT:
|
||||
assert False, f"ItemType.CONSTANT should not exist in nixpkgs.lib; item: {item}"
|
||||
elif item.type == ItemType.FUNCTION:
|
||||
path=f"manual.html#function-library-{item.attrpath}"
|
||||
else:
|
||||
assert False, f"unexpected item type for item {item}"
|
||||
elif item.attrpath == "lib":
|
||||
path = "manual.html#sec-functions-library"
|
||||
else:
|
||||
path = f"manual.html#sec-functions-library-{library}"
|
||||
register_item(db, attrpath, kind="Property", path=path)
|
||||
path = f"language/builtins.html#builtins-{item.attrpath}"
|
||||
|
||||
if item.type == ItemType.SECTION:
|
||||
kind = "Property" # TODO: should it be `namespace`?
|
||||
elif item.type == ItemType.CONSTANT:
|
||||
kind = "Constant"
|
||||
elif item.type == ItemType.FUNCTION:
|
||||
kind = "Function"
|
||||
|
||||
insert_row(db, item.attrpath, kind=kind, path=path)
|
||||
|
||||
def main() -> None:
|
||||
logging.basicConfig()
|
||||
@@ -90,11 +132,8 @@ def main() -> None:
|
||||
db = conn.cursor()
|
||||
init_db(db)
|
||||
|
||||
for section in items.sections:
|
||||
register_section(db, section)
|
||||
|
||||
for fn in items.functions:
|
||||
register_function(db, fn)
|
||||
for item in items.all():
|
||||
insert_item(db, item)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
16
pkgs/by-name/docsets/nix-builtins/Info.plist
Normal file
16
pkgs/by-name/docsets/nix-builtins/Info.plist
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>nix-builtins</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>nix.builtins</string>
|
||||
<key>DocSetPlatformFamily</key>
|
||||
<string>nix</string>
|
||||
<key>isDashDocset</key>
|
||||
<true/>
|
||||
<key>dashIndexFilePath</key>
|
||||
<string>builtins.html</string>
|
||||
</dict>
|
||||
</plist>
|
55
pkgs/by-name/docsets/nix-builtins/package.nix
Normal file
55
pkgs/by-name/docsets/nix-builtins/package.nix
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
docsets,
|
||||
jq,
|
||||
nix,
|
||||
runCommandLocal,
|
||||
stdenv,
|
||||
}:
|
||||
let
|
||||
# nix has logic to build an attrset of all the items which make it into `nix.doc`
|
||||
# this is a json dictionary with each entry like:
|
||||
# "abort": { "args": [ "s" ], "doc": "Abort Nix expression evaluation and print the error message *s*." }
|
||||
|
||||
builtins-locations = runCommandLocal "nix-locations.json" {
|
||||
nativeBuildInputs = [ jq nix ];
|
||||
} ''
|
||||
mkdir $out
|
||||
nix __dump-language | jq . > $out/locations.json
|
||||
'';
|
||||
|
||||
docset = stdenv.mkDerivation {
|
||||
pname = "nix-builtins";
|
||||
version = nix.version;
|
||||
|
||||
nativeBuildInputs = [ docsets.make-docset-index ];
|
||||
|
||||
unpackPhase = ''
|
||||
cp ${./Info.plist} Info.plist
|
||||
cp ${builtins-locations}/locations.json locations.json
|
||||
cp -R ${nix.doc}/share/doc/nix/manual nix-manual
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
mkdir -p nix-builtins.docset/Contents/Resources/
|
||||
cp Info.plist nix-builtins.docset/Contents/
|
||||
cp -R nix-manual nix-builtins.docset/Contents/Resources/Documents
|
||||
|
||||
make-docset-index --verbose locations.json --output nix-builtins.docset/Contents/Resources/docSet.dsidx
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
# docsets are usually distributed as .tgz, but compression is actually optional at least for tools like `dasht`
|
||||
installPhase = ''
|
||||
mkdir -p $out/share/docsets
|
||||
cp -R *.docset $out/share/docsets/
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
inherit builtins-locations;
|
||||
};
|
||||
};
|
||||
in
|
||||
docset
|
@@ -45,7 +45,7 @@ let
|
||||
# docsets are usually distributed as .tgz, but compression is actually optional at least for tools like `dasht`
|
||||
installPhase = ''
|
||||
mkdir -p $out/share/docsets
|
||||
cp -R nixpkgs-lib.docset $out/share/docsets/nixpkgs-lib.docset
|
||||
cp -R *.docset $out/share/docsets/
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
|
Reference in New Issue
Block a user