๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

Handlebars

jknack-handlebars.java ํ”„๋กœ์ ํŠธ README.md ํ•œ๊ตญ์–ด ๋ฒˆ์—ญ

handlebars์— ๋Œ€ํ•œ ๊ณต๋ถ€๋ฅผ ํ•˜๋˜ ๋„์ค‘, jknack/handlebars.java ํ”„๋กœ์ ํŠธ์˜ README.md์— handlebars.java์— ๋Œ€ํ•œ ์„ค๋ช…์ด ๋งค์šฐ ์ž˜ ๋‚˜์™€์žˆ์–ด ์ด๋ฅผ ํ•œ๊ตญ์–ด๋กœ ๋ฒˆ์—ญํ–ˆ์Šต๋‹ˆ๋‹ค.


Handlebars.java

Logic-less and semantic Mustache templates with Java

Handlebars handlebars = new Handlebars();

Template template = handlebars.compileInline("Hello {{this}}!");

System.out.println(template.apply("Handlebars.java"));

Output:

Hello Handlebars.java!

Handlebars.java๋Š” Java๋ฅผ ์œ„ํ•œ handlebars ์ž…๋‹ˆ๋‹ค.

Handlebars๋Š” ํšจ๊ณผ์ ์œผ๋กœ ์‹œ๋งจํ‹ฑ ํ…œํ”Œ๋ฆฟ์„ ๊ตฌ์ถ•ํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Mustache ํ…œํ”Œ๋ฆฟ์€ ํ•ธ๋“ค๋ฐ”์™€ ํ˜ธํ™˜๋˜๋ฏ€๋กœ Mustache ํ…œํ”Œ๋ฆฟ์„ ๊ฐ€์ ธ์™€์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ถ”๊ฐ€ ํ•ธ๋“ค๋ฐ” ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜์‹ญ์‹œ์˜ค.

Requirements

  • Handlebars 4.3+๋Š” Java 8 ์ด์ƒ์˜ ๋ฒ„์ „์„ ์š”๊ตฌํ•ฉ๋‹ˆ๋‹ค.

Getting Started

์ผ๋ฐ˜์ ์œผ๋กœ Handlebars ํ…œํ”Œ๋ฆฟ์˜ ๊ตฌ๋ฌธ์€ Mustache ํ…œํ”Œ๋ฆฟ์˜ ์ƒ์œ„ ์ง‘ํ•ฉ์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ๊ตฌ๋ฌธ์€ Mustache manpage๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

Handlebars.java blog๋„ ์‹œ์ž‘ํ•˜๊ธฐ์— ์ข‹์€ ๊ณณ์ž…๋‹ˆ๋‹ค. Javadoc์€ javadoc.io์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Maven

Stable version: Maven Central

  <dependency>
    <groupId>com.github.jknack</groupId>
    <artifactId>handlebars</artifactId>
    <version>${handlebars-version}</version>
  </dependency>

templates ๋กœ๋”ฉ

ํ…œํ”Œ๋ฆฟ์€ TemplateLoader ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๋“œ๋ฉ๋‹ˆ๋‹ค. Handlebars.java๋Š” TemplateLoader์˜ ์„ธ ๊ฐ€์ง€ ๊ตฌํ˜„์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  • ClassPathTemplateLoader (default)
  • FileTemplateLoader
  • SpringTemplateLoader (handlebars-springmvc module ์ฐธ๊ณ )

์ด ์˜ˆ์ œ๋Š” ํด๋ž˜์Šค ๊ฒฝ๋กœ์˜ ๋ฃจํŠธ์—์„œ mytemplate.hbs๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.

mytemplate.hbs:

Hello {{this}}!
Handlebars handlebars = new Handlebars();

Template template = handlebars.compile("mytemplate");

System.out.println(template.apply("Handlebars.java"));

Output:

Hello Handlebars.java!

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‹ค๋ฅธ TemplateLoader๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

TemplateLoader loader = ...;
Handlebars handlebars = new Handlebars(loader);

Templates ์ ‘๋‘์‚ฌ ๋ฐ ์ ‘๋ฏธ์‚ฌ

'TemplateLoader'๋Š” ๋‘ ๊ฐ€์ง€ ์ค‘์š”ํ•œ ์†์„ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  • prefix: ํ…œํ”Œ๋ฆฟ์ด ์ €์žฅ๋˜๋Š” ๊ธฐ๋ณธ ์ ‘๋‘์‚ฌ๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • suffix: ํ…œํ”Œ๋ฆฟ์˜ ๊ธฐ๋ณธ ์ ‘๋ฏธ์‚ฌ ๋˜๋Š” ํŒŒ์ผ ํ™•์žฅ์ž๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ .hbs์ž…๋‹ˆ๋‹ค.

Example:

TemplateLoader loader = new ClassPathTemplateLoader();
loader.setPrefix("/templates");
loader.setSuffix(".html");
Handlebars handlebars = new Handlebars(loader);

Template template = handlebars.compile("mytemplate");

System.out.println(template.apply("Handlebars.java"));

Handlebars.java๋Š” mytemplate์„ /templates/mytemplate.html๋กœ ํ•ด์„ํ•˜๊ณ  ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.

The Handlebars.java ์„œ๋ฒ„

handlebars.java ์„œ๋ฒ„๋Š” Mustache/Handlebars ํ…œํ”Œ๋ฆฟ์„ ์ž‘์„ฑํ•˜๊ณ  ๋ฐ์ดํ„ฐ์™€ ๋ณ‘ํ•ฉํ•  ์ˆ˜ ์žˆ๋Š” ์ž‘์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ž…๋‹ˆ๋‹ค.

์›น ๋””์ž์ด๋„ˆ์—๊ฒŒ ์œ ์šฉํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

Maven Central์—์„œ ๋‹ค์šด๋กœ๋“œ:

  1. here ํด๋ฆญ
  2. Download ์„น์…˜์—์„œ jar๋ฅผ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.

