import argparse import os import subprocess import sys from tempfile import TemporaryDirectory from typing import Tuple, Optional, List, Union # since os.PathLike breaks python typechecking path = bytes drv_output = Tuple[path, bytes] def add_root_args(gcroots_dir: Optional[bytes], symlink_name: path) -> List[Union[path, str]]: if gcroots_dir is None: return [] else: return ['--add-root', os.path.join(gcroots_dir, symlink_name), '--indirect'] def parse_drv_path(path: path) -> Optional[drv_output]: (nix_store, drv_file) = os.path.split(path) # drv “filenames” printed by nix-instantiate are of the form # ${hash}-${name}.drv[!${output}] where name, hash and output # may not contain any '!' among other things drv_parts = drv_file.split(b'!') if len(drv_parts) > 2: return None elif len(drv_parts) == 1: # default output return (path, b'out') else: return (os.path.join(nix_store, drv_parts[0]), drv_parts[1]) def derivation_outputs(attr: str, gcroots_dir: Optional[path] = None ) -> List[drv_output]: cmd: List[Union[str, path]] = ['nix-instantiate', '-Q', '-E'] # TODO(sterni): make this work with custom stuff like vuizuvi and depot expr = """ let pkgs = import {{}}; in builtins.map (o: pkgs.{attr}."${{o}}") pkgs.{attr}.outputs """.format(attr=attr) cmd.append(expr) cmd.extend(add_root_args(gcroots_dir, b'instantiation-result')) instantiate = subprocess.run(cmd, capture_output=True) if instantiate.returncode != 0: print(instantiate.stderr, file=sys.stderr) return [] else: return [o for o in map(parse_drv_path, instantiate.stdout.splitlines()) if o is not None] def main(): parser = argparse.ArgumentParser(description='nix-shell for man pages') parser.add_argument('attribute', metavar='ATTR', help='nixpkgs attribute holding the desired package') parser.add_argument('section_or_page', metavar='SECTION|PAGE', nargs='?') parser.add_argument('page', metavar='PAGE', nargs='?') args = parser.parse_args() # first arg is always the derivation attribute drv_attr = args.attribute # second arg is either the page or the section number if args.section_or_page is not None and args.section_or_page.isdigit(): section = int(args.section_or_page) page = None else: section = 1 page = args.section_or_page # last arg is the page. if it is missing and was not # given as the second, use drv_attr if args.page is not None: page = args.page elif page is None: page = drv_attr print('{}({}) from pkgs.{}'.format(page, section, drv_attr)) with TemporaryDirectory(prefix='nman-') as gcroots: outputs = derivation_outputs(drv_attr, gcroots_dir=gcroots.encode('utf-8')) for (p, o) in outputs: print('{}: {}'.format(o, os.path.realpath(p))) if __name__ == '__main__': main()