1{ 2 lib, 3 buildPythonPackage, 4 setuptools, 5 pytestCheckHook, 6 tree-sitter, 7 symlinkJoin, 8 writeTextDir, 9 pythonOlder, 10 # `name`: grammar derivation pname in the format of `tree-sitter-<lang>` 11 name, 12 grammarDrv, 13}: 14let 15 inherit (grammarDrv) version; 16 17 snakeCaseName = lib.replaceStrings [ "-" ] [ "_" ] name; 18 drvPrefix = "python-${name}"; 19 # If the name of the grammar attribute differs from the grammar's symbol name, 20 # it could cause a symbol mismatch at load time. This manually curated collection 21 # of overrides ensures the binding can find the correct symbol 22 langIdentOverrides = { 23 tree_sitter_org_nvim = "tree_sitter_org"; 24 }; 25 langIdent = langIdentOverrides.${snakeCaseName} or snakeCaseName; 26in 27buildPythonPackage { 28 inherit version; 29 pname = drvPrefix; 30 pyproject = true; 31 build-system = [ setuptools ]; 32 33 src = symlinkJoin { 34 name = "${drvPrefix}-source"; 35 paths = [ 36 (writeTextDir "${snakeCaseName}/__init__.py" '' 37 # AUTO-GENERATED DO NOT EDIT 38 39 # preload the parser object before importing c binding 40 # this way we can avoid dynamic linker kicking in when 41 # downstream code imports this python module 42 import ctypes 43 import sys 44 import os 45 parser = "${grammarDrv}/parser" 46 try: 47 ctypes.CDLL(parser, mode=ctypes.RTLD_GLOBAL) # cached 48 except OSError as e: 49 raise ImportError(f"cannot load tree-sitter parser object from {parser}: {e}") 50 51 # expose binding 52 from ._binding import language 53 __all__ = ["language"] 54 '') 55 (writeTextDir "${snakeCaseName}/binding.c" '' 56 // AUTO-GENERATED DO NOT EDIT 57 58 #include <Python.h> 59 60 typedef struct TSLanguage TSLanguage; 61 62 TSLanguage *${langIdent}(void); 63 64 static PyObject* _binding_language(PyObject *self, PyObject *args) { 65 return PyLong_FromVoidPtr(${langIdent}()); 66 } 67 68 static PyMethodDef methods[] = { 69 {"language", _binding_language, METH_NOARGS, 70 "Get the tree-sitter language for this grammar."}, 71 {NULL, NULL, 0, NULL} 72 }; 73 74 static struct PyModuleDef module = { 75 .m_base = PyModuleDef_HEAD_INIT, 76 .m_name = "_binding", 77 .m_doc = NULL, 78 .m_size = -1, 79 .m_methods = methods 80 }; 81 82 PyMODINIT_FUNC PyInit__binding(void) { 83 return PyModule_Create(&module); 84 } 85 '') 86 (writeTextDir "setup.py" '' 87 # AUTO-GENERATED DO NOT EDIT 88 89 from platform import system 90 from setuptools import Extension, setup 91 92 93 setup( 94 packages=["${snakeCaseName}"], 95 ext_package="${snakeCaseName}", 96 ext_modules=[ 97 Extension( 98 name="_binding", 99 sources=["${snakeCaseName}/binding.c"], 100 extra_objects = ["${grammarDrv}/parser"], 101 extra_compile_args=( 102 ["-std=c11"] if system() != 'Windows' else [] 103 ), 104 ) 105 ], 106 ) 107 '') 108 (writeTextDir "pyproject.toml" '' 109 # AUTO-GENERATED DO NOT EDIT 110 111 [build-system] 112 requires = ["setuptools", "wheel"] 113 build-backend = "setuptools.build_meta" 114 115 [project] 116 name="${snakeCaseName}" 117 description = "${langIdent} grammar for tree-sitter" 118 version = "${version}" 119 keywords = ["parsing", "incremental", "python"] 120 classifiers = [ 121 "Development Status :: 4 - Beta", 122 "Intended Audience :: Developers", 123 "Topic :: Software Development :: Compilers", 124 "Topic :: Text Processing :: Linguistic", 125 ] 126 127 requires-python = ">=3.8" 128 license = "MIT" 129 readme = "README.md" 130 131 [project.optional-dependencies] 132 core = ["tree-sitter~=0.21"] 133 134 [tool.cibuildwheel] 135 build = "cp38-*" 136 build-frontend = "build" 137 '') 138 (writeTextDir "tests/test_language.py" '' 139 # AUTO-GENERATED DO NOT EDIT 140 141 from ${snakeCaseName} import language 142 from tree_sitter import Language, Parser 143 144 # This test only checks that the binding can load the grammar from the compiled shared object. 145 # It does not verify the grammar itself; that is tested in 146 # `pkgs/development/tools/parsing/tree-sitter/grammar.nix`. 147 148 def test_language(): 149 lang = Language(language()) 150 assert lang is not None 151 parser = Parser(lang) 152 tree = parser.parse(bytes("", "utf-8")) 153 assert tree is not None 154 '') 155 ]; 156 }; 157 158 preCheck = '' 159 # https://github.com/NixOS/nixpkgs/issues/255262 160 rm -r ${snakeCaseName} 161 ''; 162 163 disabled = pythonOlder "3.8"; 164 165 nativeCheckInputs = [ 166 tree-sitter 167 pytestCheckHook 168 ]; 169 170 pythonImportsCheck = [ snakeCaseName ]; 171 172 meta = { 173 description = "Python bindings for ${name}"; 174 license = lib.licenses.mit; 175 maintainers = with lib.maintainers; [ 176 a-jay98 177 adfaure 178 mightyiam 179 stepbrobd 180 ]; 181 }; 182}