Skip to content

Commit ff7ad02

Browse files
authored
Java: Add Herb Java JNI Bindings (#766)
This pull request adds Java bindings for Herb using the JNI (Java Native Interface), allowing Java applications to parse and analyze ERB templates with the same implementation as the native C, Ruby C-Extension, C++ Emscripten WASM and the C++ Node.js NAPI bindings. Building the Java bindings requires Java and a C compiler: ```bash cd java make ``` Once built, you can use the `herb-java` CLI tool: #### **`herb-java version`** ``` herb java v0.7.5, libprism v1.6.0, libherb v0.7.5 (Java JNI) ``` #### **`herb-java lex examples/test.html.erb`** ```js #<Herb::Token type="TOKEN_HTML_TAG_START" value="<" range=[0, 1] start=(1:0) end=(1:1)> #<Herb::Token type="TOKEN_IDENTIFIER" value="h1" range=[1, 3] start=(1:1) end=(1:3)> #<Herb::Token type="TOKEN_WHITESPACE" value=" " range=[3, 4] start=(1:3) end=(1:4)> #<Herb::Token type="TOKEN_IDENTIFIER" value="class" range=[4, 9] start=(1:4) end=(1:9)> #<Herb::Token type="TOKEN_EQUALS" value="=" range=[9, 10] start=(1:9) end=(1:10)> #<Herb::Token type="TOKEN_QUOTE" value="\"" range=[10, 11] start=(1:10) end=(1:11)> #<Herb::Token type="TOKEN_IDENTIFIER" value="title" range=[11, 16] start=(1:11) end=(1:16)> #<Herb::Token type="TOKEN_QUOTE" value="\"" range=[16, 17] start=(1:16) end=(1:17)> #<Herb::Token type="TOKEN_HTML_TAG_END" value=">" range=[17, 18] start=(1:17) end=(1:18)> #<Herb::Token type="TOKEN_ERB_START" value="<%=" range=[18, 21] start=(1:18) end=(1:21)> #<Herb::Token type="TOKEN_ERB_CONTENT" value=" content " range=[21, 30] start=(1:21) end=(1:30)> #<Herb::Token type="TOKEN_ERB_END" value="%>" range=[30, 32] start=(1:30) end=(1:32)> #<Herb::Token type="TOKEN_HTML_TAG_START_CLOSE" value="</" range=[32, 34] start=(1:32) end=(1:34)> #<Herb::Token type="TOKEN_IDENTIFIER" value="h1" range=[34, 36] start=(1:34) end=(1:36)> #<Herb::Token type="TOKEN_HTML_TAG_END" value=">" range=[36, 37] start=(1:36) end=(1:37)> #<Herb::Token type="TOKEN_NEWLINE" value="\n" range=[37, 38] start=(1:37) end=(2:0)> #<Herb::Token type="TOKEN_EOF" value="<EOF>" range=[38, 38] start=(2:0) end=(2:0)> ``` #### **`herb-java parse examples/test.html.erb`** ```js @ DocumentNode (location: (1:0)-(2:0)) └── children: (2 items) ├── @ HTMLElementNode (location: (1:0)-(1:37)) │ ├── open_tag: │ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:18)) │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) │ │ ├── tag_name: "h1" (location: (1:1)-(1:3)) │ │ ├── tag_closing: ">" (location: (1:17)-(1:18)) │ │ ├── children: (1 item) │ │ │ └── @ HTMLAttributeNode (location: (1:4)-(1:17)) │ │ │ ├── name: │ │ │ │ └── @ HTMLAttributeNameNode (location: (1:4)-(1:9)) │ │ │ │ └── children: (1 item) │ │ │ │ └── @ LiteralNode (location: (1:4)-(1:9)) │ │ │ │ └── content: "class" │ │ │ │ │ │ │ ├── equals: "=" (location: (1:9)-(1:10)) │ │ │ └── value: │ │ │ └── @ HTMLAttributeValueNode (location: (1:10)-(1:17)) │ │ │ ├── open_quote: "\"" (location: (1:10)-(1:11)) │ │ │ ├── children: (1 item) │ │ │ │ └── @ LiteralNode (location: (1:11)-(1:16)) │ │ │ │ └── content: "title" │ │ │ ├── close_quote: "\"" (location: (1:16)-(1:17)) │ │ │ └── quoted: true │ │ └── is_void: false │ │ │ ├── tag_name: "h1" (location: (1:1)-(1:3)) │ ├── body: (1 item) │ │ └── @ ERBContentNode (location: (1:18)-(1:32)) │ │ ├── tag_opening: "<%=" (location: (1:18)-(1:21)) │ │ ├── content: " content " (location: (1:21)-(1:30)) │ │ ├── tag_closing: "%>" (location: (1:30)-(1:32)) │ │ ├── parsed: true │ │ └── valid: true │ ├── close_tag: │ │ └── @ HTMLCloseTagNode (location: (1:32)-(1:37)) │ │ ├── tag_opening: "</" (location: (1:32)-(1:34)) │ │ ├── tag_name: "h1" (location: (1:34)-(1:36)) │ │ ├── children: [] │ │ └── tag_closing: ">" (location: (1:36)-(1:37)) │ │ │ ├── is_void: false │ └── source: "" │ └── @ HTMLTextNode (location: (1:37)-(2:0)) └── content: "\n" ``` Resolves #648
1 parent d4c201c commit ff7ad02

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2578
-5
lines changed

.envrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export PATH="$PWD/javascript/packages/formatter/bin:$PATH"
44
export PATH="$PWD/javascript/packages/language-server/bin:$PATH"
55
export PATH="$PWD/javascript/packages/highlighter/bin:$PATH"
66
export PATH="$PWD/javascript/packages/stimulus-lint/bin:$PATH"
7+
export PATH="$PWD/java/bin:$PATH"

.gitattributes

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
# Templates
22
templates/**/*.c.erb linguist-language=C
3+
templates/**/*.cpp.erb linguist-language=C++
34
templates/**/*.h.erb linguist-language=C
5+
templates/**/*.java.erb linguist-language=Java
6+
templates/**/*.js.erb linguist-language=JavaScript
47
templates/**/*.rb.erb linguist-language=Ruby
5-
templates/**/*.cpp.erb linguist-language=C++
8+
templates/**/*.ts.erb linguist-language=TypeScript
69

710
# Template-generated RBS files
811
sig/**/*.rbs linguist-generated

.github/labeler.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ typescript:
3636
- '**/javascript/**/*.ts'
3737
- '**/javascript/**/*.ts.erb'
3838

39+
java:
40+
- changed-files:
41+
- any-glob-to-any-file:
42+
- '**/*.java'
43+
- '**/*.java.erb'
44+
3945
ruby:
4046
- changed-files:
4147
- any-glob-to-any-file:

.github/workflows/java.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Java
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
build:
14+
name: Build
15+
runs-on: ubuntu-latest
16+
timeout-minutes: 10
17+
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
22+
- name: Set up Java
23+
uses: actions/setup-java@v4
24+
with:
25+
distribution: 'temurin'
26+
java-version: '17'
27+
28+
- name: Set up Ruby
29+
uses: ruby/setup-ruby@v1
30+
with:
31+
bundler-cache: true
32+
33+
- name: Render Templates
34+
run: bundle exec rake templates
35+
36+
- name: Compile Herb
37+
run: bundle exec rake make
38+
39+
- name: Build JNI library
40+
run: make jni
41+
working-directory: java
42+
43+
- name: Compile Java classes
44+
run: make java
45+
working-directory: java
46+
47+
- name: Run tests
48+
run: ./run_tests.sh
49+
working-directory: java
50+
51+
- name: Test CLI version command
52+
run: ./bin/herb-java version
53+
working-directory: java

.gitignore

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,17 @@ ext/herb/error_helpers.c
8989
ext/herb/error_helpers.h
9090
ext/herb/nodes.c
9191
ext/herb/nodes.h
92+
java/error_helpers.c
93+
java/error_helpers.h
94+
java/nodes.c
95+
java/nodes.h
96+
java/org/herb/ast/Errors.java
97+
java/org/herb/ast/Nodes.java
98+
java/org/herb/ast/NodeVisitor.java
99+
java/org/herb/ast/Visitor.java
92100
javascript/packages/core/src/errors.ts
93-
javascript/packages/core/src/nodes.ts
94101
javascript/packages/core/src/node-type-guards.ts
102+
javascript/packages/core/src/nodes.ts
95103
javascript/packages/core/src/visitor.ts
96104
javascript/packages/node/extension/error_helpers.cpp
97105
javascript/packages/node/extension/error_helpers.h
@@ -100,8 +108,8 @@ javascript/packages/node/extension/nodes.h
100108
lib/herb/ast/nodes.rb
101109
lib/herb/errors.rb
102110
lib/herb/visitor.rb
103-
sig/serialized_ast_nodes.rbs
104111
sig/serialized_ast_errors.rbs
112+
sig/serialized_ast_nodes.rbs
105113
src/ast_nodes.c
106114
src/ast_pretty_print.c
107115
src/errors.c
@@ -114,6 +122,10 @@ wasm/error_helpers.h
114122
wasm/nodes.cpp
115123
wasm/nodes.h
116124

125+
# Java Build Artifacts
126+
java/target/
127+
java/.java_compiled
128+
117129
# NX Monorepo
118130
.nx/
119131
.nx/cache

docs/.vitepress/config/theme.mts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ const defaultSidebar = [
9090
{ text: "Reference", link: "/bindings/javascript/reference" },
9191
],
9292
},
93+
{
94+
text: "Java",
95+
collapsed: false,
96+
items: [
97+
{ text: "Installation", link: "/bindings/java/" },
98+
{ text: "Reference", link: "/bindings/java/reference" },
99+
],
100+
},
93101
{ text: "WebAssembly", link: "/projects/webassembly" },
94102
],
95103
},

