
محرك قوالب Mustache بلغة Java آمن من ناحية الأنواع.
تُترجم القوالب إلى كود مصدري Java قابل للقراءة، وتُفحص ارتباطات القيم بشكل ثابت.
الوثائق متاحة أيضًا على javadoc.io لكنها غير مجمّعة مثل ما سبق.
الوثائق المجمّعة هي الوثائق المفضلة، وبقية هذا README مخصصة أساسًا لأغراض دعاية تسويقية.
للإصدارات السابقة:
https://jstach.io/doc/jstachio/VERSION/apidocs
حيث VERSION هو الإصدار الذي تريده.
مُغطى في why_jstachio_is_better.md.
صيغة Mustache (الإصدار 1.3) الخالية من المنطق.
احصل على دعم شبيه بـ JEP 430 اليوم ولكن بقوة أكبر.
تُترجم القوالب إلى كود Java
ارتباطات القيم تُفحص بشكل ثابت.
يمكن الإشارة إلى الطرق والحقول وطرق الوصول (getter-methods) في القوالب.
رسائل خطأ ودية مع سياق.
صفر إعدادات. لا حاجة إلى إضافات أو تعديلات. كل شيء يتم بواسطة javac القياسي مع أي IDE و/أو نظام بناء.
تُدعم القوالب غير HTML. مجموعة أنواع المحتوى المدعومة للهروب (escaping) قابلة للتوسيع.
تُدعم التخطيطات عبر مواصفة وراثة Mustache.
نقطة تمديد لخدمة العرض الاحتياطية عبر ServiceLoader
تخصيص الأنواع المسموح بإخراجها، وإلا خطأ في المترجم (لتجنب toString على الفئات التي لا تمتلك toString ودية).
مُنسِّق لـ toString المخصص للمتغيرات في وقت التشغيل
إضافة واجهات implements إضافية إلى الكود المُولَّد لإضافات شبيهة بالسمات (@JStacheInterfaces)
دعم قوي لـ Lambda
دعم Map<String, ?>
ليس هدف هذا المشروع أن يكون أسرع محرك قوالب Java!
(ومع ذلك، فهو حاليًا الأسرع الذي أعرفه عند آخر تحديث لهذا README)
ليس أن الأداء مهم كثيرًا مع لغات القوالب (ونادرًا ما يكون هو عنق الزجاجة) لكن JStachio سريع جدًا:
https://github.com/agentgt/template-benchmark