Maven:

<dependency>
  <groupId>com.github.jknack</groupId>
  <artifactId>handlebars-proto</artifactId>
  <version>${current-version}</version>
</dependency>

Usage:
java -jar handlebars-proto-${current-version}.jar -dir myTemplates

Example:

myTemplates/home.hbs

<ul>
 {{#items}}
 {{name}}
 {{/items}}
</ul>

myTemplates/home.json

{
  "items": [
    {
      "name": "Handlebars.java rocks!"
    }
  ]
}

๋˜๋Š” YAML myTemplates/home.yml์„ ์„ ํ˜ธํ•˜๋Š” ๊ฒฝ์šฐ:

items:
  - name: Handlebars.java rocks!

๋‹ค์Œ ์ฃผ์†Œ์˜ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์—ฝ๋‹ˆ๋‹ค.

http://localhost:6780/home.hbs

์ถ”๊ฐ€ ์˜ต์…˜:

  • -dir: ํ…œํ”Œ๋ฆฟ ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ •
  • -prefix: ํ…œํ”Œ๋ฆฟ์˜ ์ ‘๋‘์‚ฌ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ /์ž…๋‹ˆ๋‹ค.
  • -suffix: ํ…œํ”Œ๋ฆฟ์˜ ์ ‘๋ฏธ์‚ฌ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ .hbs์ž…๋‹ˆ๋‹ค.
  • -context: ์ปจํ…์ŠคํŠธ์˜ ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ /์ž…๋‹ˆ๋‹ค.
  • -port: ํฌํŠธ ๋ฒˆํ˜ธ ์„ค์ •, ๊ธฐ๋ณธ๊ฐ’์€ 6780
    --content-type: ์ปจํ…์ธ  ์œ ํ˜• ํ—ค๋”๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ text/html์ž…๋‹ˆ๋‹ค.

template ๋‹น ๋‹ค์ˆ˜์˜ data sources

๋‹จ์ผ ํ…œํ”Œ๋ฆฟ์— ๋Œ€ํ•ด ๋‹ค์ˆ˜์˜ ๋ฐ์ดํ„ฐ์…‹์„ ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•˜๊ฑฐ๋‚˜ ํ…Œ์ŠคํŠธํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ์š”์ฒญ URI์—์„œ 'data' ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์—ฌ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Example:

http://localhost:6780/home.hbs?data=mytestdata

ํ™•์žฅ ํŒŒ์ผ์„ ์ง€์ •ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

Helpers

Built-in helpers:

  • with
  • each
  • if
  • unless
  • log
  • block
  • partial
  • precompile
  • embedded
  • i18n and i18nJs
  • string helpers
  • conditional helpers

with, each, if, unless:

built-in helper documentation๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

block and partial

Block and partial helpers๊ฐ€ ํ•จ๊ป˜ ์ž‘๋™ํ•˜์—ฌ Template Inheritance์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Usage:

  {{#block "title"}}
    ...
  {{/block}}

context: region์˜ ์ด๋ฆ„์„ ์ •์˜ํ•˜๋Š” ๋ฌธ์ž์—ด ๋ฆฌํ„ฐ๋Ÿด.

Usage:

  {{#partial "title"}}
    ...
  {{/partial}}

context: region์˜ ์ด๋ฆ„์„ ์ •์˜ํ•˜๋Š” ๋ฌธ์ž์—ด ๋ฆฌํ„ฐ๋Ÿด.

precompile

handlebars.js๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Handlebars.java ํ…œํ”Œ๋ฆฟ์„ JavaScript๋กœ Precompile

user.hbs

Hello {{this}}!

home.hbs

<script type="text/javascript">
  {{precompile "user"}}
</script>

Output:

<script type="text/javascript">
  (function () {
    var template = Handlebars.template,
      templates = (Handlebars.templates = Handlebars.templates || {});
    templates["user"] = template(function (
      Handlebars,
      depth0,
      helpers,
      partials,
      data
    ) {
      helpers = helpers || Handlebars.helpers;
      var buffer = "",
        functionType = "function",
        escapeExpression = this.escapeExpression;

      buffer += "Hi ";
      depth0 = typeof depth0 === functionType ? depth0() : depth0;
      buffer += escapeExpression(depth0) + "!";
      return buffer;
    });
  })();
</script>

๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฏธ๋ฆฌ ์ปดํŒŒ์ผ๋œ ํ…œํ”Œ๋ฆฟ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

var template = Handlebars.templates["user"];

๊ธฐ๋ณธ์ ์œผ๋กœ /handlebars-v1.3.0.js๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ…œํ”Œ๋ฆฟ์„ ์ปดํŒŒ์ผํ•ฉ๋‹ˆ๋‹ค. handlebars.java 2.x๋ถ€ํ„ฐ handlebars.js 2.x๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

Handlebars handlebars = new Handlebars();
handlebars.handlebarsJsFile("/handlebars-v2.0.0.js");

๋” ์ž์„ธํ•œ ์ •๋ณด๋Š” Precompiling Templates ๋ฌธ์„œ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Usage:

{{precompile "template" [wrapper="anonymous, amd or none"]}}

context: ํ•„์ˆ˜๋กœ ๋“ค์–ด๊ฐ€์•ผํ•˜๋Š” template์˜ ์ด๋ฆ„์ž…๋‹ˆ๋‹ค.

wrapper: "anonymous", "amd" or "none" ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ "anonymous"

maven plugin๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

embedded

The embedded helper๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด <script> HTML ํƒœ๊ทธ ์•ˆ์— ํ•ธ๋“ค๋ฐ” ํ…œํ”Œ๋ฆฟ์„ "ํฌํ•จ"ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

user.hbs

<tr>
  <td>{{firstName}}</td>
  <td>{{lastName}}</td>
</tr>

home.hbs

<html>
  ... {{embedded "user"}} ...
</html>

Output:

<html>
  ...
  <script id="user-hbs" type="text/x-handlebars">
    <tr>
      <td>{{firstName}}</td>
      <td>{{lastName}}</td>
    </tr>
  </script>
  ...
</html>

Usage:

{{embedded "template"}}

context: ํ•„์ˆ˜๋กœ ๋“ค์–ด๊ฐ€์•ผํ•˜๋Š” template์˜ ์ด๋ฆ„์ž…๋‹ˆ๋‹ค.

i18n

{@link ResourceBundle} ์œ„์— ๊ตฌ์ถ•๋œ helper์ž…๋‹ˆ๋‹ค. {@link ResourceBundle}์€ Java์—์„œ ๊ฐ€์žฅ ์ž˜ ์•Œ๋ ค์ง„ ๊ตญ์ œํ™”(i18n) ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค.

Usage:

{{i18n "hello"}}

์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” classpath์˜ ๋ฃจํŠธ์— messages.properties๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

Using a locale:

{{i18n "hello" locale="es_AR"}}

์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” classpath์˜ ๋ฃจํŠธ์— messages_es_AR.properties๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค๋ฅธ bundle ์‚ฌ์šฉ:

{{i18n "hello" bundle="myMessages"}}

์ด๋ฅผ ์œ„ํ•ด์„œ๋Š” ํด๋ž˜์Šค ๊ฒฝ๋กœ์˜ ๋ฃจํŠธ์— 'myMessages.properties'๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

message format ์‚ฌ์šฉ:

{{i18n "hello" "Handlebars.java"}}

hello๊ฐ€ Hola {0}!์ธ ๊ฒฝ์šฐ ๊ฒฐ๊ณผ๋Š” Hola Handlebars.java!์ž…๋‹ˆ๋‹ค.

i18nJs

'ResourceBundle'์„ JavaScript ์ฝ”๋“œ๋กœ ๋ฒˆ์—ญํ•ฉ๋‹ˆ๋‹ค. ์ƒ์„ฑ๋œ ์ฝ”๋“œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— I18n์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค.

Usage:

{{i18nJs [locale] [bundle=messages]}}

locale ์ธ์ˆ˜๊ฐ€ ์žˆ์œผ๋ฉด ํ•ด๋‹น ๋กœ์ผ€์ผ์„ JavaScript๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ locale์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ƒ์„ฑ๋œ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

<script type="text/javascript">
  I18n.defaultLocale = 'es_AR';
  I18n.locale = 'es_AR';
  I18n.translations = I18n.translations || {};
  // Spanish (Argentina)
  I18n.translations['es_AR'] = {
    "hello": "Hi {{arg0}}!"
  }
</script>

๋งˆ์ง€๋ง‰์œผ๋กœ Hi {0}์™€ ๊ฐ™์€ ๋ฉ”์‹œ์ง€ ํŒจํ„ด์„ Hi {{arg0}}๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ I18n JS ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋ณ€์ˆ˜๋ฅผ ๋ณด๊ฐ„ํ•˜๋Š” ๊ฒƒ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

string helpers

StringHelpers์—์„œ ์•ฝ์–ด, ๋Œ€๋ฌธ์ž, ์กฐ์ธ, dateFormat, yesno ๋“ฑ์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. github/jknack/handlebars/helper/StringHelpers.java).

NOTE: string helpers๋ฅผ ๋“ฑ๋กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(๊ธฐ๋ณธ์ ์œผ๋กœ ์ถ”๊ฐ€๋˜์ง€ ์•Š์Œ).

conditional helpers

eq, neq, lt, gt, and, or, not ๋“ฑ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ์€ [ConditionalHelpers](https://github.com/jknack/handlebars.java/blob/master/handlebars/src/main/ java/com/github/jknack/handlebars/helper/ConditionalHelpers.java)์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

NOTE: conditional helpers๋ฅผ ๋“ฑ๋กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(๊ธฐ๋ณธ์ ์œผ๋กœ ์ถ”๊ฐ€๋˜์ง€ ์•Š์Œ).

TypeSafe Templates

TypeSafe Templates๋Š” TypeSafeTemplate interface๋ฅผ ํ™•์žฅํ•˜์—ฌ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด:

// 1
public static interface UserTemplate extends TypeSafeTemplate<User> {

  // 2
  public UserTemplate setAge(int age);

  public UserTemplate setRole(String role);

}

// 3
UserTemplate userTmpl = handlebars.compileInline("{{name}} is {{age}} years old!")
  .as(UserTemplate.class);

userTmpl.setAge(32);

assertEquals("Edgar is 32 years old!", userTmpl.apply(new User("Edgar")));
  1. TypeSafeTemplate interface๋ฅผ ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค.
  2. ํ•„์š”ํ•œ ๋ชจ๋“  ์„ค์ • ๋ฐฉ๋ฒ•์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. set ๋ฉ”์†Œ๋“œ๋Š” 'void' ๋˜๋Š” 'TypeSafeTemplate' ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. as() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด ์œ ํ˜• ์•ˆ์ „ ํ…œํ”Œ๋ฆฟ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

Registering Helpers

Registering Helpers๋ฅผ ๋“ฑ๋กํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

Helper interface๋ฅผ ์‚ฌ์šฉ

handlebars.registerHelper("blog", new Helper<Blog>() {
  public CharSequence apply(Blog blog, Options options) {
    return options.fn(blog);
  }
});
handlebars.registerHelper("blog-list", new Helper<List<Blog>>() {
  public CharSequence apply(List<Blog> list, Options options) {
    String ret = "<ul>";
    for (Blog blog: list) {
      ret += "<li>" + options.fn(blog) + "</li>";
    }
    return new Handlebars.SafeString(ret + "</ul>");
  }
});

HelperSource๋ฅผ ์‚ฌ์šฉ

helper source๋Š” "CharSequence"์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ณต์šฉ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“  ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.

  public static? CharSequence methodName(context?, parameter*, options?) {
  }

์œ„์น˜:

  • ๋ฉ”์„œ๋“œ๋Š” ์ •์ ์ผ ์ˆ˜๋„ ์•„๋‹ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฉ”์„œ๋“œ์˜ ์ด๋ฆ„์ด helper์˜ ์ด๋ฆ„์ด ๋ฉ๋‹ˆ๋‹ค.
  • ์ปจํ…์ŠคํŠธ, ๋งค๊ฐœ ๋ณ€์ˆ˜ ๋ฐ ์˜ต์…˜์€ ๋ชจ๋‘ ์„ ํƒ ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค.
  • ์ปจํ…์ŠคํŠธ ๋ฐ ์˜ต์…˜์ด ์žˆ๋Š” ๊ฒฝ์šฐ ๋ฉ”์„œ๋“œ์˜ ์ฒซ ๋ฒˆ์งธ ๋ฐ ๋งˆ์ง€๋ง‰ ์ธ์ˆ˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ๋ชจ๋‘ helper ๋ฉ”์„œ๋“œ์˜ ์œ ํšจํ•œ ์ •์˜์ž…๋‹ˆ๋‹ค.

public class HelperSource {
  public String blog(Blog blog, Options options) {
    return options.fn(blog);
  }

  public static String now() {
    return new Date().toString();
  }

  public String render(Blog context, String param0, int param1, boolean param2, Options options) {
    return ...
  }
}

...

handlebars.registerHelpers(new HelperSource());

๋˜๋Š” ์ •์  ๋ฉ”์„œ๋“œ๋งŒ ์„ ํ˜ธํ•˜๋Š” ๊ฒฝ์šฐ:

handlebars.registerHelpers(HelperSource.class);

With plain JavaScript

1.1.0๋ถ€ํ„ฐ๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋กœ helper๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

helpers.js:

Handlebars.registerHelper("hello", function (context) {
  return "Hello " + context;
});
handlebars.registerHelpers(new File("helpers.js"));

๋ฉ‹์ง€์ง€ ์•Š์•„์š”?

Helper ์˜ต์…˜

๋งค๊ฐœ ๋ณ€์ˆ˜

handlebars.registerHelper("blog-list", new Helper<Blog>() {
  public CharSequence apply(List<Blog> list, Options options) {
    String p0 = options.param(0);
    assertEquals("param0", p0);
    Integer p1 = options.param(1);
    assertEquals(123, p1);
    ...
  }
});

Bean bean = new Bean();
bean.setParam1(123);

Template template = handlebars.compileInline("{{#blog-list blogs \"param0\" param1}}{{/blog-list}}");
template.apply(bean);

๊ธฐ๋ณธ ๋งค๊ฐœ ๋ณ€์ˆ˜

handlebars.registerHelper("blog-list", new Helper<Blog>() {
  public CharSequence apply(List<Blog> list, Options options) {
    String p0 = options.param(0, "param0");
    assertEquals("param0", p0);
    Integer p1 = options.param(1, 123);
    assertEquals(123, p1);
    ...
  }
});

Template template = handlebars.compileInline("{{#blog-list blogs}}{{/blog-list}}");

Hash

handlebars.registerHelper("blog-list", new Helper<Blog>() {
  public CharSequence apply(List<Blog> list, Options options) {
    String class = options.hash("class");
    assertEquals("blog-css", class);
    ...
  }
});

handlebars.compileInline("{{#blog-list blogs class=\"blog-css\"}}{{/blog-list}}");

๊ธฐ๋ณธ hash

handlebars.registerHelper("blog-list", new Helper<Blog>() {
  public CharSequence apply(List<Blog> list, Options options) {
    String class = options.hash("class", "blog-css");
    assertEquals("blog-css", class);
    ...
  }
});

handlebars.compileInline("{{#blog-list blogs}}{{/blog-list}}");

Error ๋ณด๊ณ 

๊ตฌ๋ฌธ errors

file:line:column: message
   evidence
   ^
[at file:line:column]

Examples:

template.hbs

{{value
/templates.hbs:1:8: found 'eof', expected: 'id', 'parameter', 'hash' or '}'
    {{value
           ^

partial์„ ์ฐพ์„ ์ˆ˜ ์—†๊ฑฐ๋‚˜ ์˜ค๋ฅ˜๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ํ˜ธ์ถœ ์Šคํƒ์ด ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค:

/deep1.hbs:1:5: The partial '/deep2.hbs' could not be found
    {{> deep2
        ^
at /deep1.hbs:1:10
at /deep.hbs:1:10

Helper/Runtime errors

Helper๋‚˜ runtime errors ๋Š” rnans errors์— ๋‘๊ฐ€์ง€๋ฅผ ์ œ์™ธํ•˜๊ณ  ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค:

  1. ๋ฌธ์ œ์˜ ์œ„์น˜๊ฐ€ ์ •ํ™•ํ•  ์ˆ˜๋„ ์žˆ๊ณ  ์•„๋‹ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. stack-trace๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

Examples:

Block helper:

public CharSequence apply(final Object context, final Options options) throws IOException {
  if (context == null) {
    throw new IllegalArgumentException(
        "found 'null', expected 'string'");
  }
  if (!(context instanceof String)) {
    throw new IllegalArgumentException(
        "found '" + context + "', expected 'string'");
  }
  ...
}

base.hbs

{{#block}} {{/block}}

Handlebars.java reports:

/base.hbs:2:4: found 'null', expected 'string'
    {{#block}} ... {{/block}}

๊ฐ„๋‹จํžˆ ๋งํ•ด์„œ helper์—์„œ ์˜ˆ์™ธ๋ฅผ ๋˜์งˆ ์ˆ˜ ์žˆ๊ณ  Handlebars.java๋Š” ํŒŒ์ผ ์ด๋ฆ„, ์ค„, ์—ด ๋ฐ ์ฆ๊ฑฐ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

Advanced Usage

Context stack ํ™•์žฅ

๋ชจ๋“  ๋‹จ์ผ ๋ณด๊ธฐ/ํŽ˜์ด์ง€์—์„œ ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์— ์•ก์„ธ์Šคํ•ด์•ผ ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
Context-stack์— ์—ฐ๊ฒฐํ•˜์—ฌ ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋ฅผ ๊ฒŒ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•ด๋ณด์„ธ์š”:

 hookContextStack(Object model, Template template) {
   User user = ....;// ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž ๊ฐ€์ ธ์˜ค๊ธฐ
   Map moreData = ...;
   Context context = Context
     .newBuilder(model)
       .combine("user", user)
       .combine(moreData)
       .build();
   template.apply(context);
   context.destroy();
 }

'hookContextStack' ๋ฉ”์†Œ๋“œ๋Š” ์–ด๋””์— ์žˆ์„๊นŒ์š”? ๊ทธ๊ฒƒ์€ ๋‹น์‹ ์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์•„ํ‚คํ…์ฒ˜์— ๋‹ฌ๋ ค ์žˆ์Šต๋‹ˆ๋‹ค.

Using the ValueResolver

๊ธฐ๋ณธ์ ์œผ๋กœ Handlebars.java๋Š” JavaBean ๋ฉ”์†Œ๋“œ(์ฆ‰, public getXxx ๋ฐ isXxx ๋ฉ”์†Œ๋“œ)์™€ Map๋กœ ๊ฐ’์„ ํ•ด์„ํ•ฉ๋‹ˆ๋‹ค.

๊ฐ’์„ ํ•ด์„ํ•˜๋Š” ๊ฒƒ์„ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์„น์…˜์—์„œ๋Š” ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

The JavaBeanValueResolver

"get/is" ์ ‘๋‘์‚ฌ๊ฐ€ ๋ถ™์€ public ๋ฉ”์„œ๋“œ์˜ ๊ฐ’์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

Context context = Context
  .newBuilder(model)
  .resolver(JavaBeanValueResolver.INSTANCE)
  .build();

The FieldValueResolver

no-static ํ•„๋“œ์˜ ๊ฐ’์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

Context context = Context
  .newBuilder(model)
  .resolver(FieldValueResolver.INSTANCE)
  .build();

The MapValueResolver

java.util.Map ๊ฐ์ฒด์˜ ๊ฐ’์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

Context context = Context
  .newBuilder(model)
  .resolver(MapValueResolver.INSTANCE)
  .build();

The MethodValueResolver

public ๋ฉ”์„œ๋“œ์˜ ๊ฐ’์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

Context context = Context
  .newBuilder(model)
  .resolver(MethodValueResolver.INSTANCE)
  .build();

The JsonNodeValueResolver

'JsonNode' ๊ฐ์ฒด์˜ ๊ฐ’์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

Context context = Context
  .newBuilder(model)
  .resolver(JsonNodeValueResolver.INSTANCE)
  .build();

Jackson 1.x๊ณผ Jackson 2.x ๋ชจ๋“ˆ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

Multiples value resolvers ์‚ฌ์šฉ

Context context = Context
  .newBuilder(model)
  .resolver(
      MapValueResolver.INSTANCE,
      JavaBeanValueResolver.INSTANCE,
      FieldValueResolver.INSTANCE
  ).build();

 

The Cache System

Cache System์€ ํ™•์žฅ์„ฑ๊ณผ ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•˜๋„๋ก ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ TemplateCache ์‹œ์Šคํ…œ์— ๋Œ€ํ•œ ๊ฐ„๋žตํ•œ ๋ณด๊ธฐ์ž…๋‹ˆ๋‹ค.

 public interface TemplateCache {

  /**
   * ์บ์‹œ์—์„œ ๋ชจ๋“  ๋งคํ•‘์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.
   */
  void clear();

  /**
   * ์ด ์บ์‹œ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์ด ์†Œ์Šค์— ๋Œ€ํ•œ ๋งคํ•‘์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.
   *
   * @param source ๋งคํ•‘์ด ์บ์‹œ์—์„œ ์ œ๊ฑฐ๋  ์†Œ์Šค
   */
  void evict(TemplateSource source);

  /**
   * ์ด ์บ์‹œ๊ฐ€ ์ง€์ •๋œ ํ‚ค๋ฅผ ๋งคํ•‘ํ•˜๋Š” ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
   *
   * @param source ์—ฐ๊ฒฐ๋œ ํ…œํ”Œ๋ฆฟ์ด ๋ฐ˜ํ™˜๋  ์†Œ์Šค์ž…๋‹ˆ๋‹ค.
   * @param parser Handlebars ํŒŒ์„œ
   * @return A template.
   * @throws IOException input์ด ํŒŒ์‹ฑ๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ
   */
  Template get(TemplateSource source, Parser parser) throws IOException;
}

๋ณด์‹œ๋‹ค์‹œํ”ผ put ๋ฉ”์†Œ๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ๋ฌด๊ฑฐ์šด ์ž‘์—…์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์บ์‹œ ์‹œ์Šคํ…œ์˜ ํ•ต์‹ฌ์ธ get ๋ฉ”์†Œ๋“œ์—์„œ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ Handlebars.java๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ null ์บ์‹œ ๊ตฌํ˜„(์ผ๋ช… no cache at all)์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Template get(TemplateSource source, Parser parser) throws IOException {
  return parser.parse(source);
}

'null' ์บ์‹œ ์™ธ์—๋„ Handlebars.java๋Š” ์„ธ ๊ฐ€์ง€ ๊ตฌํ˜„์„ ๋” ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

  1. ConcurrentMapTemplateCache: ํŒŒ์ผ์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๋Š” ConcurrentMap ์œ„์— ๊ตฌ์ถ•๋œ ํ…œํ”Œ๋ฆฟ ์บ์‹œ ๊ตฌํ˜„์ž…๋‹ˆ๋‹ค.
    ์ด ๊ตฌํ˜„์€ ์ผ๋ฐ˜์ ์œผ๋กœ ๋งค์šฐ ์ž˜ ์ž‘๋™ํ•˜์ง€๋งŒ ๋‘˜ ์ด์ƒ์˜ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์ผํ•œ ํ…œํ”Œ๋ฆฟ์„ ์ปดํŒŒ์ผํ•  ์ˆ˜ ์žˆ๋Š” ์ž‘์€ ์—ฌ์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋งค์šฐ ๋น ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— Handlebars.java์—์„œ ํฐ ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    ๊ทธ๋Ÿฌ๋‚˜ ์ด ๊ตฌํ˜„์„ ์›ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด HighConcurrencyTemplateCache ํ…œํ”Œ๋ฆฟ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. HighConcurrencyTemplateCache: ํŒŒ์ผ์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๋Š” ConcurrentMap ์œ„์— ๊ตฌ์ถ•๋œ ํ…œํ”Œ๋ฆฟ ์บ์‹œ ๊ตฌํ˜„์ž…๋‹ˆ๋‹ค.
    ์ด ์บ์‹œ ๊ตฌํ˜„์€ ConcurrentMapTemplateCache์— ์˜ํ•ด ์ƒ์„ฑ๋œ ์ฐฝ์„ 0์œผ๋กœ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.
    ์ด๊ฒƒ์€ Java Concurrency in Practice์—์„œ ์„ค๋ช…ํ•œ ํŒจํ„ด์„ ๋”ฐ๋ฅด๋ฉฐ ์Šค๋ ˆ๋“œ ์ˆ˜์— ๊ด€๊ณ„์—†์ด ํ…œํ”Œ๋ฆฟ์ด ํ•œ ๋ฒˆ๋งŒ ์ปดํŒŒ์ผ๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  3. GuavaTemplateCache: Google Guava๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌ์ถ•๋œ ํ…œํ”Œ๋ฆฟ ์บ์‹œ ๊ตฌํ˜„์ž…๋‹ˆ๋‹ค. handlebars-guava-cache module์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์„ ํ†ตํ•ด ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก Handlebars.java๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

Handlebars hbs = new Handlebars()
  .with(new MyCache());

MissingValueResolver ์‚ฌ์šฉ (@deprecated)

NOTE: MissingValueResolver๋Š” <= 1.3.0์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. > 1.3.0์˜ ๊ฒฝ์šฐ helper ๋ˆ„๋ฝ์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

MissingValueResolver๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด null๋กœ ํ™•์ธ๋œ {{๋ณ€์ˆ˜}} ํ‘œํ˜„์‹์— ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  MissingValueResolver missingValueResolver = new MissingValueResolver() {
    public String resolve(Object context, String name) {
      //๊ธฐ๋ณธ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ์˜ˆ์™ธ๋ฅผ ๋˜์ง‘๋‹ˆ๋‹ค.
      ...;
    }
  };
  Handlebars handlebars = new Handlebars().with(missingValueResolver);

Helper Missing

๊ธฐ๋ณธ์ ์œผ๋กœ Handlebars.java๋Š” helper๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ java.lang.IllegalArgumentException()์„ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
special helper์ธ helperMissing์„ ์ œ๊ณตํ•˜์—ฌ ๊ธฐ๋ณธ ๋™์ž‘์„ ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ์‹œ:

  handlebars.registerHelperMissing(new Helper<Object>() {
    @Override
    public CharSequence apply(final Object context, final Options options) throws IOException {
      return options.fn.text();
    }
  });

๋ฌธ์ž์—ด ํ˜•์‹ ๋งค๊ฐœ๋ณ€์ˆ˜

stringParams: true๋ฅผ ์„ค์ •ํ•˜๋ฉด ๋งค๊ฐœ๋ณ€์ˆ˜ ์ด๋ฆ„์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ์‹œ:

{{sayHi this edgar}}
  Handlebars handlebars = new Handlebars()
    .stringParams(true);

  handlebars.registerHelper("sayHi", new Helper<Object>() {
    public Object apply(Object context, Options options) {
      return "Hello " + options.param(0) + "!";
    }
  });

๊ฒฐ๊ณผ:

Hello edgar!

์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๊นŒ? stringParams: true๋Š” ๊ฐ’์ด ์ปจํ…์ŠคํŠธ ์Šคํƒ์— ์—†์œผ๋ฉด ์ด๋ฆ„์— ๋Œ€ํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ™•์ธํ•˜๋„๋ก Handlebars.java์— ์ง€์‹œํ•ฉ๋‹ˆ๋‹ค.

๋ฌดํ•œ ๋ฃจํ”„ ํ—ˆ์šฉ

๊ธฐ๋ณธ์ ์œผ๋กœ Handlebars.java๋Š” ๋ถ€๋ถ„ ํ˜ธ์ถœ์ด ์ง์ ‘ ๋˜๋Š” ๊ฐ„์ ‘์ ์œผ๋กœ ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
Handlebars.inifiteLoops(true)๋ฅผ ์„ค์ •ํ•˜์—ฌ ์ด๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์ง€๋งŒ StackOverflowError์— ์ฃผ์˜ํ•˜์„ธ์š”.

Pretty Print

Mustache Spec์—๋Š” ๊ณต๋ฐฑ๊ณผ ์ƒˆ ์ค„์„ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•œ ๋ช‡ ๊ฐ€์ง€ ๊ทœ์น™์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋น„ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
Handlebars.prettyPrint(true)๋ฅผ ์„ค์ •ํ•˜์—ฌ ์ด ๊ธฐ๋Šฅ์„ ์ผค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Modules

Jackson 1.x

Maven:

 <dependency>
   <groupId>com.github.jknack</groupId>
   <artifactId>handlebars-json</artifactId>
   <version>${handlebars-version}</version>
 </dependency>

Usage:

 handlebars.registerHelper("json", JacksonHelper.INSTANCE);
 {{json context [view="foo.MyFullyQualifiedClassName"] [escapeHTML=false] [pretty=false]}}

๋Œ€์•ˆ:

 handlebars.registerHelper("json", new JacksonHelper().viewAlias("myView",
   foo.MyFullyQualifiedClassName.class);
 {{json context [view="myView"] [escapeHTML=false] [pretty=false]}}

context: ๊ฐ์ฒด๋Š” null์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

view: Jackson View์˜ ์ด๋ฆ„์ด๋ฉฐ ์„ ํƒ ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค.

escapeHTML: True, JSON ์ฝ˜ํ…์ธ ์— HTML ๋ฌธ์ž๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๊ณ  ์ด ๋ฌธ์ž๋ฅผ ์ด์Šค์ผ€์ดํ”„ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ. ๊ธฐ๋ณธ๊ฐ’: false.

pretty: True, JSON ์ฝ˜ํ…์ธ ์˜ ํ˜•์‹์„ ์ง€์ •ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ. ๊ธฐ๋ณธ๊ฐ’: false.

Jackson 2.x

Maven:

 <dependency>
   <groupId>com.github.jknack</groupId>
   <artifactId>handlebars-jackson2</artifactId>
   <version>${handlebars-version}</version>
 </dependency>

helper ์ด๋ฆ„์„ ์ œ์™ธํ•˜๊ณ  Jackson1.x์™€ ๋™์ผ: Jackson2Helper

Markdown

Maven:

 <dependency>
   <groupId>com.github.jknack</groupId>
   <artifactId>handlebars-markdown</artifactId>
   <version>${handlebars-version}</version>
 </dependency>

Usage:

 handlebars.registerHelper("md", new MarkdownHelper());
 {{md context}}

context: ํ•„์ˆ˜์ ์œผ๋กœ ๊ฐœ์ฒด ๋˜๋Š” null ์ž…๋‹ˆ๋‹ค.

Humanize

Maven:

 <dependency>
   <groupId>com.github.jknack</groupId>
   <artifactId>handlebars-humanize</artifactId>
   <version>${handlebars-version}</version>
 </dependency>

Usage:

 // Humanize helper๋ฅผ ๋ชจ๋‘ ๋“ฑ๋ก
 HumanizeHelper.register(handlebars);

์ถ”๊ฐ€ ์ •๋ณด๋Š” [HumanizeHelper] (https://github.com/jknack/handlebars.java/blob/master/handlebars-humanize/src/main/java/com/github/jknack/handlebars/HumanizeHelper.java)์˜ JavaDoc์„ ์ฐธ์กฐํ•˜์„ธ์š”.

SpringMVC

Maven:

 <dependency>
   <groupId>com.github.jknack</groupId>
   <artifactId>handlebars-springmvc</artifactId>
   <version>${handlebars-version}</version>
 </dependency>

Value resolvers ์‚ฌ์šฉ:

 HandlebarsViewResolver viewResolver = ...;

 viewResolver.setValueResolvers(...);

๋˜ํ•œ HandlebarsViewResolver๋Š” Spring์˜ MessageSource ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” message helper๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

{{message "code" [arg]* [default="default message"]}}

์„ค๋ช…:

  • code: ํ•„์ˆ˜์ธ ๋ฉ”์‹œ์ง€์˜ ์ฝ”๋“œ
  • arg: ์„ ํƒ ์‚ฌํ•ญ์ธ ๋ฉ”์‹œ์ง€์˜ ์ธ์ˆ˜
  • default: ์„ ํƒ ์‚ฌํ•ญ์ธ ๊ธฐ๋ณธ๊ฐ’์˜ ๋ฉ”์‹œ์ง€

HandlebarsViewResolver๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

์„ฑ๋Šฅ

Handlebars.java๋Š” ํ˜„๋Œ€์ ์ด๊ณ  ์™„์ „ํ•œ ๊ธฐ๋Šฅ์„ ๊ฐ–์ถ˜ ํ…œํ”Œ๋ฆฟ ์—”์ง„์ด์ง€๋งŒ ๋งค์šฐ ์šฐ์ˆ˜ํ•œ ์„ฑ๋Šฅ(Hbs)๋„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

Template Comparison

๋ฒค์น˜๋งˆํฌ ์†Œ์Šค ์ฝ”๋“œ๋Š” ์—ฌ๊ธฐ์ž…๋‹ˆ๋‹ค: https://github.com/mbosecke/template-benchmark

์•„ํ‚คํ…์ฒ˜ ๋ฐ API ์„ค๊ณ„

  • Handlebars.java follows the JavaScript API with some minors exceptions due to the nature of the Java language.
  • Handlebars.java๋Š” Java ์–ธ์–ด์˜ ํŠน์„ฑ์œผ๋กœ ์ธํ•ด JavaScript API์˜ ์ผ๋ถ€ minors exceptions๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.
  • ํŒŒ์„œ๋Š” ANTLR v4 ์œ„์— ๊ตฌ์ถ•๋ฉ๋‹ˆ๋‹ค.
  • ๋ฐ์ดํ„ฐ๋Š” ๊ธฐ๋ณธ ์œ ํ˜•(int, boolean, double ๋“ฑ), ๋ฌธ์ž์—ด, ๋งต, ๋ชฉ๋ก ๋˜๋Š” JavaBeans ๊ฐœ์ฒด๋กœ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.
  • Helper๋Š” type-safe ์ž…๋‹ˆ๋‹ค.
  • Handlebars.java๋Š” thread-safe ์ž…๋‹ˆ๋‹ค.

Handlebars.java์™€ Handlebars.js์˜ ์ฐจ์ด์ 

Handlebars.java์˜ scope resolution์€ Mustache Spec์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด:

Given:

{
  "value": "parent",
  "child": {}
}

and

Hello {{#child}}{{value}}{{/child}}

will be:

Hello parent

์ด์ œ Handlebars.js๊ฐ€ ์žˆ๋Š” ๋™์ผํ•œ ๋ชจ๋ธ ๋ฐ ํ…œํ”Œ๋ฆฟ์€:

Hello

์ด๋Š” Handlebars.js๊ฐ€ ์ปจํ…์ŠคํŠธ ์Šคํƒ์—์„œ current scope์—์„œ ๋ˆ„๋ฝ๋œ ์†์„ฑ์„ ์ฐพ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค(์ด๋Š” Mustache ์‚ฌ์–‘๊ณผ ์ผ์น˜ํ•จ).

this.๋กœ ์†์„ฑ์„ ํ•œ์ •ํ•˜์—ฌ Handlebars.java์—์„œ ์ปจํ…์ŠคํŠธ ์Šคํƒ lookup์„ ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค:

Hello {{#child}}{{this.value}}{{/child}}

Handlebars.java์™€ Mustache.js์˜ ์ฐจ์ด์ 

  • partial์„ โ€‹โ€‹๋กœ๋“œํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ Handlebars.java์—์„œ java.io.FileNotFoundException์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

Status

Mustache 1.0 Compliant

Handlebars.js Compliant

Dependencies

+- org.slf4j:slf4j-api:jar:1.6.4

FAQ

Contribute๋ฅผ ํ•˜๊ณ  ์‹ถ์œผ์‹ ๊ฐ€์š”?

  • ์ด project๋ฅผ Fork ํ•ฉ๋‹ˆ๋‹ค.
  • ์–ด๋–ค ์ž‘์—…์„ ํ•ด์•ผ ํ• ์ง€ ๊ถ๊ธˆํ•˜์„ธ์š”? ์ž‘์—…/๋ฒ„๊ทธ ๋ชฉ๋ก์„ ๋ณด๊ณ  ์ž‘์—…ํ•˜๊ณ  ์‹ถ์€ ๊ฒƒ์„ ์„ ํƒํ•˜์‹ญ์‹œ์˜ค.
  • ํ•˜๋‚˜ ์ด์ƒ์˜ helper๋ฅผ donate ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? ์ปค๋ฎค๋‹ˆํ‹ฐ helper๋ฅผ ์œ„ํ•œ ์ €์žฅ์†Œ handlebars=helpers๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.
  • issue๋‚˜ fix๋Š” issues list์—์„œ ๋งŒ๋“œ์„ธ์š”.
  • mailing list์— ๊ฒŒ์‹œ๋œ ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๋‹ต๋ณ€์„ ์•Œ๊ณ  ์žˆ๋‹ค๋ฉด ์ฃผ์ €ํ•˜์ง€ ๋ง๊ณ  ๋‹ต์žฅ์„ ์ž‘์„ฑํ•˜์„ธ์š”.
  • mailing list์—์„œ ์•„์ด๋””์–ด๋ฅผ ๊ณต์œ ํ•˜๊ฑฐ๋‚˜ ์งˆ๋ฌธ์„ ํ•˜์„ธ์š”. ์ฃผ์ €ํ•˜์ง€ ๋ง๊ณ  ๋‹ต์žฅ์„ ์ž‘์„ฑํ•˜์„ธ์š”. ๊ทธ๋Ÿฌ๋ฉด javadocs/FAQ๋ฅผ ๊ฐœ์„ ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.
  • ํŠน์ • ๊ธฐ๋Šฅ์„ ๋†“์น˜๋ฉด mailing list๋ฅผ ์ฐพ์•„๋ณด๊ฑฐ๋‚˜ ๋ฌธ์˜ํ•˜์„ธ์š”. ์ฃผ์ €ํ•˜์ง€ ๋ง๊ณ  ๋‹ต์žฅ์„ ์ž‘์„ฑํ•˜๊ณ  ์ƒ˜ํ”Œ ์ฝ”๋“œ๋ฅผ ๋ณด์—ฌ์ฃผ์„ธ์š”. ๋ฌธ์ œ๋ฅผ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.
  • handlebars.java๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ํ™•์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๋ฌผ์„ ์ž‘์„ฑํ•˜์‹ญ์‹œ์˜ค.
  • ๋ถˆ๋ถ„๋ช…ํ•œ ๊ฒƒ์„ ๋ฐœ๊ฒฌํ•˜๋ฉด javadoc/์˜ˆ์™ธ ๋ฉ”์‹œ์ง€์— ๋Œ€ํ•œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ œ์•ˆํ•˜์‹ญ์‹œ์˜ค.
  • ๋ฌธ์„œ์— ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์ง๊ด€์ ์ด์ง€ ์•Š๊ฑฐ๋‚˜ ๋”ฐ๋ฅด๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. - ์•Œ๋ ค์ฃผ์‹œ๋ฉด ์ œ์•ˆ์— ๋”ฐ๋ผ ๊ฐœ์„ ํ•˜๋„๋ก ๋…ธ๋ ฅํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์–ด๋–ค ๊ฑด์„ค์ ์ธ ๋น„ํŒ๋„ ๋งค์šฐ ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์—ฌ๊ฐ€ ์‹œ๊ฐ„์— ๊ฐœ๋ฐœ๋˜๊ณ  ๋ฌธ์„œํ™”๋œ ์˜คํ”ˆ ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋ผ๋Š” ๊ฒƒ์„ ์žŠ์ง€ ๋งˆ์‹ญ์‹œ์˜ค.

๋„์›€๋ง ๋ฐ ์ง€์›

๋„์›€๋ง ๋ฐ ํ† ๋ก 

๋ฒ„๊ทธ์™€ Issues, Features

๊ธฐ๋ถ€

Edgar ๋งฅ์ฃผ๋ฅผ ์‚ฌ์„ธ์š”!

๊ด€๋ จ ํ”„๋กœ์ ํŠธ

์ €์ž‘์ž

Edgar Espina

๋ผ์ด์„ผ์Šค

Apache License 2

 

 

 

์ถœ์ฒ˜ :  jknack/handlebars.java