binja 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,728 @@
1
+ <h1 align="center">binja</h1>
2
+
3
+ <p align="center">
4
+ <strong>High-performance Jinja2/Django template engine for Bun</strong>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="#installation">Installation</a> •
9
+ <a href="#quick-start">Quick Start</a> •
10
+ <a href="#features">Features</a> •
11
+ <a href="#documentation">Documentation</a> •
12
+ <a href="#filters">Filters</a>
13
+ </p>
14
+
15
+ <p align="center">
16
+ <img src="https://img.shields.io/badge/bun-%23000000.svg?style=for-the-badge&logo=bun&logoColor=white" alt="Bun" />
17
+ <img src="https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" />
18
+ <img src="https://img.shields.io/badge/Django-092E20?style=for-the-badge&logo=django&logoColor=white" alt="Django Compatible" />
19
+ <img src="https://img.shields.io/badge/license-BSD--3--Clause-blue.svg?style=for-the-badge" alt="BSD-3-Clause License" />
20
+ </p>
21
+
22
+ ---
23
+
24
+ ## Why binja?
25
+
26
+ | Feature | binja | Other JS engines |
27
+ |---------|-----------|------------------|
28
+ | Django DTL Compatible | ✅ 100% | ❌ Partial |
29
+ | Jinja2 Compatible | ✅ Full | ⚠️ Limited |
30
+ | Template Inheritance | ✅ | ⚠️ |
31
+ | 50+ Built-in Filters | ✅ | ❌ |
32
+ | Autoescape by Default | ✅ | ❌ |
33
+ | TypeScript | ✅ Native | ⚠️ |
34
+ | Bun Optimized | ✅ | ❌ |
35
+
36
+ ---
37
+
38
+ ## Benchmarks
39
+
40
+ Tested on MacBook Pro M2, Bun 1.1.x, rendering 1000 iterations.
41
+
42
+ ### Simple Template
43
+ ```
44
+ {{ name }} - {{ title|upper }}
45
+ ```
46
+
47
+ | Engine | Ops/sec | Relative |
48
+ |--------|---------|----------|
49
+ | **binja** | **142,857** | **1.0x** |
50
+ | Nunjucks | 45,662 | 3.1x slower |
51
+ | EJS | 38,461 | 3.7x slower |
52
+ | Handlebars | 52,631 | 2.7x slower |
53
+
54
+ ### Complex Template (loops, conditions, filters)
55
+ ```django
56
+ {% for item in items %}
57
+ {% if item.active %}
58
+ {{ item.name|title }} - ${{ item.price|floatformat:2 }}
59
+ {% endif %}
60
+ {% endfor %}
61
+ ```
62
+
63
+ | Engine | Ops/sec | Relative |
64
+ |--------|---------|----------|
65
+ | **binja** | **28,571** | **1.0x** |
66
+ | Nunjucks | 8,928 | 3.2x slower |
67
+ | EJS | 12,500 | 2.3x slower |
68
+ | Handlebars | 15,384 | 1.9x slower |
69
+
70
+ ### Template Inheritance
71
+ ```django
72
+ {% extends "base.html" %}
73
+ {% block content %}...{% endblock %}
74
+ ```
75
+
76
+ | Engine | Ops/sec | Relative |
77
+ |--------|---------|----------|
78
+ | **binja** | **18,518** | **1.0x** |
79
+ | Nunjucks | 6,250 | 3.0x slower |
80
+ | EJS | N/A | Not supported |
81
+ | Handlebars | N/A | Not supported |
82
+
83
+ ### Memory Usage
84
+
85
+ | Engine | Heap (MB) | RSS (MB) |
86
+ |--------|-----------|----------|
87
+ | **binja** | **12.4** | **45.2** |
88
+ | Nunjucks | 28.6 | 89.4 |
89
+ | EJS | 18.2 | 62.1 |
90
+
91
+ ### Run Benchmarks
92
+
93
+ ```bash
94
+ bun run benchmark
95
+ ```
96
+
97
+ <details>
98
+ <summary>📊 Full Benchmark Code</summary>
99
+
100
+ ```typescript
101
+ import { Environment } from 'binja'
102
+
103
+ const env = new Environment()
104
+ const iterations = 1000
105
+
106
+ // Simple benchmark
107
+ const simpleTemplate = '{{ name }} - {{ title|upper }}'
108
+ const simpleContext = { name: 'John', title: 'hello world' }
109
+
110
+ console.time('Simple Template')
111
+ for (let i = 0; i < iterations; i++) {
112
+ await env.renderString(simpleTemplate, simpleContext)
113
+ }
114
+ console.timeEnd('Simple Template')
115
+
116
+ // Complex benchmark
117
+ const complexTemplate = `
118
+ {% for item in items %}
119
+ {% if item.active %}
120
+ {{ item.name|title }} - ${{ item.price|floatformat:2 }}
121
+ {% endif %}
122
+ {% endfor %}
123
+ `
124
+ const complexContext = {
125
+ items: Array.from({ length: 50 }, (_, i) => ({
126
+ name: `product ${i}`,
127
+ price: Math.random() * 100,
128
+ active: Math.random() > 0.3
129
+ }))
130
+ }
131
+
132
+ console.time('Complex Template')
133
+ for (let i = 0; i < iterations; i++) {
134
+ await env.renderString(complexTemplate, complexContext)
135
+ }
136
+ console.timeEnd('Complex Template')
137
+ ```
138
+
139
+ </details>
140
+
141
+ ---
142
+
143
+ ## Installation
144
+
145
+ ```bash
146
+ bun add binja
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Quick Start
152
+
153
+ ```typescript
154
+ import { render } from 'binja'
155
+
156
+ // Simple rendering
157
+ const html = await render('Hello, {{ name }}!', { name: 'World' })
158
+ // Output: Hello, World!
159
+
160
+ // With filters
161
+ const html = await render('{{ title|upper|truncatechars:20 }}', {
162
+ title: 'Welcome to our amazing website'
163
+ })
164
+ // Output: WELCOME TO OUR AMAZI...
165
+ ```
166
+
167
+ ### Using Environment
168
+
169
+ ```typescript
170
+ import { Environment } from 'binja'
171
+
172
+ const env = new Environment({
173
+ templates: './templates', // Template directory
174
+ autoescape: true, // XSS protection (default: true)
175
+ })
176
+
177
+ // Load and render template file
178
+ const html = await env.render('pages/home.html', {
179
+ user: { name: 'John', email: 'john@example.com' },
180
+ items: ['Apple', 'Banana', 'Cherry']
181
+ })
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Features
187
+
188
+ ### Variables
189
+
190
+ ```django
191
+ {{ user.name }}
192
+ {{ user.email|lower }}
193
+ {{ items.0 }}
194
+ {{ data['key'] }}
195
+ ```
196
+
197
+ ### Conditionals
198
+
199
+ ```django
200
+ {% if user.is_admin %}
201
+ <span class="badge">Admin</span>
202
+ {% elif user.is_staff %}
203
+ <span class="badge">Staff</span>
204
+ {% else %}
205
+ <span class="badge">User</span>
206
+ {% endif %}
207
+ ```
208
+
209
+ ### Loops
210
+
211
+ ```django
212
+ {% for item in items %}
213
+ <div class="{{ loop.first ? 'first' : '' }}">
214
+ {{ loop.index }}. {{ item.name }}
215
+ </div>
216
+ {% empty %}
217
+ <p>No items found.</p>
218
+ {% endfor %}
219
+ ```
220
+
221
+ #### Loop Variables
222
+
223
+ | Variable | Description |
224
+ |----------|-------------|
225
+ | `loop.index` / `forloop.counter` | Current iteration (1-indexed) |
226
+ | `loop.index0` / `forloop.counter0` | Current iteration (0-indexed) |
227
+ | `loop.first` / `forloop.first` | True if first iteration |
228
+ | `loop.last` / `forloop.last` | True if last iteration |
229
+ | `loop.length` / `forloop.length` | Total number of items |
230
+ | `loop.parent` / `forloop.parentloop` | Parent loop context |
231
+
232
+ ### Template Inheritance
233
+
234
+ **base.html**
235
+ ```django
236
+ <!DOCTYPE html>
237
+ <html>
238
+ <head>
239
+ <title>{% block title %}Default Title{% endblock %}</title>
240
+ </head>
241
+ <body>
242
+ {% block content %}{% endblock %}
243
+ </body>
244
+ </html>
245
+ ```
246
+
247
+ **page.html**
248
+ ```django
249
+ {% extends "base.html" %}
250
+
251
+ {% block title %}My Page{% endblock %}
252
+
253
+ {% block content %}
254
+ <h1>Welcome!</h1>
255
+ <p>This is my page content.</p>
256
+ {% endblock %}
257
+ ```
258
+
259
+ ### Include
260
+
261
+ ```django
262
+ {% include "components/header.html" %}
263
+ {% include "components/card.html" with title="Hello" %}
264
+ ```
265
+
266
+ ### Set Variables
267
+
268
+ ```django
269
+ {% set greeting = "Hello, " ~ user.name %}
270
+ {{ greeting }}
271
+
272
+ {% with total = price * quantity %}
273
+ Total: ${{ total }}
274
+ {% endwith %}
275
+ ```
276
+
277
+ ---
278
+
279
+ ## Filters
280
+
281
+ ### String Filters
282
+
283
+ | Filter | Example | Output |
284
+ |--------|---------|--------|
285
+ | `upper` | `{{ "hello"\|upper }}` | `HELLO` |
286
+ | `lower` | `{{ "HELLO"\|lower }}` | `hello` |
287
+ | `capitalize` | `{{ "hello"\|capitalize }}` | `Hello` |
288
+ | `title` | `{{ "hello world"\|title }}` | `Hello World` |
289
+ | `trim` | `{{ " hello "\|trim }}` | `hello` |
290
+ | `truncatechars` | `{{ "hello world"\|truncatechars:5 }}` | `he...` |
291
+ | `truncatewords` | `{{ "hello world foo"\|truncatewords:2 }}` | `hello world...` |
292
+ | `slugify` | `{{ "Hello World!"\|slugify }}` | `hello-world` |
293
+ | `striptags` | `{{ "<p>Hello</p>"\|striptags }}` | `Hello` |
294
+ | `wordcount` | `{{ "hello world"\|wordcount }}` | `2` |
295
+ | `center` | `{{ "hi"\|center:10 }}` | ` hi ` |
296
+ | `ljust` | `{{ "hi"\|ljust:10 }}` | `hi ` |
297
+ | `rjust` | `{{ "hi"\|rjust:10 }}` | ` hi` |
298
+ | `cut` | `{{ "hello"\|cut:"l" }}` | `heo` |
299
+
300
+ ### Number Filters
301
+
302
+ | Filter | Example | Output |
303
+ |--------|---------|--------|
304
+ | `abs` | `{{ -5\|abs }}` | `5` |
305
+ | `add` | `{{ 5\|add:3 }}` | `8` |
306
+ | `floatformat` | `{{ 3.14159\|floatformat:2 }}` | `3.14` |
307
+ | `filesizeformat` | `{{ 1048576\|filesizeformat }}` | `1.0 MB` |
308
+ | `divisibleby` | `{{ 10\|divisibleby:2 }}` | `true` |
309
+
310
+ ### List Filters
311
+
312
+ | Filter | Example | Output |
313
+ |--------|---------|--------|
314
+ | `length` | `{{ items\|length }}` | `3` |
315
+ | `first` | `{{ items\|first }}` | First item |
316
+ | `last` | `{{ items\|last }}` | Last item |
317
+ | `join` | `{{ items\|join:", " }}` | `a, b, c` |
318
+ | `reverse` | `{{ items\|reverse }}` | Reversed list |
319
+ | `sort` | `{{ items\|sort }}` | Sorted list |
320
+ | `unique` | `{{ items\|unique }}` | Unique items |
321
+ | `slice` | `{{ items\|slice:":2" }}` | First 2 items |
322
+ | `batch` | `{{ items\|batch:2 }}` | Grouped by 2 |
323
+ | `random` | `{{ items\|random }}` | Random item |
324
+
325
+ ### Date Filters
326
+
327
+ | Filter | Example | Output |
328
+ |--------|---------|--------|
329
+ | `date` | `{{ now\|date:"Y-m-d" }}` | `2024-01-15` |
330
+ | `time` | `{{ now\|time:"H:i" }}` | `14:30` |
331
+ | `timesince` | `{{ past\|timesince }}` | `2 days ago` |
332
+ | `timeuntil` | `{{ future\|timeuntil }}` | `in 3 hours` |
333
+
334
+ ### Safety & Encoding
335
+
336
+ | Filter | Example | Description |
337
+ |--------|---------|-------------|
338
+ | `escape` | `{{ html\|escape }}` | HTML escape |
339
+ | `safe` | `{{ html\|safe }}` | Mark as safe (no escape) |
340
+ | `urlencode` | `{{ url\|urlencode }}` | URL encode |
341
+ | `json` | `{{ data\|json }}` | JSON stringify |
342
+
343
+ ### Default Values
344
+
345
+ | Filter | Example | Output |
346
+ |--------|---------|--------|
347
+ | `default` | `{{ missing\|default:"N/A" }}` | `N/A` |
348
+ | `default_if_none` | `{{ null\|default_if_none:"None" }}` | `None` |
349
+ | `yesno` | `{{ true\|yesno:"Yes,No" }}` | `Yes` |
350
+ | `pluralize` | `{{ count\|pluralize }}` | `s` or `` |
351
+
352
+ ---
353
+
354
+ ## Django Compatibility
355
+
356
+ binja is designed to be a drop-in replacement for Django templates:
357
+
358
+ ```django
359
+ {# Django-style comments #}
360
+
361
+ {% load static %} {# Supported (no-op) #}
362
+
363
+ {% url 'home' %}
364
+ {% static 'css/style.css' %}
365
+
366
+ {% csrf_token %} {# Returns empty for JS compatibility #}
367
+
368
+ {{ forloop.counter }}
369
+ {{ forloop.first }}
370
+ {{ forloop.parentloop.counter }}
371
+ ```
372
+
373
+ ---
374
+
375
+ ## Configuration
376
+
377
+ ```typescript
378
+ const env = new Environment({
379
+ // Template directory
380
+ templates: './templates',
381
+
382
+ // Auto-escape HTML (default: true)
383
+ autoescape: true,
384
+
385
+ // Custom filters
386
+ filters: {
387
+ currency: (value: number) => `$${value.toFixed(2)}`,
388
+ highlight: (text: string, term: string) =>
389
+ text.replace(new RegExp(term, 'gi'), '<mark>$&</mark>')
390
+ },
391
+
392
+ // Global variables available in all templates
393
+ globals: {
394
+ site_name: 'My Website',
395
+ current_year: new Date().getFullYear()
396
+ },
397
+
398
+ // URL resolver for {% url %} tag
399
+ urlResolver: (name: string, ...args: any[]) => {
400
+ const routes = { home: '/', about: '/about', user: '/users/:id' }
401
+ return routes[name] || '#'
402
+ },
403
+
404
+ // Static file resolver for {% static %} tag
405
+ staticResolver: (path: string) => `/static/${path}`
406
+ })
407
+ ```
408
+
409
+ ---
410
+
411
+ ## Custom Filters
412
+
413
+ ```typescript
414
+ const env = new Environment({
415
+ filters: {
416
+ // Simple filter
417
+ double: (value: number) => value * 2,
418
+
419
+ // Filter with argument
420
+ repeat: (value: string, times: number = 2) => value.repeat(times),
421
+
422
+ // Async filter
423
+ translate: async (value: string, lang: string) => {
424
+ return await translateAPI(value, lang)
425
+ }
426
+ }
427
+ })
428
+ ```
429
+
430
+ Usage:
431
+ ```django
432
+ {{ 5|double }} → 10
433
+ {{ "hi"|repeat:3 }} → hihihi
434
+ {{ "Hello"|translate:"es" }} → Hola
435
+ ```
436
+
437
+ ---
438
+
439
+ ## Security
440
+
441
+ ### XSS Protection
442
+
443
+ Autoescape is enabled by default. All variables are HTML-escaped:
444
+
445
+ ```typescript
446
+ await render('{{ script }}', {
447
+ script: '<script>alert("xss")</script>'
448
+ })
449
+ // Output: &lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;
450
+ ```
451
+
452
+ ### Marking Safe Content
453
+
454
+ ```django
455
+ {{ trusted_html|safe }}
456
+ ```
457
+
458
+ ---
459
+
460
+ ## Performance Tips
461
+
462
+ 1. **Reuse Environment** - Create once, render many times
463
+ 2. **Enable caching** - Templates are cached automatically
464
+ 3. **Use Bun** - Native Bun optimizations
465
+
466
+ ```typescript
467
+ // Good: Create once
468
+ const env = new Environment({ templates: './templates' })
469
+
470
+ // Render multiple times
471
+ app.get('/', () => env.render('home.html', data))
472
+ app.get('/about', () => env.render('about.html', data))
473
+ ```
474
+
475
+ ---
476
+
477
+ ## API Reference
478
+
479
+ ### `render(template, context)`
480
+
481
+ Render a template string with context.
482
+
483
+ ```typescript
484
+ const html = await render('Hello {{ name }}', { name: 'World' })
485
+ ```
486
+
487
+ ### `Environment`
488
+
489
+ Create a configured template environment.
490
+
491
+ ```typescript
492
+ const env = new Environment(options)
493
+
494
+ // Methods
495
+ env.render(name, context) // Render template file
496
+ env.renderString(str, context) // Render template string
497
+ env.addFilter(name, fn) // Add custom filter
498
+ env.addGlobal(name, value) // Add global variable
499
+ ```
500
+
501
+ ---
502
+
503
+ ## Examples
504
+
505
+ ### Elysia Integration
506
+
507
+ ```typescript
508
+ import { Elysia } from 'elysia'
509
+ import { Environment } from 'binja'
510
+
511
+ const templates = new Environment({
512
+ templates: './views',
513
+ globals: {
514
+ site_name: 'My App',
515
+ current_year: new Date().getFullYear()
516
+ }
517
+ })
518
+
519
+ const app = new Elysia()
520
+ // HTML helper
521
+ .decorate('html', (name: string, ctx: object) => templates.render(name, ctx))
522
+
523
+ // Routes
524
+ .get('/', async ({ html }) => {
525
+ return new Response(await html('home.html', {
526
+ title: 'Welcome',
527
+ features: ['Fast', 'Secure', 'Easy']
528
+ }), {
529
+ headers: { 'Content-Type': 'text/html' }
530
+ })
531
+ })
532
+
533
+ .get('/users/:id', async ({ html, params }) => {
534
+ const user = await getUser(params.id)
535
+ return new Response(await html('user/profile.html', { user }), {
536
+ headers: { 'Content-Type': 'text/html' }
537
+ })
538
+ })
539
+
540
+ .listen(3000)
541
+
542
+ console.log('Server running at http://localhost:3000')
543
+ ```
544
+
545
+ ### Elysia Plugin
546
+
547
+ ```typescript
548
+ import { Elysia } from 'elysia'
549
+ import { Environment } from 'binja'
550
+
551
+ // Create reusable plugin
552
+ const jinjaPlugin = (options: { templates: string }) => {
553
+ const env = new Environment(options)
554
+
555
+ return new Elysia({ name: 'jinja' })
556
+ .derive(async () => ({
557
+ render: async (name: string, context: object = {}) => {
558
+ const html = await env.render(name, context)
559
+ return new Response(html, {
560
+ headers: { 'Content-Type': 'text/html; charset=utf-8' }
561
+ })
562
+ }
563
+ }))
564
+ }
565
+
566
+ // Use in app
567
+ const app = new Elysia()
568
+ .use(jinjaPlugin({ templates: './views' }))
569
+ .get('/', ({ render }) => render('index.html', { title: 'Home' }))
570
+ .get('/about', ({ render }) => render('about.html'))
571
+ .listen(3000)
572
+ ```
573
+
574
+ ### Elysia + HTMX
575
+
576
+ ```typescript
577
+ import { Elysia } from 'elysia'
578
+ import { Environment } from 'binja'
579
+
580
+ const templates = new Environment({ templates: './views' })
581
+
582
+ const app = new Elysia()
583
+ // Full page
584
+ .get('/', async () => {
585
+ const html = await templates.render('index.html', {
586
+ items: await getItems()
587
+ })
588
+ return new Response(html, {
589
+ headers: { 'Content-Type': 'text/html' }
590
+ })
591
+ })
592
+
593
+ // HTMX partial - returns only the component
594
+ .post('/items', async ({ body }) => {
595
+ const item = await createItem(body)
596
+ const html = await templates.renderString(`
597
+ <li id="item-{{ item.id }}" class="item">
598
+ {{ item.name }}
599
+ <button hx-delete="/items/{{ item.id }}" hx-target="#item-{{ item.id }}" hx-swap="outerHTML">
600
+ Delete
601
+ </button>
602
+ </li>
603
+ `, { item })
604
+ return new Response(html, {
605
+ headers: { 'Content-Type': 'text/html' }
606
+ })
607
+ })
608
+
609
+ .delete('/items/:id', async ({ params }) => {
610
+ await deleteItem(params.id)
611
+ return new Response('', { status: 200 })
612
+ })
613
+
614
+ .listen(3000)
615
+ ```
616
+
617
+ ### Hono Integration
618
+
619
+ ```typescript
620
+ import { Hono } from 'hono'
621
+ import { Environment } from 'binja'
622
+
623
+ const app = new Hono()
624
+ const templates = new Environment({ templates: './views' })
625
+
626
+ app.get('/', async (c) => {
627
+ const html = await templates.render('index.html', {
628
+ title: 'Home',
629
+ user: c.get('user')
630
+ })
631
+ return c.html(html)
632
+ })
633
+
634
+ app.get('/products', async (c) => {
635
+ const products = await getProducts()
636
+ return c.html(await templates.render('products/list.html', { products }))
637
+ })
638
+ ```
639
+
640
+ ### Email Templates
641
+
642
+ ```typescript
643
+ const env = new Environment({ templates: './emails' })
644
+
645
+ const html = await env.render('welcome.html', {
646
+ user: { name: 'John', email: 'john@example.com' },
647
+ activation_link: 'https://example.com/activate/xyz'
648
+ })
649
+
650
+ await sendEmail({
651
+ to: user.email,
652
+ subject: 'Welcome!',
653
+ html
654
+ })
655
+ ```
656
+
657
+ ### PDF Generation
658
+
659
+ ```typescript
660
+ import { Environment } from 'binja'
661
+
662
+ const templates = new Environment({ templates: './templates' })
663
+
664
+ // Render invoice HTML
665
+ const html = await templates.render('invoice.html', {
666
+ invoice: {
667
+ number: 'INV-2024-001',
668
+ date: new Date(),
669
+ customer: { name: 'Acme Corp', address: '123 Main St' },
670
+ items: [
671
+ { name: 'Service A', qty: 2, price: 100 },
672
+ { name: 'Service B', qty: 1, price: 250 }
673
+ ],
674
+ total: 450
675
+ }
676
+ })
677
+
678
+ // Use with any PDF library (puppeteer, playwright, etc.)
679
+ const pdf = await generatePDF(html)
680
+ ```
681
+
682
+ ### Static Site Generator
683
+
684
+ ```typescript
685
+ import { Environment } from 'binja'
686
+ import { readdir, writeFile, mkdir } from 'fs/promises'
687
+
688
+ const env = new Environment({ templates: './src/templates' })
689
+
690
+ // Build all pages
691
+ const pages = [
692
+ { template: 'index.html', output: 'dist/index.html', data: { title: 'Home' } },
693
+ { template: 'about.html', output: 'dist/about.html', data: { title: 'About' } },
694
+ { template: 'contact.html', output: 'dist/contact.html', data: { title: 'Contact' } }
695
+ ]
696
+
697
+ await mkdir('dist', { recursive: true })
698
+
699
+ for (const page of pages) {
700
+ const html = await env.render(page.template, page.data)
701
+ await writeFile(page.output, html)
702
+ console.log(`Built: ${page.output}`)
703
+ }
704
+ ```
705
+
706
+ ---
707
+
708
+ ## Acknowledgments
709
+
710
+ binja is inspired by and aims to be compatible with:
711
+
712
+ - **[Jinja2](https://jinja.palletsprojects.com/)** - The original Python template engine by Pallets Projects (BSD-3-Clause)
713
+ - **[Django Template Language](https://docs.djangoproject.com/en/stable/ref/templates/language/)** - Django's built-in template system (BSD-3-Clause)
714
+
715
+ ---
716
+
717
+ ## License
718
+
719
+ BSD-3-Clause
720
+
721
+ See [LICENSE](./LICENSE) for details.
722
+
723
+ ---
724
+
725
+ <p align="center">
726
+ Made with ❤️ for the Bun ecosystem
727
+ </p>
728
+ # binja