@JStache(template = """
{{#people}}
{{message}} {{name}}! You are {{#ageInfo}}{{age}}{{/ageInfo}} years old!
{{#-last}}
That is all for now!
{{/-last}}
{{/people}}
""")
public record HelloWorld(String message, List<Person> people) implements AgeLambdaSupport {
}
public record Person(String name, LocalDate birthday) {
}
public record AgeInfo(long age, String date) {
}
public interface AgeLambdaSupport {
@JStacheLambda
default AgeInfo ageInfo(Person person) {
long age = ChronoUnit.YEARS.between(person.birthday(), LocalDate.now());
String date = person.birthday().format(DateTimeFormatter.ISO_DATE);
return new AgeInfo(age, date);
}
}
@Test
public void testPerson() throws Exception {
Person rick = new Person("Rick", LocalDate.now().minusYears(70));
Person morty = new Person("Morty", LocalDate.now().minusYears(14));
Person beth = new Person("Beth", LocalDate.now().minusYears(35));
Person jerry = new Person("Jerry", LocalDate.now().minusYears(35));
String actual = JStachio.render(new HelloWorld("Hello alien", List.of(rick, morty, beth, jerry)));
String expected = """
Hello alien Rick! You are 70 years old!
Hello alien Morty! You are 14 years old!
Hello alien Beth! You are 35 years old!
Hello alien Jerry! You are 35 years old!
That is all for now!
""";
assertEquals(expected, actual);
}
<properties>
<io.jstach.version>0.6.0-SNAPSHOT</io.jstach.version>
</properties>
...
<dependencies>
<dependency>
<groupId>io.jstach</groupId>
<artifactId>jstachio</artifactId>
<version>${io.jstach.version}</version>
</dependency>
</dependencies>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source> <!-- 17 is the minimum -->
<target>17</target> <!-- 17 is the minimum -->
<annotationProcessorPaths>
<path>
<groupId>io.jstach</groupId>
<artifactId>jstachio-apt</artifactId>
<version>${io.jstach.version}</version>
</path>
<!-- other annotation processors -->
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
ملاحظة: يتم جلب jar التعليقات التوضيحية (jstachio-annotation) بشكل غير مباشر.
dependencies {
implementation 'io.jstach:jstachio:VERSION'
annotationProcessor 'io.jstach:jstachio-apt:VERSION'
}
{{#name}}
<p>Name: {{.}}, Name Length is {{length}}</p>
{{/name}}
<p>Age: {{ age }}</p>
<p>Achievements:</p>
<ul>
{{#array}}
<li>{{.}}</li>
{{/array}}
</ul>
{{^array}}
<p>No achievements</p>
{{/array}}
<p>Items:</p>
<ol>
{{#list1}}
<li>{{value}}</li>
{{/list1}}
</ol>
يمكن استخدام الفئة التالية لتوفير البيانات الفعلية لتعبئة القالب أعلاه.
@JStache(
// points to src/main/resources/user.mustache file
path = "user.mustache",
// or alternatively you can inline the template
template = "",
)
public record User(String name, int age, String[] array, List<Item<String>> list) {
public static class Item<T> {
private final T value;
public Item(T value) {
this.value = value;
}
T value() {
return value;
}
}
}
سيتم توليد فئة جديدة UserRenderer آليًا من الكود أعلاه. يمكن استخدام هذه الفئة لعرض القالب معبأً بالبيانات الفعلية. لعرض القالب يمكن استخدام الكود التالي:
class Main {
public static void main(String[] args) throws IOException {
User user = new User("John Doe", 21, new String[] {"Knowns nothing"}, list);
StringBuilder appendable = new StringBuilder();
JStachio.render(user, appendable);
}
}
ستكون نتيجة تشغيل هذا الكود:
<p>Name: John Doe, Name Length is 8</p>
<p>Age: 21</p>
<p>Achievements:</p>
<ul>
<li>Knowns nothing</li>
</ul>
<p>Items:</p>
<ol>
<li>helmet</li>
<li>shower</li>
</ol>
الإشارة إلى حقول غير موجودة، أو حقول بنوع غير قابل للعرض، ينتج عنها جميعًا أخطاء في وقت الترجمة. تُبلَّغ هذه الأخطاء في وقت ترجمة مشروعك بالإضافة إلى الأخطاء الأخرى المحتملة في مصادر Java.
target/classes/user.mustache:5: error: Field not found in current context: 'age1'
<p>Age: {{ age1 }} ({{birthdate}}) </p>
^
symbol: mustache directive
location: mustache template
target/classes/user.mustache:5: error: Unable to render field: type error: Can't render data.birthdate expression of java.util.Date type
<p>Age: {{ age }} ({{birthdate}}) </p>
^
symbol: mustache directive
location: mustache template
انظر مشروع test/examples لمزيد من الأمثلة.
بشكل أساسي، تمتلك التعدادات (enums) مفاتيح منطقية هي اسم التعداد (Enum.name()) يمكن استخدامها كأقسام شرطية.
افترض أن light هو enum مثل:
public enum Light {
RED,
GREEN,
YELLOW
}
يمكنك الاختيار الشرطي على الـ enum مثل مطابقة النمط:
{{#light.RED}}
STOP
{{/light.RED}}
{{#light.GREEN}}
GO
{{/light.GREEN}}
{{#light.YELLOW}}
Proceeed with caution
{{/light.YELLOW}}
JStachio متوافق مع مفاتيح الفهرس الخاصة بـ handlebars و JMustache معًا للأقسام القابلة للتكرار.
-first قيمة منطقية تكون صحيحة عندما تكون في العنصر الأول-last قيمة منطقية تكون صحيحة عندما تكون في آخر عنصر في القائمة القابلة للتكرار-index فهرس يبدأ من واحد. العنصر الأول سيكون 1 وليس 0يدعم JStachio استدعاءات أقسام lambda بطريقة مشابهة لـ JMustache. فقط ضع وسمًا على طرقك بـ @JStacheLambda وسيتم استخدام النماذج المُعادة لعرض محتويات قسم lambda. يمكن تمرير أعلى مكدس السياق إلى lambda.
على عكس المواصفة، لا يدعم JStachio إرجاع قوالب ديناميكية يتم عرضها بعد ذلك على مكدس السياق. ومع ذلك يمكن تحقيق مخرجات ديناميكية من خلال قيام المستدعي بتغيير محتويات قسم lambda، لأن محتويات القسم تعمل كقالب مضمّن.
الفكرة هي إنشاء محرك قوالب يجمع بين فلسفة mustache الخالية من المنطق وبين مبدأ المسؤولية الواحدة والكتابة الثابتة في Java. الفحص الكامل لبناء الجملة وربط البيانات في وقت الترجمة هو المتطلب الرئيسي.
حاليًا يتم توليد كود Java للقوالب. يجب ألا يفشل كود Java المُولَّد في الترجمة أبدًا. إذا تعذّر توليد كود Java صالح من قالب معين، فيجب توليد خطأ ودّي في وقت الترجمة يشير إلى ملف القالب. لا ينبغي أبدًا أن يطّلع المستخدمون على كود Java المُولَّد.
تستخدم mustache الأصلية كائنات JavaScript لتحديد سياق العرض. يتم ربط حقول كائنات JavaScript المحددة بحقول القالب.
تستخدم Static mustache كائنات Java لتحديد سياق العرض. يتم تحديد ربط حقول القالب والتحقق منه في وقت الترجمة. الحقول المفقودة تُعد خطأ في وقت الترجمة.
JStachio مرخّص بموجب رخصة BSD ثلاثية البنود.
دعم Optional<?>
متوافق مع امتدادات فهرس القوائم في JMustache و Handlebars (مثل -first و -last و -index)
إنه إلى حد بعيد أسرع محرك قوالب Java شبيه بـ Mustache، ومن الأسرع عمومًا.
صفر تبعيات بخلاف JStachio نفسه
يتوفر خيار بعدم وجود أي تبعيات وقت تشغيل إطلاقًا (أي أن كل الكود المطلوب يتم توليده ولا نحتاج حتى إلى jstachio أثناء وقت التشغيل). لا حاجة لاستخدام Maven shade لمعالجات التعليقات التوضيحية ومشاريع صفر التبعيات الأخرى. مفيد أيضًا لمشاريع Graal VM الأصلية لتحقيق أصغر مساحة ممكنة.
دعم من الدرجة الأولى لإطار Spring Framework (بمعنى أن المشروع نفسه سيوفر الإضافات بدلاً من مشروع مساعد)