From 53e1ee2036ecf3bc8c1ccdb55c6517e16fcaebc4 Mon Sep 17 00:00:00 2001 From: Daniel Hanson Date: Wed, 16 Sep 2026 14:26:03 -0500 Subject: [PATCH] add bindings and fix documentation --- .editorconfig | 50 +++ .gitattributes | 46 +++ .gitignore | 29 +- CLAUDE.md | 25 ++ CMakeLists.txt | 76 ++++ Cargo.toml | 34 ++ Makefile | 116 ++++++ Package.swift | 41 +++ README.md | 34 +- binding.gyp | 35 ++ bindings/c/tree-sitter-django.pc.in | 10 + bindings/c/tree_sitter/tree-sitter-django.h | 16 + bindings/go/binding.go | 15 + bindings/go/binding_test.go | 15 + bindings/node/binding.cc | 19 + bindings/node/binding_test.js | 11 + bindings/node/index.d.ts | 60 ++++ bindings/node/index.js | 37 ++ bindings/python/tests/test_binding.py | 12 + .../python/tree_sitter_django/__init__.py | 43 +++ .../python/tree_sitter_django/__init__.pyi | 17 + bindings/python/tree_sitter_django/binding.c | 35 ++ bindings/python/tree_sitter_django/py.typed | 0 bindings/rust/build.rs | 56 +++ bindings/rust/lib.rs | 60 ++++ bindings/swift/TreeSitterDjango/django.h | 16 + .../TreeSitterDjangoTests.swift | 12 + go.mod | 7 + go.sum | 36 ++ package-lock.json | 340 ++++++++++++++++++ package.json | 45 ++- pyproject.toml | 29 ++ setup.py | 78 ++++ tree-sitter.json | 29 +- 34 files changed, 1472 insertions(+), 12 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 CMakeLists.txt create mode 100644 Cargo.toml create mode 100644 Makefile create mode 100644 Package.swift create mode 100644 binding.gyp create mode 100644 bindings/c/tree-sitter-django.pc.in create mode 100644 bindings/c/tree_sitter/tree-sitter-django.h create mode 100644 bindings/go/binding.go create mode 100644 bindings/go/binding_test.go create mode 100644 bindings/node/binding.cc create mode 100644 bindings/node/binding_test.js create mode 100644 bindings/node/index.d.ts create mode 100644 bindings/node/index.js create mode 100644 bindings/python/tests/test_binding.py create mode 100644 bindings/python/tree_sitter_django/__init__.py create mode 100644 bindings/python/tree_sitter_django/__init__.pyi create mode 100644 bindings/python/tree_sitter_django/binding.c create mode 100644 bindings/python/tree_sitter_django/py.typed create mode 100644 bindings/rust/build.rs create mode 100644 bindings/rust/lib.rs create mode 100644 bindings/swift/TreeSitterDjango/django.h create mode 100644 bindings/swift/TreeSitterDjangoTests/TreeSitterDjangoTests.swift create mode 100644 go.mod create mode 100644 go.sum create mode 100644 pyproject.toml create mode 100644 setup.py diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ff17b12 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,50 @@ +root = true + +[*] +charset = utf-8 + +[*.{json,toml,yml,gyp,xml}] +indent_style = space +indent_size = 2 + +[*.{js,ts}] +indent_style = space +indent_size = 2 + +[*.scm] +indent_style = space +indent_size = 2 + +[*.{c,cc,h}] +indent_style = space +indent_size = 4 + +[*.rs] +indent_style = space +indent_size = 4 + +[*.{py,pyi}] +indent_style = space +indent_size = 4 + +[*.swift] +indent_style = space +indent_size = 4 + +[*.java] +indent_style = space +indent_size = 4 + +[*.go] +indent_style = tab +indent_size = 8 + +[Makefile] +indent_style = tab +indent_size = 8 + +[parser.c] +indent_size = 2 + +[{alloc,array,parser}.h] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..027ac70 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,46 @@ +* text=auto eol=lf + +# Generated source files +src/*.json linguist-generated +src/parser.c linguist-generated +src/tree_sitter/* linguist-generated + +# C bindings +bindings/c/** linguist-generated +CMakeLists.txt linguist-generated +Makefile linguist-generated + +# Rust bindings +bindings/rust/* linguist-generated +Cargo.toml linguist-generated +Cargo.lock linguist-generated + +# Node.js bindings +bindings/node/* linguist-generated +binding.gyp linguist-generated +package.json linguist-generated +package-lock.json linguist-generated + +# Python bindings +bindings/python/** linguist-generated +setup.py linguist-generated +pyproject.toml linguist-generated + +# Go bindings +bindings/go/* linguist-generated +go.mod linguist-generated +go.sum linguist-generated + +# Swift bindings +bindings/swift/** linguist-generated +Package.swift linguist-generated +Package.resolved linguist-generated + +# Zig bindings +bindings/zig/* linguist-generated +build.zig linguist-generated +build.zig.zon linguist-generated + +# Java bindings +pom.xml linguist-generated +bindings/java/** linguist-generated diff --git a/.gitignore b/.gitignore index dd8addc..23f4c69 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,29 @@ node_modules -tree-sitter-django.wasm \ No newline at end of file +tree-sitter-django.wasm + +# Node binding +build/ +prebuilds/ +*.node + +# Rust binding +target/ +Cargo.lock + +# Python binding +dist/ +*.egg-info/ +__pycache__/ +*.so +*.pyd + +# C binding +*.o +*.a +*.dylib +libtree-sitter-django.* + +# Swift binding +.build/ +Package.resolved +*.pc diff --git a/CLAUDE.md b/CLAUDE.md index d6d7ff4..2776fdd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,6 +11,10 @@ npm run parser-generate # regenerate src/parser.c, grammar.json, node-types.js npm run parser-test # run the corpus tests npm run parser-build # build tree-sitter-django.wasm npm run playground # explore the grammar interactively +npm install # also builds the Node binding, through node-gyp-build +npm run binding-test # the Node binding's own test +cargo test # the Rust binding +make # the C library, and make clean to remove it npx tree-sitter test -i "Cycle With As" # one test, by name regex npx tree-sitter test --file-name cycle.txt # one corpus file @@ -30,6 +34,27 @@ back, producing churn in both directions. `npm run lint` does not work: `eslint.config.mjs` was copied from a Next.js project and enables `@typescript-eslint` and `react-hooks` rules whose plugins aren't installed. Formatting is prettier's job. +### Bindings + +`bindings/` and the manifests beside it — `binding.gyp`, `Cargo.toml`, `pyproject.toml`, `setup.py`, +`go.mod`, `Package.swift`, `Makefile`, `CMakeLists.txt` — are generated by `tree-sitter init` from the +`bindings` and `metadata` sections of `tree-sitter.json`, and `.gitattributes` marks them +`linguist-generated`. Do not edit them by hand: change `tree-sitter.json` and regenerate. + +`tree-sitter init --update` only rewrites what it considers outdated, and a changed `version` or +`description` does not count, so the manifests keep the old values until they are deleted and `init` is run +again. It leaves `README.md`, `package.json` and `.gitignore` alone, which is why the Node binding's +`package.json` entries — `main`/`types`, the `.` export, `files`, `node-addon-api`, `node-gyp-build`, the +`install` script and the optional `tree-sitter` peer — are maintained here rather than generated. + +Every binding was built and run on macOS: `npm install` plus `npm run binding-test`, `cargo test`, +`go test ./bindings/go/`, `pip install .` with a parse, `swift build`, and `make`. The Go binding needs a +`go.sum`, which `go mod tidy` writes and which is committed with the rest. + +`src/scanner.c` compiles without warnings, and should stay that way: cc and clang warn about `&&` within +`||` (`-Wlogical-op-parentheses`), so a comparison chain written with both needs its `&&` groups +parenthesized. + ### Stale parser library tree-sitter caches the compiled parser in `~/.cache/tree-sitter/lib/django.*`, and it has served a stale diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5e718e0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 3.13) + +project(tree-sitter-django + VERSION "0.1.0" + DESCRIPTION "Django grammar for tree-sitter" + HOMEPAGE_URL "https://github.com/danhanson/tree-sitter-django" + LANGUAGES C) + +option(BUILD_SHARED_LIBS "Build using shared libraries" ON) +option(TREE_SITTER_REUSE_ALLOCATOR "Reuse the library allocator" OFF) + +set(TREE_SITTER_ABI_VERSION 15 CACHE STRING "Tree-sitter ABI version") +if(NOT ${TREE_SITTER_ABI_VERSION} MATCHES "^[0-9]+$") + unset(TREE_SITTER_ABI_VERSION CACHE) + message(FATAL_ERROR "TREE_SITTER_ABI_VERSION must be an integer") +endif() + +include(GNUInstallDirs) + +find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI") + +add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json" + "${CMAKE_CURRENT_SOURCE_DIR}/src/node-types.json" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/grammar.js" + COMMAND "${TREE_SITTER_CLI}" generate grammar.js --no-parser + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Generating grammar.json") + +add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c" + BYPRODUCTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/parser.h" + "${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/alloc.h" + "${CMAKE_CURRENT_SOURCE_DIR}/src/tree_sitter/array.h" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json" + COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json + --abi=${TREE_SITTER_ABI_VERSION} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Generating parser.c") + +add_library(tree-sitter-django src/parser.c) +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/scanner.c) + target_sources(tree-sitter-django PRIVATE src/scanner.c) +endif() +target_include_directories(tree-sitter-django + PRIVATE src + INTERFACE $ + $) + +target_compile_definitions(tree-sitter-django PRIVATE + $<$:TREE_SITTER_REUSE_ALLOCATOR> + $<$:TREE_SITTER_DEBUG>) + +set_target_properties(tree-sitter-django + PROPERTIES + C_STANDARD 11 + POSITION_INDEPENDENT_CODE ON + SOVERSION "${TREE_SITTER_ABI_VERSION}.${PROJECT_VERSION_MAJOR}" + DEFINE_SYMBOL "") + +configure_file(bindings/c/tree-sitter-django.pc.in + "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-django.pc" @ONLY) + +install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bindings/c/tree_sitter" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + FILES_MATCHING PATTERN "*.h") +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tree-sitter-django.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") +install(TARGETS tree-sitter-django + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}") + +file(GLOB QUERIES queries/*.scm) +install(FILES ${QUERIES} + DESTINATION "${CMAKE_INSTALL_DATADIR}/tree-sitter/queries/django") + +add_custom_target(ts-test "${TREE_SITTER_CLI}" test + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "tree-sitter test") diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..df7bc29 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "tree-sitter-django" +description = "Django grammar for tree-sitter" +version = "0.1.0" +authors = ["Daniel Hanson"] +license = "MIT" +readme = "README.md" +keywords = ["incremental", "parsing", "tree-sitter", "django"] +categories = ["parser-implementations", "parsing", "text-editors"] +repository = "https://github.com/danhanson/tree-sitter-django" +edition = "2021" +autoexamples = false + +build = "bindings/rust/build.rs" +include = [ + "bindings/rust/*", + "grammar.js", + "queries/*", + "src/*", + "tree-sitter.json", + "/LICENSE", +] + +[lib] +path = "bindings/rust/lib.rs" + +[dependencies] +tree-sitter-language = "0.1" + +[build-dependencies] +cc = "1.2" + +[dev-dependencies] +tree-sitter = "0.26.12" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b9c2886 --- /dev/null +++ b/Makefile @@ -0,0 +1,116 @@ +LANGUAGE_NAME := tree-sitter-django +HOMEPAGE_URL := https://github.com/danhanson/tree-sitter-django +VERSION := 0.1.0 +DESCRIPTION := Django grammar for tree-sitter + +# repository +SRC_DIR := src + +TS ?= tree-sitter + +# install directory layout +PREFIX ?= /usr/local +DATADIR ?= $(PREFIX)/share +INCLUDEDIR ?= $(PREFIX)/include +LIBDIR ?= $(PREFIX)/lib +BINDIR ?= $(PREFIX)/bin +PCLIBDIR ?= $(LIBDIR)/pkgconfig + +# source/object files +PARSER := $(SRC_DIR)/parser.c +EXTRAS := $(filter-out $(PARSER),$(wildcard $(SRC_DIR)/*.c)) +OBJS := $(patsubst %.c,%.o,$(PARSER) $(EXTRAS)) + +# flags +ARFLAGS ?= rcs +override CFLAGS += -I$(SRC_DIR) -std=c11 -fPIC + +# ABI versioning +SONAME_MAJOR = $(shell sed -n 's/\#define LANGUAGE_VERSION //p' $(PARSER)) +SONAME_MINOR = $(word 1,$(subst ., ,$(VERSION))) + +# OS-specific bits +MACHINE := $(shell $(CC) -dumpmachine) + +ifneq ($(findstring darwin,$(MACHINE)),) + SOEXT = dylib + SOEXTVER_MAJOR = $(SONAME_MAJOR).$(SOEXT) + SOEXTVER = $(SONAME_MAJOR).$(SONAME_MINOR).$(SOEXT) + LINKSHARED = -dynamiclib -Wl,-install_name,$(LIBDIR)/lib$(LANGUAGE_NAME).$(SOEXTVER),-rpath,@executable_path/../Frameworks +else ifneq ($(findstring mingw32,$(MACHINE)),) + SOEXT = dll + LINKSHARED += -s -shared -Wl,--out-implib,lib$(LANGUAGE_NAME).dll.a +else + SOEXT = so + SOEXTVER_MAJOR = $(SOEXT).$(SONAME_MAJOR) + SOEXTVER = $(SOEXT).$(SONAME_MAJOR).$(SONAME_MINOR) + LINKSHARED = -shared -Wl,-soname,lib$(LANGUAGE_NAME).$(SOEXTVER) +ifneq ($(filter $(shell uname),FreeBSD NetBSD DragonFly),) + PCLIBDIR := $(PREFIX)/libdata/pkgconfig +endif +endif + +all: lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) $(LANGUAGE_NAME).pc + +lib$(LANGUAGE_NAME).a: $(OBJS) + $(AR) $(ARFLAGS) $@ $^ + +lib$(LANGUAGE_NAME).$(SOEXT): $(OBJS) + $(CC) $(LDFLAGS) $(LINKSHARED) $^ $(LDLIBS) -o $@ +ifneq ($(STRIP),) + $(STRIP) $@ +endif + +ifneq ($(findstring mingw32,$(MACHINE)),) +lib$(LANGUAGE_NAME).dll.a: lib$(LANGUAGE_NAME).$(SOEXT) +endif + +$(LANGUAGE_NAME).pc: bindings/c/$(LANGUAGE_NAME).pc.in + sed -e 's|@PROJECT_VERSION@|$(VERSION)|' \ + -e 's|@CMAKE_INSTALL_LIBDIR@|$(LIBDIR:$(PREFIX)/%=%)|' \ + -e 's|@CMAKE_INSTALL_INCLUDEDIR@|$(INCLUDEDIR:$(PREFIX)/%=%)|' \ + -e 's|@PROJECT_DESCRIPTION@|$(DESCRIPTION)|' \ + -e 's|@PROJECT_HOMEPAGE_URL@|$(HOMEPAGE_URL)|' \ + -e 's|@CMAKE_INSTALL_PREFIX@|$(PREFIX)|' $< > $@ + +$(SRC_DIR)/grammar.json: grammar.js + $(TS) generate --no-parser $^ + +$(PARSER): $(SRC_DIR)/grammar.json + $(TS) generate $^ + +install: all + install -d '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/django '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)' + install -m644 bindings/c/tree_sitter/$(LANGUAGE_NAME).h '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h + install -m644 $(LANGUAGE_NAME).pc '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc + install -m644 lib$(LANGUAGE_NAME).a '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a + install -m755 lib$(LANGUAGE_NAME).$(SOEXT) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) +ifneq ($(findstring mingw32,$(MACHINE)),) + install -d '$(DESTDIR)$(BINDIR)' + install -m755 lib$(LANGUAGE_NAME).dll '$(DESTDIR)$(BINDIR)'/lib$(LANGUAGE_NAME).dll + install -m755 lib$(LANGUAGE_NAME).dll.a '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).dll.a +else + install -m755 lib$(LANGUAGE_NAME).$(SOEXT) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) + cd '$(DESTDIR)$(LIBDIR)' && ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER) lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) + cd '$(DESTDIR)$(LIBDIR)' && ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) lib$(LANGUAGE_NAME).$(SOEXT) +endif +ifneq ($(wildcard queries/*.scm),) + install -m644 queries/*.scm '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/django +endif + +uninstall: + $(RM) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a \ + '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) \ + '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) \ + '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT) \ + '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h \ + '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc + $(RM) -r '$(DESTDIR)$(DATADIR)'/tree-sitter/queries/django + +clean: + $(RM) $(OBJS) $(LANGUAGE_NAME).pc lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) lib$(LANGUAGE_NAME).dll.a + +test: + $(TS) test + +.PHONY: all install uninstall clean test diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..ea757bb --- /dev/null +++ b/Package.swift @@ -0,0 +1,41 @@ +// swift-tools-version:5.3 + +import Foundation +import PackageDescription + +var sources = ["src/parser.c"] +if FileManager.default.fileExists(atPath: "src/scanner.c") { + sources.append("src/scanner.c") +} + +let package = Package( + name: "TreeSitterDjango", + products: [ + .library(name: "TreeSitterDjango", targets: ["TreeSitterDjango"]), + ], + dependencies: [ + .package(url: "https://github.com/tree-sitter/swift-tree-sitter", from: "0.10.0"), + ], + targets: [ + .target( + name: "TreeSitterDjango", + dependencies: [], + path: ".", + sources: sources, + resources: [ + .copy("queries") + ], + publicHeadersPath: "bindings/swift", + cSettings: [.headerSearchPath("src")] + ), + .testTarget( + name: "TreeSitterDjangoTests", + dependencies: [ + .product(name: "SwiftTreeSitter", package: "swift-tree-sitter"), + "TreeSitterDjango", + ], + path: "bindings/swift/TreeSitterDjangoTests" + ) + ], + cLanguageStandard: .c11 +) diff --git a/README.md b/README.md index a993707..71dca88 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ disagree is listed under [Divergences](#divergences-from-django). ## Using it -There is no published package yet, and no language bindings — those are coming. What is here today: +There is no published package yet, but the language bindings are in place. What is here today: - **`src/parser.c` and `src/scanner.c` are generated and committed**, so any tree-sitter host can compile them directly without running the CLI. The scanner is required: block-name matching @@ -30,6 +30,38 @@ There is no published package yet, and no language bindings — those are coming meant for a linter. See [Queries](#queries). - **`tree-sitter.json`** names the grammar `django`, scopes it `source.django`, and points at the query files, which is what a tree-sitter host reads to wire all of the above together. +- **`bindings/`** holds a binding per language, generated by `tree-sitter init` from the `bindings` and + `metadata` sections of `tree-sitter.json`. See [Bindings](#bindings). + +## Bindings + +| Language | Build from a checkout | Entry point | +| -------- | -------------------------------------------------------- | ------------------------------------------------------ | +| Node | `npm install` builds the addon through `node-gyp-build` | `bindings/node/index.js` (ESM), typed by `index.d.ts` | +| Rust | `cargo build` | `bindings/rust/lib.rs`, `Cargo.toml` | +| Python | `pip install .` | `bindings/python/tree_sitter_django`, `pyproject.toml` | +| Go | `go test ./bindings/go` | `bindings/go/binding.go`, `go.mod` | +| Swift | `swift build` | `bindings/swift/TreeSitterDjango`, `Package.swift` | +| C / C++ | `make` (static, shared and a pkg-config file) or `cmake` | `bindings/c/tree_sitter/tree-sitter-django.h` | + +```js +import Parser from "tree-sitter"; +import django from "tree-sitter-django"; + +const parser = new Parser(); +parser.setLanguage(django); +``` + +```python +import tree_sitter, tree_sitter_django + +language = tree_sitter.Language(tree_sitter_django.language()) +tree = tree_sitter.Parser(language).parse(b"{% if x %}hi {{ y|upper }}{% endif %}") +``` + +The binding files are generated, so they are not edited by hand: change `tree-sitter.json` and re-run +`tree-sitter init`. `package.json` still carries `"private": true`, so nothing is published from this +repository yet. ## Development diff --git a/binding.gyp b/binding.gyp new file mode 100644 index 0000000..8f63fc0 --- /dev/null +++ b/binding.gyp @@ -0,0 +1,35 @@ +{ + "targets": [ + { + "target_name": "tree_sitter_django_binding", + "dependencies": [ + " + +typedef struct TSLanguage TSLanguage; + +extern "C" TSLanguage *tree_sitter_django(); + +// "tree-sitter", "language" hashed with BLAKE2 +const napi_type_tag LANGUAGE_TYPE_TAG = { + 0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16 +}; + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + auto language = Napi::External::New(env, tree_sitter_django()); + language.TypeTag(&LANGUAGE_TYPE_TAG); + exports["language"] = language; + return exports; +} + +NODE_API_MODULE(tree_sitter_django_binding, Init) diff --git a/bindings/node/binding_test.js b/bindings/node/binding_test.js new file mode 100644 index 0000000..7a91a84 --- /dev/null +++ b/bindings/node/binding_test.js @@ -0,0 +1,11 @@ +import assert from "node:assert"; +import { test } from "node:test"; +import Parser from "tree-sitter"; + +test("can load grammar", () => { + const parser = new Parser(); + assert.doesNotReject(async () => { + const { default: language } = await import("./index.js"); + parser.setLanguage(language); + }); +}); diff --git a/bindings/node/index.d.ts b/bindings/node/index.d.ts new file mode 100644 index 0000000..ac9174e --- /dev/null +++ b/bindings/node/index.d.ts @@ -0,0 +1,60 @@ +type BaseNode = { + type: string; + named: boolean; +}; + +type ChildNode = { + multiple: boolean; + required: boolean; + types: BaseNode[]; +}; + +type NodeInfo = + | (BaseNode & { + subtypes: BaseNode[]; + }) + | (BaseNode & { + fields: { [name: string]: ChildNode }; + children: ChildNode[]; + }); + +/** + * The tree-sitter language object for this grammar. + * + * @see {@linkcode https://tree-sitter.github.io/node-tree-sitter/interfaces/Parser.Language.html Parser.Language} + * + * @example + * import Parser from "tree-sitter"; + * import django from "tree-sitter-django"; + * + * const parser = new Parser(); + * parser.setLanguage(django); + */ +declare const binding: { + /** + * The inner language object. + * @private + */ + language: unknown; + + /** + * The content of the `node-types.json` file for this grammar. + * + * @see {@linkplain https://tree-sitter.github.io/tree-sitter/using-parsers/6-static-node-types Static Node Types} + */ + nodeTypeInfo: NodeInfo[]; + + /** The syntax highlighting query for this grammar. */ + HIGHLIGHTS_QUERY?: string; + + /** The language injection query for this grammar. */ + INJECTIONS_QUERY?: string; + + /** The local variable query for this grammar. */ + LOCALS_QUERY?: string; + + /** The symbol tagging query for this grammar. */ + TAGS_QUERY?: string; +}; + +export default binding; diff --git a/bindings/node/index.js b/bindings/node/index.js new file mode 100644 index 0000000..26c7c36 --- /dev/null +++ b/bindings/node/index.js @@ -0,0 +1,37 @@ +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; + +const root = fileURLToPath(new URL("../..", import.meta.url)); + +const binding = typeof process.versions.bun === "string" + // Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time + ? await import(`${root}/prebuilds/${process.platform}-${process.arch}/tree-sitter-django.node`) + : (await import("node-gyp-build")).default(root); + +try { + const nodeTypes = await import(`${root}/src/node-types.json`, { with: { type: "json" } }); + binding.nodeTypeInfo = nodeTypes.default; +} catch { } + +const queries = [ + ["HIGHLIGHTS_QUERY", `${root}/queries/highlights.scm`], + ["INJECTIONS_QUERY", `${root}/queries/injections.scm`], + ["LOCALS_QUERY", `${root}/queries/locals.scm`], + ["TAGS_QUERY", `${root}/queries/tags.scm`], +]; + +for (const [prop, path] of queries) { + Object.defineProperty(binding, prop, { + configurable: true, + enumerable: true, + get() { + delete binding[prop]; + try { + binding[prop] = readFileSync(path, "utf8"); + } catch { } + return binding[prop]; + } + }); +} + +export default binding; diff --git a/bindings/python/tests/test_binding.py b/bindings/python/tests/test_binding.py new file mode 100644 index 0000000..8e17fa6 --- /dev/null +++ b/bindings/python/tests/test_binding.py @@ -0,0 +1,12 @@ +from unittest import TestCase + +from tree_sitter import Language, Parser +import tree_sitter_django + + +class TestLanguage(TestCase): + def test_can_load_grammar(self): + try: + Parser(Language(tree_sitter_django.language())) + except Exception: + self.fail("Error loading Django Parser grammar") diff --git a/bindings/python/tree_sitter_django/__init__.py b/bindings/python/tree_sitter_django/__init__.py new file mode 100644 index 0000000..d03219e --- /dev/null +++ b/bindings/python/tree_sitter_django/__init__.py @@ -0,0 +1,43 @@ +"""Django grammar for tree-sitter""" + +from importlib.resources import files as _files + +from ._binding import language + + +def _get_query(name, file): + try: + query = _files(f"{__package__}") / file + globals()[name] = query.read_text() + except FileNotFoundError: + globals()[name] = None + return globals()[name] + + +def __getattr__(name): + if name == "HIGHLIGHTS_QUERY": + return _get_query("HIGHLIGHTS_QUERY", "queries/highlights.scm") + if name == "INJECTIONS_QUERY": + return _get_query("INJECTIONS_QUERY", "queries/injections.scm") + if name == "LOCALS_QUERY": + return _get_query("LOCALS_QUERY", "queries/locals.scm") + if name == "TAGS_QUERY": + return _get_query("TAGS_QUERY", "queries/tags.scm") + + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + + +__all__ = [ + "language", + "HIGHLIGHTS_QUERY", + "INJECTIONS_QUERY", + "LOCALS_QUERY", + "TAGS_QUERY", +] + + +def __dir__(): + return sorted(__all__ + [ + "__all__", "__builtins__", "__cached__", "__doc__", "__file__", + "__loader__", "__name__", "__package__", "__path__", "__spec__", + ]) diff --git a/bindings/python/tree_sitter_django/__init__.pyi b/bindings/python/tree_sitter_django/__init__.pyi new file mode 100644 index 0000000..5c88ff6 --- /dev/null +++ b/bindings/python/tree_sitter_django/__init__.pyi @@ -0,0 +1,17 @@ +from typing import Final +from typing_extensions import CapsuleType + +HIGHLIGHTS_QUERY: Final[str] | None +"""The syntax highlighting query for this grammar.""" + +INJECTIONS_QUERY: Final[str] | None +"""The language injection query for this grammar.""" + +LOCALS_QUERY: Final[str] | None +"""The local variable query for this grammar.""" + +TAGS_QUERY: Final[str] | None +"""The symbol tagging query for this grammar.""" + +def language() -> CapsuleType: + """The tree-sitter language function for this grammar.""" diff --git a/bindings/python/tree_sitter_django/binding.c b/bindings/python/tree_sitter_django/binding.c new file mode 100644 index 0000000..8f35cfa --- /dev/null +++ b/bindings/python/tree_sitter_django/binding.c @@ -0,0 +1,35 @@ +#include + +typedef struct TSLanguage TSLanguage; + +TSLanguage *tree_sitter_django(void); + +static PyObject* _binding_language(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args)) { + return PyCapsule_New(tree_sitter_django(), "tree_sitter.Language", NULL); +} + +static struct PyModuleDef_Slot slots[] = { +#ifdef Py_GIL_DISABLED + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, +#endif + {0, NULL} +}; + +static PyMethodDef methods[] = { + {"language", _binding_language, METH_NOARGS, + "Get the tree-sitter language for this grammar."}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef module = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "_binding", + .m_doc = NULL, + .m_size = 0, + .m_methods = methods, + .m_slots = slots, +}; + +PyMODINIT_FUNC PyInit__binding(void) { + return PyModuleDef_Init(&module); +} diff --git a/bindings/python/tree_sitter_django/py.typed b/bindings/python/tree_sitter_django/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/bindings/rust/build.rs b/bindings/rust/build.rs new file mode 100644 index 0000000..03dd89d --- /dev/null +++ b/bindings/rust/build.rs @@ -0,0 +1,56 @@ +fn main() { + let src_dir = std::path::Path::new("src"); + + let mut c_config = cc::Build::new(); + c_config.std("c11").include(src_dir); + + #[cfg(target_env = "msvc")] + c_config.flag("-utf-8"); + + if std::env::var("TARGET").unwrap() == "wasm32-unknown-unknown" { + let Ok(wasm_headers) = std::env::var("DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS") else { + panic!("Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_HEADERS must be set by the language crate"); + }; + let Ok(wasm_src) = + std::env::var("DEP_TREE_SITTER_LANGUAGE_WASM_SRC").map(std::path::PathBuf::from) + else { + panic!("Environment variable DEP_TREE_SITTER_LANGUAGE_WASM_SRC must be set by the language crate"); + }; + + c_config.include(&wasm_headers); + c_config.files([ + wasm_src.join("stdio.c"), + wasm_src.join("stdlib.c"), + wasm_src.join("string.c"), + ]); + } + + let parser_path = src_dir.join("parser.c"); + c_config.file(&parser_path); + println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap()); + + let scanner_path = src_dir.join("scanner.c"); + if scanner_path.exists() { + c_config.file(&scanner_path); + println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap()); + } + + c_config.compile("tree-sitter-django"); + + println!("cargo:rustc-check-cfg=cfg(with_highlights_query)"); + if !"queries/highlights.scm".is_empty() && std::path::Path::new("queries/highlights.scm").exists() { + println!("cargo:rustc-cfg=with_highlights_query"); + } + println!("cargo:rustc-check-cfg=cfg(with_injections_query)"); + if !"queries/injections.scm".is_empty() && std::path::Path::new("queries/injections.scm").exists() { + println!("cargo:rustc-cfg=with_injections_query"); + } + println!("cargo:rustc-check-cfg=cfg(with_locals_query)"); + if !"queries/locals.scm".is_empty() && std::path::Path::new("queries/locals.scm").exists() { + println!("cargo:rustc-cfg=with_locals_query"); + } + println!("cargo:rustc-check-cfg=cfg(with_tags_query)"); + if !"queries/tags.scm".is_empty() && std::path::Path::new("queries/tags.scm").exists() { + println!("cargo:rustc-cfg=with_tags_query"); + } +} diff --git a/bindings/rust/lib.rs b/bindings/rust/lib.rs new file mode 100644 index 0000000..61d1bcd --- /dev/null +++ b/bindings/rust/lib.rs @@ -0,0 +1,60 @@ +//! This crate provides Django Parser language support for the [tree-sitter] parsing library. +//! +//! Typically, you will use the [`LANGUAGE`] constant to add this language to a +//! tree-sitter [`Parser`], and then use the parser to parse some code: +//! +//! ``` +//! let code = r#" +//! "#; +//! let mut parser = tree_sitter::Parser::new(); +//! let language = tree_sitter_django::LANGUAGE; +//! parser +//! .set_language(&language.into()) +//! .expect("Error loading Django Parser parser"); +//! let tree = parser.parse(code, None).unwrap(); +//! assert!(!tree.root_node().has_error()); +//! ``` +//! +//! [`Parser`]: https://docs.rs/tree-sitter/0.26.12/tree_sitter/struct.Parser.html +//! [tree-sitter]: https://tree-sitter.github.io/ + +use tree_sitter_language::LanguageFn; + +extern "C" { + fn tree_sitter_django() -> *const (); +} + +/// The tree-sitter [`LanguageFn`] for this grammar. +pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_django) }; + +/// The content of the [`node-types.json`] file for this grammar. +/// +/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers/6-static-node-types +pub const NODE_TYPES: &str = include_str!("../../src/node-types.json"); + +#[cfg(with_highlights_query)] +/// The syntax highlighting query for this grammar. +pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm"); + +#[cfg(with_injections_query)] +/// The language injection query for this grammar. +pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm"); + +#[cfg(with_locals_query)] +/// The local variable query for this grammar. +pub const LOCALS_QUERY: &str = include_str!("../../queries/locals.scm"); + +#[cfg(with_tags_query)] +/// The symbol tagging query for this grammar. +pub const TAGS_QUERY: &str = include_str!("../../queries/tags.scm"); + +#[cfg(test)] +mod tests { + #[test] + fn test_can_load_grammar() { + let mut parser = tree_sitter::Parser::new(); + parser + .set_language(&super::LANGUAGE.into()) + .expect("Error loading Django Parser parser"); + } +} diff --git a/bindings/swift/TreeSitterDjango/django.h b/bindings/swift/TreeSitterDjango/django.h new file mode 100644 index 0000000..1fc093a --- /dev/null +++ b/bindings/swift/TreeSitterDjango/django.h @@ -0,0 +1,16 @@ +#ifndef TREE_SITTER_DJANGO_H_ +#define TREE_SITTER_DJANGO_H_ + +typedef struct TSLanguage TSLanguage; + +#ifdef __cplusplus +extern "C" { +#endif + +const TSLanguage *tree_sitter_django(void); + +#ifdef __cplusplus +} +#endif + +#endif // TREE_SITTER_DJANGO_H_ diff --git a/bindings/swift/TreeSitterDjangoTests/TreeSitterDjangoTests.swift b/bindings/swift/TreeSitterDjangoTests/TreeSitterDjangoTests.swift new file mode 100644 index 0000000..0fe9900 --- /dev/null +++ b/bindings/swift/TreeSitterDjangoTests/TreeSitterDjangoTests.swift @@ -0,0 +1,12 @@ +import XCTest +import SwiftTreeSitter +import TreeSitterDjango + +final class TreeSitterDjangoTests: XCTestCase { + func testCanLoadGrammar() throws { + let parser = Parser() + let language = Language(language: tree_sitter_django()) + XCTAssertNoThrow(try parser.setLanguage(language), + "Error loading Django Parser grammar") + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b36776c --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module github.com/danhanson/tree-sitter-django + +go 1.22 + +require github.com/tree-sitter/go-tree-sitter v0.24.0 + +require github.com/mattn/go-pointer v0.0.1 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0a48bbe --- /dev/null +++ b/go.sum @@ -0,0 +1,36 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= +github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tree-sitter/go-tree-sitter v0.24.0 h1:kRZb6aBNfcI/u0Qh8XEt3zjNVnmxTisDBN+kXK0xRYQ= +github.com/tree-sitter/go-tree-sitter v0.24.0/go.mod h1:x681iFVoLMEwOSIHA1chaLkXlroXEN7WY+VHGFaoDbk= +github.com/tree-sitter/tree-sitter-c v0.21.5-0.20240818205408-927da1f210eb h1:A8425heRM8mylnv4H58FPUiH+aYivyitre0PzxrfmWs= +github.com/tree-sitter/tree-sitter-c v0.21.5-0.20240818205408-927da1f210eb/go.mod h1:dOF6gtQiF9UwNh995T5OphYmtIypkjsp3ap7r9AN/iA= +github.com/tree-sitter/tree-sitter-cpp v0.22.4-0.20240818224355-b1a4e2b25148 h1:AfFPZwtwGN01BW1jDdqBVqscTwetvMpydqYZz57RSlc= +github.com/tree-sitter/tree-sitter-cpp v0.22.4-0.20240818224355-b1a4e2b25148/go.mod h1:Bh6U3viD57rFXRYIQ+kmiYtr+1Bx0AceypDLJJSyi9s= +github.com/tree-sitter/tree-sitter-embedded-template v0.21.1-0.20240819044651-ffbf64942c33 h1:TwqSV3qLp3tKSqirGLRHnjFk9Tc2oy57LIl+FQ4GjI4= +github.com/tree-sitter/tree-sitter-embedded-template v0.21.1-0.20240819044651-ffbf64942c33/go.mod h1:CvCKCt3v04Ufos1zZnNCelBDeCGRpPucaN8QczoUsN4= +github.com/tree-sitter/tree-sitter-go v0.21.3-0.20240818010209-8c0f0e7a6012 h1:Xvxck3tE5FW7F7bTS97iNM2ADMyCMJztVqn5HYKdJGo= +github.com/tree-sitter/tree-sitter-go v0.21.3-0.20240818010209-8c0f0e7a6012/go.mod h1:T40D0O1cPvUU/+AmiXVXy1cncYQT6wem4Z0g4SfAYvY= +github.com/tree-sitter/tree-sitter-html v0.20.5-0.20240818004741-d11201a263d0 h1:c46K6uh5Dz00zJeU9BfjXdb8I+E4RkUdfnWJpQADXFo= +github.com/tree-sitter/tree-sitter-html v0.20.5-0.20240818004741-d11201a263d0/go.mod h1:hcNt/kOJHcIcuMvouE7LJcYdeFUFbVpBJ6d4wmOA+tU= +github.com/tree-sitter/tree-sitter-java v0.21.1-0.20240824015150-576d8097e495 h1:jrt4qbJVEFs4H93/ITxygHc6u0TGqAkkate7TQ4wFSA= +github.com/tree-sitter/tree-sitter-java v0.21.1-0.20240824015150-576d8097e495/go.mod h1:oyaR7fLnRV0hT9z6qwE9GkaeTom/hTDwK3H2idcOJFc= +github.com/tree-sitter/tree-sitter-javascript v0.21.5-0.20240818005344-15887341e5b5 h1:om4X9AVg3asL8gxNJDcz4e/Wp+VpQj1PY3uJXKr6EOg= +github.com/tree-sitter/tree-sitter-javascript v0.21.5-0.20240818005344-15887341e5b5/go.mod h1:nNqgPoV/h9uYWk6kYEFdEAhNVOacpfpRW5SFmdaP4tU= +github.com/tree-sitter/tree-sitter-json v0.21.1-0.20240818005659-bdd69eb8c8a5 h1:pfV3G3k7NCKqKk8THBmyuh2zA33lgYHS3GVrzRR8ry4= +github.com/tree-sitter/tree-sitter-json v0.21.1-0.20240818005659-bdd69eb8c8a5/go.mod h1:GbMKRjLfk0H+PI7nLi1Sx5lHf5wCpLz9al8tQYSxpEk= +github.com/tree-sitter/tree-sitter-php v0.22.9-0.20240819002312-a552625b56c1 h1:ZXZMDwE+IhUtGug4Brv6NjJWUU3rfkZBKpemf6RY8/g= +github.com/tree-sitter/tree-sitter-php v0.22.9-0.20240819002312-a552625b56c1/go.mod h1:UKCLuYnJ312Mei+3cyTmGOHzn0YAnaPRECgJmHtzrqs= +github.com/tree-sitter/tree-sitter-python v0.21.1-0.20240818005537-55a9b8a4fbfb h1:EXEM82lFM7JjJb6qiKZXkpIDaCcbV2obNn82ghwj9lw= +github.com/tree-sitter/tree-sitter-python v0.21.1-0.20240818005537-55a9b8a4fbfb/go.mod h1:lXCF1nGG5Dr4J3BTS0ObN4xJCCICiSu/b+Xe/VqMV7g= +github.com/tree-sitter/tree-sitter-ruby v0.21.1-0.20240818211811-7dbc1e2d0e2d h1:fcYCvoXdcP1uRQYXqJHRy6Hec+uKScQdKVtMwK9JeCI= +github.com/tree-sitter/tree-sitter-ruby v0.21.1-0.20240818211811-7dbc1e2d0e2d/go.mod h1:T1nShQ4v5AJtozZ8YyAS4uzUtDAJj/iv4YfwXSbUHzg= +github.com/tree-sitter/tree-sitter-rust v0.21.3-0.20240818005432-2b43eafe6447 h1:o9alBu1J/WjrcTKEthYtXmdkDc5OVXD+PqlvnEZ0Lzc= +github.com/tree-sitter/tree-sitter-rust v0.21.3-0.20240818005432-2b43eafe6447/go.mod h1:1Oh95COkkTn6Ezp0vcMbvfhRP5gLeqqljR0BYnBzWvc= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/package-lock.json b/package-lock.json index 0bca809..fcd2f88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,16 +7,29 @@ "": { "name": "tree-sitter-django", "version": "0.1.0", + "hasInstallScript": true, "license": "MIT", "dependencies": { + "node-addon-api": "^8.3.1", + "node-gyp-build": "^4.8.4", "web-tree-sitter": "^0.26.12" }, "devDependencies": { "eslint": "^9", "eslint-config-prettier": "^10.1.8", + "prebuildify": "^6.0.1", "prettier": "^3.9.6", + "tree-sitter": "^0.25.1", "tree-sitter-cli": "^0.26.12", "typescript": "^5" + }, + "peerDependencies": { + "tree-sitter": "^0.25.0" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } } }, "node_modules/@eslint-community/eslint-utils": { @@ -313,6 +326,39 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.18", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", @@ -324,6 +370,31 @@ "concat-map": "0.0.1" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -351,6 +422,13 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -418,6 +496,16 @@ "dev": true, "license": "MIT" }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -673,6 +761,13 @@ "dev": true, "license": "ISC" }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -709,6 +804,27 @@ "node": ">=8" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -746,6 +862,13 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -880,6 +1003,23 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -894,6 +1034,62 @@ "dev": true, "license": "MIT" }, + "node_modules/node-abi": { + "version": "3.96.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.96.0.tgz", + "integrity": "sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "8.9.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.2.tgz", + "integrity": "sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/npm-run-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz", + "integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -977,6 +1173,24 @@ "node": ">=8" } }, + "node_modules/prebuildify": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/prebuildify/-/prebuildify-6.0.1.tgz", + "integrity": "sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "mkdirp-classic": "^0.5.3", + "node-abi": "^3.3.0", + "npm-run-path": "^3.1.0", + "pump": "^3.0.0", + "tar-fs": "^2.1.0" + }, + "bin": { + "prebuildify": "bin.js" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -1003,6 +1217,17 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -1013,6 +1238,21 @@ "node": ">=6" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -1023,6 +1263,40 @@ "node": ">=4" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -1046,6 +1320,16 @@ "node": ">=8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -1072,6 +1356,48 @@ "node": ">=8" } }, + "node_modules/tar-fs": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", + "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tree-sitter": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.25.1.tgz", + "integrity": "sha512-mrcEdkYtHfrK1A6fs3O6FxkBo0Qig5XUXqHhxUOQu0bmPo00QF4XaSx4edpazdHwxnSCjlGKGgIqWdaN4dvTLA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.5.0", + "node-gyp-build": "^4.8.4" + } + }, "node_modules/tree-sitter-cli": { "version": "0.26.12", "resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.26.12.tgz", @@ -1123,6 +1449,13 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/web-tree-sitter": { "version": "0.26.12", "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.26.12.tgz", @@ -1155,6 +1488,13 @@ "node": ">=0.10.0" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index e7af9c6..202862a 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,10 @@ "license": "MIT", "type": "module", "exports": { + ".": { + "types": "./bindings/node/index.d.ts", + "default": "./bindings/node/index.js" + }, "./grammar": "./grammar.js", "./src/scanner.c": "./src/scanner.c", "./queries/*": "./queries/*", @@ -20,16 +24,55 @@ "parser-build": "tree-sitter build --wasm", "highlight": "tree-sitter highlight", "tag": "tree-sitter tag", - "playground": "tree-sitter playground" + "playground": "tree-sitter playground", + "install": "node-gyp-build", + "prebuildify": "prebuildify --napi --strip", + "binding-test": "node --test bindings/node/*_test.js" }, "dependencies": { + "node-addon-api": "^8.3.1", + "node-gyp-build": "^4.8.4", "web-tree-sitter": "^0.26.12" }, "devDependencies": { "eslint": "^9", "eslint-config-prettier": "^10.1.8", + "prebuildify": "^6.0.1", "prettier": "^3.9.6", + "tree-sitter": "^0.25.1", "tree-sitter-cli": "^0.26.12", "typescript": "^5" + }, + "description": "Django grammar for tree-sitter", + "repository": { + "type": "git", + "url": "git+https://github.com/danhanson/tree-sitter-django.git" + }, + "author": "Daniel Hanson (https://github.com/danhanson)", + "keywords": [ + "incremental", + "parsing", + "tree-sitter", + "django" + ], + "main": "bindings/node", + "types": "bindings/node", + "files": [ + "grammar.js", + "tree-sitter.json", + "binding.gyp", + "prebuilds/**", + "bindings/node/*", + "queries/*", + "src/**", + "*.wasm" + ], + "peerDependencies": { + "tree-sitter": "^0.25.0" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } } } diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..29d835b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,29 @@ +[build-system] +requires = ["setuptools>=62.4.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "tree-sitter-django" +description = "Django grammar for tree-sitter" +version = "0.1.0" +keywords = ["incremental", "parsing", "tree-sitter", "django"] +classifiers = [ + "Intended Audience :: Developers", + "Topic :: Software Development :: Compilers", + "Topic :: Text Processing :: Linguistic", + "Typing :: Typed", +] +authors = [{ name = "Daniel Hanson" }] +requires-python = ">=3.10" +license.text = "MIT" +readme = "README.md" + +[project.urls] +Homepage = "https://github.com/danhanson/tree-sitter-django" + +[project.optional-dependencies] +core = ["tree-sitter~=0.24"] + +[tool.cibuildwheel] +build = "cp310-*" +build-frontend = "build" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..77082f6 --- /dev/null +++ b/setup.py @@ -0,0 +1,78 @@ +from os import path +from sysconfig import get_config_var + +from setuptools import Extension, find_packages, setup +from setuptools.command.build import build +from setuptools.command.build_ext import build_ext +from setuptools.command.egg_info import egg_info +from wheel.bdist_wheel import bdist_wheel + + +class Build(build): + def run(self): + if path.isdir("queries"): + dest = path.join(self.build_lib, "tree_sitter_django", "queries") + self.copy_tree("queries", dest) + super().run() + + +class BuildExt(build_ext): + def build_extension(self, ext: Extension): + if self.compiler.compiler_type != "msvc": + ext.extra_compile_args = ["-std=c11", "-fvisibility=hidden"] + else: + ext.extra_compile_args = ["/std:c11", "/utf-8"] + if path.exists("src/scanner.c"): + ext.sources.append("src/scanner.c") + if ext.py_limited_api: + ext.define_macros.append(("Py_LIMITED_API", "0x030A0000")) + super().build_extension(ext) + + +class BdistWheel(bdist_wheel): + def get_tag(self): + python, abi, platform = super().get_tag() + if python.startswith("cp") and not get_config_var("Py_GIL_DISABLED"): + python, abi = "cp310", "abi3" + return python, abi, platform + + +class EggInfo(egg_info): + def find_sources(self): + super().find_sources() + self.filelist.recursive_include("queries", "*.scm") + self.filelist.include("src/tree_sitter/*.h") + self.filelist.include("src/*.c") + + +setup( + packages=find_packages("bindings/python"), + package_dir={"": "bindings/python"}, + package_data={ + "tree_sitter_django": ["*.pyi", "py.typed"], + "tree_sitter_django.queries": ["*.scm"], + }, + ext_package="tree_sitter_django", + ext_modules=[ + Extension( + name="_binding", + sources=[ + "bindings/python/tree_sitter_django/binding.c", + "src/parser.c", + ], + define_macros=[ + ("PY_SSIZE_T_CLEAN", None), + ("TREE_SITTER_HIDE_SYMBOLS", None), + ], + include_dirs=["src"], + py_limited_api=not get_config_var("Py_GIL_DISABLED"), + ) + ], + cmdclass={ + "build": Build, + "build_ext": BuildExt, + "bdist_wheel": BdistWheel, + "egg_info": EggInfo, + }, + zip_safe=False +) diff --git a/tree-sitter.json b/tree-sitter.json index 023f8d8..71f0755 100644 --- a/tree-sitter.json +++ b/tree-sitter.json @@ -6,21 +6,22 @@ "camelcase": "django", "title": "Django Parser", "scope": "source.django", - "file-types": ["html", "txt", "md"], - "injection-regex": "^django$", - "class-name": "TreeSitterDjango", + "file-types": [ + "html", + "txt", + "md" + ], "highlights": "queries/highlights.scm", + "injections": "queries/injections.scm", "locals": "queries/locals.scm", - "injections": "queries/injections.scm" + "injection-regex": "^django$", + "class-name": "TreeSitterDjango" } ], - "bindings": { - "node": true - }, "metadata": { - "version": "0.0.1", - "description": "DJango grammar for tree-sitter", + "version": "0.1.0", "license": "MIT", + "description": "Django grammar for tree-sitter", "authors": [ { "name": "Daniel Hanson", @@ -30,5 +31,15 @@ "links": { "repository": "https://github.com/danhanson/tree-sitter-django" } + }, + "bindings": { + "c": true, + "go": true, + "java": false, + "node": true, + "python": true, + "rust": true, + "swift": true, + "zig": false } }