docs/docs/bindings/java/index.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# Herb Java Bindings
6+
7+
Herb provides official Java bindings through JNI (Java Native Interface) to the C library, allowing you to parse HTML+ERB in Java projects with native performance.
8+
9+
> [!TIP] More Language Bindings
10+
> Herb also has bindings for:
11+
> - [Ruby](/bindings/ruby/)
12+
> - [JavaScript/Node.js](/bindings/javascript/)
13+
14+
## Installation
15+
16+
### Prerequisites
17+
18+
Ensure you have Java installed:
19+
20+
:::code-group
21+
```shell
22+
java -version
23+
```
24+
:::
25+
26+
### Build from Source
27+
28+
Clone the repository and build the Java bindings:
29+
30+
:::code-group
31+
```shell
32+
git clone https://github.com/your-org/herb.git
33+
cd herb/java
34+
make templates
35+
make jni
36+
make java
37+
```
38+
:::
39+
40+
This creates the native library (`libherb_jni.dylib` on macOS, `.so` on Linux).
41+
42+
### Setting Up Your Project
43+
44+
Add the compiled classes to your classpath and ensure the native library is in your `java.library.path`.
45+
46+
## Getting Started
47+
48+
### Basic Example
49+
50+
Here's a simple example of parsing HTML+ERB:
51+
52+
:::code-group
53+
```java
54+
import org.herb.Herb;
55+
import org.herb.ParseResult;
56+
57+
public class Example {
58+
public static void main(String[] args) {
59+
String source = "<h1><%= user.name %></h1>";
60+
61+
ParseResult result = Herb.parse(source);
62+
63+
if (result.getValue() != null) {
64+
System.out.println(result.getValue().treeInspect());
65+
}
66+
}
67+
}
68+
```
69+
:::
70+
71+
### Lexing Example
72+
73+
You can also tokenize HTML+ERB source:
74+
75+
:::code-group
76+
```java
77+
import org.herb.Herb;
78+
import org.herb.LexResult;
79+
import org.herb.Token;
80+
81+
public class LexExample {
82+
public static void main(String[] args) {
83+
String source = "<h1><%= user.name %></h1>";
84+
85+
LexResult result = Herb.lex(source);
86+
87+
for (Token token : result.getTokens()) {
88+
System.out.println(token.inspect());
89+
}
90+
}
91+
}
92+
```
93+
:::

0 commit comments

Comments
 (0)