docsets.nix-builtins: init
This commit is contained in:
@@ -60,6 +60,7 @@ in {
|
|||||||
];
|
];
|
||||||
sane.programs.docsets.config.pkgs = with pkgs; [
|
sane.programs.docsets.config.pkgs = with pkgs; [
|
||||||
# packages which ship docsets natively:
|
# packages which ship docsets natively:
|
||||||
|
docsets.nix-builtins
|
||||||
docsets.nixpkgs-lib
|
docsets.nixpkgs-lib
|
||||||
docsets.rust-std
|
docsets.rust-std
|
||||||
] ++ lib.map
|
] ++ lib.map
|
||||||
|
@@ -4,43 +4,70 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import re
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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
|
@dataclass
|
||||||
class Items:
|
class Items:
|
||||||
functions: list[str]
|
items: dict[str, Item]
|
||||||
sections: list[str]
|
|
||||||
|
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:
|
def read_items(locations_json: Path) -> Items:
|
||||||
# TODO: this seems to yield some items which don't have documentation? like `lib.gvariant.type`
|
# 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:
|
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)
|
item = Item(attrpath=attrpath, type=type)
|
||||||
while True:
|
items.add(item)
|
||||||
expanded_sections = sections.union(parent_attrpath(s) for s in sections)
|
|
||||||
if expanded_sections == sections:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
sections = expanded_sections
|
|
||||||
|
|
||||||
# the above algorithm includes a nameless root section `""`: remove that
|
while any(items.add(i.parent()) for i in items.all() if i.parent()):
|
||||||
sections = [ s for s in sections if s ]
|
pass
|
||||||
|
|
||||||
# for some cases (lib.gvariant.type), both the section and its members show up in `locations.json`
|
return items
|
||||||
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 ]
|
|
||||||
|
|
||||||
def parent_attrpath(p: str) -> str:
|
def parent_attrpath(p: str) -> str:
|
||||||
""" `lib.foo.bar` -> `lib.foo` """
|
""" `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 TABLE searchIndex(id INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT);")
|
||||||
db.execute("CREATE UNIQUE INDEX anchor ON searchIndex (name, type, path);")
|
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})")
|
logger.debug(f"register ({attrpath}, {kind}, {path})")
|
||||||
db.execute("INSERT INTO searchIndex(name, type, path) values (?, ?, ?);", (attrpath, kind, path))
|
db.execute("INSERT INTO searchIndex(name, type, path) values (?, ?, ?);", (attrpath, kind, path))
|
||||||
|
|
||||||
def register_function(db, attrpath: str) -> None:
|
def insert_item(db, item: Item) -> None:
|
||||||
register_item(db, attrpath, kind="Function", path=f"manual.html#function-library-{attrpath}")
|
# TODO: stop hardcoding the .html filename; maybe even the anchor prefix
|
||||||
|
if item.attrpath.startswith("lib."):
|
||||||
def register_section(db, attrpath: str) -> None:
|
if item.type == ItemType.SECTION:
|
||||||
library = chomp_attrpath(attrpath)
|
library = chomp_attrpath(item.attrpath)
|
||||||
if library == "":
|
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"
|
path = "manual.html#sec-functions-library"
|
||||||
else:
|
else:
|
||||||
path = f"manual.html#sec-functions-library-{library}"
|
path = f"language/builtins.html#builtins-{item.attrpath}"
|
||||||
register_item(db, attrpath, kind="Property", path=path)
|
|
||||||
|
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:
|
def main() -> None:
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
@@ -90,11 +132,8 @@ def main() -> None:
|
|||||||
db = conn.cursor()
|
db = conn.cursor()
|
||||||
init_db(db)
|
init_db(db)
|
||||||
|
|
||||||
for section in items.sections:
|
for item in items.all():
|
||||||
register_section(db, section)
|
insert_item(db, item)
|
||||||
|
|
||||||
for fn in items.functions:
|
|
||||||
register_function(db, fn)
|
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
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`
|
# docsets are usually distributed as .tgz, but compression is actually optional at least for tools like `dasht`
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
mkdir -p $out/share/docsets
|
mkdir -p $out/share/docsets
|
||||||
cp -R nixpkgs-lib.docset $out/share/docsets/nixpkgs-lib.docset
|
cp -R *.docset $out/share/docsets/
|
||||||
'';
|
'';
|
||||||
|
|
||||||
passthru = {
|
passthru = {
|
||||||
|
Reference in New Issue
Block a user