diff --git a/pom.xml b/pom.xml index aca2db795..e7a9454d7 100644 --- a/pom.xml +++ b/pom.xml @@ -1,320 +1,382 @@ - - 4.0.0 + + 4.0.0 - - org.sonatype.oss - oss-parent - 7 - + + org.sonatype.oss + oss-parent + 7 + org.jbake jbake-core 2.5.0-SNAPSHOT jar - jbake - JBake is a Java based open source static site/blog generator for developers. - http://jbake.org - - - jonbullock - Jonathan Bullock - jonbullock@gmail.com - http://jonathanbullock.com - 0 - - + jbake + JBake is a Java based open source static site/blog generator for developers. + http://jbake.org + + + jonbullock + Jonathan Bullock + jonbullock@gmail.com + http://jonathanbullock.com + 0 + + - - https://github.com/jbake-org/jbake/ - scm:git:git@github.com:jbake-org/jbake.git - scm:git:https://github.com/jbake-org/jbake.git - - - - GitHub Issues - https://github.com/jbake-org/jbake/issues - + + https://github.com/jbake-org/jbake/ + scm:git:git@github.com:jbake-org/jbake.git + scm:git:https://github.com/jbake-org/jbake.git + - - - jbake-dev - jbake-dev@googlegroups.com - jbake-dev+unsubscribe@googlegroups.com - http://groups.google.com/group/jbake-dev - - - jbake-user - jbake-user@googlegroups.com - jbake-user+unsubscribe@googlegroups.com - http://groups.google.com/group/jbake-user - - + + GitHub Issues + https://github.com/jbake-org/jbake/issues + - - - The MIT License (MIT) - http://opensource.org/licenses/MIT - - + + + jbake-dev + jbake-dev@googlegroups.com + jbake-dev+unsubscribe@googlegroups.com + http://groups.google.com/group/jbake-dev + + + jbake-user + jbake-user@googlegroups.com + jbake-user+unsubscribe@googlegroups.com + http://groups.google.com/group/jbake-user + + - - UTF-8 - ${maven.build.timestamp} - yyyy-MM-dd HH:mm:ssa - - 1.5.2 - 2.4 - 1.9 - 2.0.26 - 2.3.20 - 4.11 - 1.4.2 - 8.1.12.v20130726 + + + The MIT License (MIT) + http://opensource.org/licenses/MIT + + + + + UTF-8 + ${maven.build.timestamp} + yyyy-MM-dd HH:mm:ssa + + 1.5.2 + 2.4 + 1.9 + 2.0.26 + 2.3.20 + 4.11 + 1.4.2 + 8.1.12.v20130726 1.6.4 - 2.3.6 + 2.4.1 1.7.6 1.1.1 1.7.0 2.1.3.RELEASE 2.1.1.RELEASE 1.1.1 + 1.3.0 + 0.4.2 + - - ${project.artifactId} - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - - 1.6 - 1.6 - - - - org.apache.maven.plugins - maven-eclipse-plugin - 2.9 - - true - - - - org.apache.maven.plugins - maven-scm-plugin - 1.1 - - - checkout-example-project-freemarker - prepare-package - - checkout - - - ${project.build.directory}/example-project-freemarker - scm:git:git://github.com/jbake-org/jbake-example-project-freemarker.git - - - - checkout-example-project-groovy + + ${project.artifactId} + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.9 + + true + + + + org.apache.maven.plugins + maven-scm-plugin + 1.1 + + + checkout-example-project-freemarker + prepare-package + + checkout + + + ${project.build.directory}/example-project-freemarker + scm:git:git://github.com/jbake-org/jbake-example-project-freemarker.git + + + + + checkout-example-project-groovy + prepare-package + + checkout + + + ${project.build.directory}/example-project-groovy + scm:git:git://github.com/jbake-org/jbake-example-project-groovy.git + + + + + checkout-example-project-thymeleaf + prepare-package + + checkout + + + ${project.build.directory}/example-project-thymeleaf + scm:git:git://github.com/jbake-org/jbake-example-project-thymeleaf.git + + + + + checkout-example-project-jade prepare-package checkout - ${project.build.directory}/example-project-groovy - scm:git:git://github.com/jbake-org/jbake-example-project-groovy.git - - - - checkout-example-project-thymeleaf - prepare-package - - checkout - - - ${project.build.directory}/example-project-thymeleaf - scm:git:git://github.com/jbake-org/jbake-example-project-thymeleaf.git - - - - - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - - - - true - lib/ - org.jbake.launcher.Main - org.jbake.launcher - - - lib/ - - - - - - org.apache.maven.plugins - maven-clean-plugin - 2.4.1 - - - - dist - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 2.4 - - - copy-dependencies - package - - copy-dependencies - - - ${project.build.directory}/lib - test - compile - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 2.4 - - - zip-example-project-freemarker - package - - single - - - example_project_freemarker - false - - src/main/assembly/assembly-example-project-freemarker.xml - - false + ${project.build.directory}/example-project-jade + + scm:git:git://github.com/mariuszs/jbake-example-project-jade.git - - zip-example-project-groovy + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + true + lib/ + org.jbake.launcher.Main + org.jbake.launcher + + + lib/ + + + + + + org.apache.maven.plugins + maven-clean-plugin + 2.4.1 + + + + dist + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.4 + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/lib + test + compile + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4 + + + zip-example-project-freemarker + package + + single + + + example_project_freemarker + false + + src/main/assembly/assembly-example-project-freemarker.xml + + false + + + + zip-example-project-groovy + package + + single + + + example_project_groovy + false + + src/main/assembly/assembly-example-project-groovy.xml + + false + + + + zip-example-project-thymeleaf + package + + single + + + example_project_thymeleaf + false + + src/main/assembly/assembly-example-project-thymeleaf.xml + + false + + + + zip-example-project-jade package single - example_project_groovy + example_project_jade false - src/main/assembly/assembly-example-project-groovy.xml - - false - - - - zip-example-project-thymeleaf - package - - single - - - example_project_thymeleaf - false - - src/main/assembly/assembly-example-project-thymeleaf.xml - - false - - - - make-assembly - package - - single - - - dist - jbake-${project.version} - - src/main/assembly/assembly.xml + src/main/assembly/assembly-example-project-jade.xml false - - - - org.jacoco - jacoco-maven-plugin - 0.7.2.201409121644 - - - prepare-agent - - prepare-agent - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 2.0.1 - - - - - - - - - src/main/resources - true - - - - - - commons-io - commons-io - ${commons.io.version} - - - commons-configuration - commons-configuration - ${commons.configuration.version} - - - com.googlecode.json-simple - json-simple - ${json-simple.version} - - - args4j - args4j - ${args4j.version} - - - org.freemarker - freemarker - ${freemarker.version} + + make-assembly + package + + single + + + dist + jbake-${project.version} + + src/main/assembly/assembly.xml + + false + + + + + + org.jacoco + jacoco-maven-plugin + 0.7.2.201409121644 + + + prepare-agent + + prepare-agent + + + + + + org.eluder.coveralls + coveralls-maven-plugin + 2.0.1 + + + + + + + + + + + + + + src/main/resources + true + + + + + + commons-io + commons-io + ${commons.io.version} + + + commons-configuration + commons-configuration + ${commons.configuration.version} + + + com.googlecode.json-simple + json-simple + ${json-simple.version} + + + args4j + args4j + ${args4j.version} + + + org.freemarker + freemarker + ${freemarker.version} true - + com.orientechnologies orient-commons @@ -326,41 +388,47 @@ ${orientdb.version} - - junit - junit - ${junit.version} - test - - - org.assertj - assertj-core - ${assertj.version} - test - - - - org.pegdown - pegdown - ${pegdown.version} + + junit + junit + ${junit.version} + test + + + + org.assertj + assertj-core + ${assertj.version} + test + + + + org.pegdown + pegdown + ${pegdown.version} true - - - org.asciidoctor - asciidoctorj - ${asciidoctorj.version} + + + org.asciidoctor + asciidoctorj + ${asciidoctorj.version} true - - - org.eclipse.jetty - jetty-server - ${jetty.version} + + + org.eclipse.jetty + jetty-server + ${jetty.version} true - + org.codehaus.groovy groovy @@ -372,7 +440,7 @@ groovy-templates ${groovy.version} true - + org.thymeleaf thymeleaf @@ -385,6 +453,12 @@ ${thymeleaf.extras.version} true + + de.neuland-bfi + jade4j + ${jade.version} + true + org.slf4j @@ -411,5 +485,12 @@ ${logback.version} true + + + com.mitchellbosecke + pebble + ${pebble.version} + true + diff --git a/src/main/assembly/assembly-example-project-jade.xml b/src/main/assembly/assembly-example-project-jade.xml new file mode 100644 index 000000000..71cc39a23 --- /dev/null +++ b/src/main/assembly/assembly-example-project-jade.xml @@ -0,0 +1,19 @@ + + base + false + + + zip + + + + ${project.build.directory}/example-project-jade/ + / + + .git + README.md + LICENSE + + + + \ No newline at end of file diff --git a/src/main/assembly/assembly-example-project-pebble.xml b/src/main/assembly/assembly-example-project-pebble.xml new file mode 100644 index 000000000..ddc22f88c --- /dev/null +++ b/src/main/assembly/assembly-example-project-pebble.xml @@ -0,0 +1,19 @@ + + base + false + + + zip + + + + ${project.build.directory}/example-project-pebble/ + / + + .git + README.md + LICENSE + + + + \ No newline at end of file diff --git a/src/main/java/org/jbake/app/ConfigUtil.java b/src/main/java/org/jbake/app/ConfigUtil.java index 0c864a4b2..a3f0e0d2b 100644 --- a/src/main/java/org/jbake/app/ConfigUtil.java +++ b/src/main/java/org/jbake/app/ConfigUtil.java @@ -1,13 +1,13 @@ package org.jbake.app; +import java.io.File; + import org.apache.commons.configuration.CompositeConfiguration; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.PropertiesConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; - /** * Provides Configuration related functions. * @@ -21,7 +21,6 @@ public class ConfigUtil { * */ public static interface Keys { - /** * Output filename for archive file, is only used when {@link #RENDER_ARCHIVE} is true */ @@ -52,6 +51,11 @@ public static interface Keys { */ static final String ASSET_FOLDER = "asset.folder"; + /** + * URI prefix for content that should be given extensionless output URI's + */ + static final String URI_NO_EXTENSION = "uri.noExtension"; + /** * Flag indicating if hidden asset resources should be ignored */ @@ -182,6 +186,15 @@ public static interface Keys { */ static final String VERSION = "version"; + /** + * Flag indicating if there should be pagination when rendering index + */ + static final String PAGINATE_INDEX = "index.paginate"; + + /** + * How many posts per page on index + */ + static final String POSTS_PER_PAGE = "index.posts_per_page"; } private final static Logger LOGGER = LoggerFactory.getLogger(ConfigUtil.class); diff --git a/src/main/java/org/jbake/app/ContentStore.java b/src/main/java/org/jbake/app/ContentStore.java index d7074d729..0c6273269 100644 --- a/src/main/java/org/jbake/app/ContentStore.java +++ b/src/main/java/org/jbake/app/ContentStore.java @@ -33,7 +33,10 @@ import com.orientechnologies.orient.core.sql.OCommandSQL; import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.jbake.model.DocumentTypes; @@ -42,7 +45,18 @@ * @author jdlee */ public class ContentStore { + public static final String PUBLISHED_DATE = "published_date"; + public static final String TAG_POSTS = "tag_posts"; + public static final String ALLTAGS = "alltags"; + public static final String ALL_CONTENT = "all_content"; + public static final String PUBLISHED_CONTENT = "published_content"; + public static final String PUBLISHED_PAGES = "published_pages"; + public static final String PUBLISHED_POSTS = "published_posts"; + public static final String DATABASE = "db"; + private ODatabaseDocumentTx db; + private long start = -1; + private long limit = -1; public ContentStore(final String type, String name) { db = new ODatabaseDocumentTx(type + ":" + name); @@ -57,6 +71,27 @@ public ContentStore(final String type, String name) { } } + public long getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public long getLimit() { + return limit; + } + + public void setLimit(int limit) { + this.limit = limit; + } + + public void resetPagination() { + this.start = -1; + this.limit = -1; + } + public final void updateSchema() { OSchema schema = db.getMetadata().getSchema(); for (String docType : DocumentTypes.getDocumentTypes()) { @@ -74,6 +109,7 @@ public final void updateSchema() { public void close() { db.close(); + DBUtil.closeDataStore(); } public void drop() { @@ -102,11 +138,19 @@ public List getPublishedPages() { } public List getPublishedContent(String docType) { - return query("select * from " + docType + " where status='published' order by date desc"); + String query = "select * from " + docType + " where status='published'"; + if ((start >= 0) && (limit > -1)) { + query += " SKIP " + start + " LIMIT " + limit; + } + return query(query + " order by date desc"); } public List getAllContent(String docType) { - return query("select * from " + docType + " order by date desc"); + String query = "select * from " + docType; + if ((start >= 0) && (limit > -1)) { + query += " SKIP " + start + " LIMIT " + limit; + } + return query(query + " order by date desc"); } public List getAllTagsFromPublishedPosts() { @@ -153,6 +197,16 @@ private void executeCommand(String query, Object... args) { db.command(new OCommandSQL(query)).execute(args); } + public Set getTags() { + List docs = this.getAllTagsFromPublishedPosts(); + Set result = new HashSet(); + for (ODocument document : docs) { + String[] tags = DBUtil.toStringArray(document.field("tags")); + Collections.addAll(result, tags); + } + return result; + } + private static void createDocType(final OSchema schema, final String doctype) { OClass page = schema.createClass(doctype); page.createProperty("sha1", OType.STRING).setNotNull(true); diff --git a/src/main/java/org/jbake/app/Crawler.java b/src/main/java/org/jbake/app/Crawler.java index 49cfa5f2b..d485ee5dd 100644 --- a/src/main/java/org/jbake/app/Crawler.java +++ b/src/main/java/org/jbake/app/Crawler.java @@ -1,15 +1,14 @@ package org.jbake.app; -import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; -import com.orientechnologies.orient.core.record.impl.ODocument; -import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; import org.apache.commons.configuration.CompositeConfiguration; import org.jbake.app.ConfigUtil.Keys; +import org.jbake.app.Crawler.Attributes.Status; import org.jbake.model.DocumentStatus; import org.jbake.model.DocumentTypes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static java.io.File.separator; import java.io.File; import java.util.Arrays; @@ -20,7 +19,15 @@ import java.util.Map; import java.util.Set; -import static java.io.File.separator; +import org.apache.commons.io.FilenameUtils; +import org.jbake.model.DocumentStatus; +import org.jbake.model.DocumentTypes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; /** * Crawls a file system looking for content. @@ -28,6 +35,31 @@ * @author Jonathan Bullock */ public class Crawler { + public static interface Attributes { + /** + * Possible values of the {@link Attributes#STATUS} property + * @author ndx + * + */ + public static interface Status { + + static final String PUBLISHED_DATE = "published-date"; + static final String PUBLISHED = "published"; + static final String DRAFT = "draft"; + } + static final String CACHED = "cached"; + static final String DATE = "date"; + static final String STATUS = "status"; + static final String TYPE = "type"; + static final String URI = "uri"; + static final String FILE = "file"; + static final String TAGS = "tags"; + static final String TAG = "tag"; + static final String RENDERED = "rendered"; + static final String SHA1 = "sha1"; + static final String ROOTPATH = "rootpath"; + + } private static final Logger LOGGER = LoggerFactory.getLogger(Crawler.class); private CompositeConfiguration config; @@ -105,7 +137,13 @@ private String buildHash(final File sourceFile) { private String buildURI(final File sourceFile) { String uri = FileUtil.asPath(sourceFile.getPath()).replace(FileUtil.asPath( contentPath), ""); - // strip off leading / to enable generating non-root based sites + String noExtensionUrlFolder = config.getString(Keys.URI_NO_EXTENSION); + if (!noExtensionUrlFolder.equals("false") && uri.startsWith(noExtensionUrlFolder)) { + uri = "/" + FilenameUtils.getPath(uri) + FilenameUtils.getBaseName(uri) + "/index" + config.getString(Keys.OUTPUT_EXTENSION); + } else { + uri = uri.substring(0, uri.lastIndexOf(".")) + config.getString(Keys.OUTPUT_EXTENSION); + } + // strip off leading / to enable generating non-root based sites if (uri.startsWith("/")) { uri = uri.substring(1, uri.length()); } @@ -115,29 +153,35 @@ private String buildURI(final File sourceFile) { private void crawlSourceFile(final File sourceFile, final String sha1, final String uri) { Map fileContents = parser.processFile(sourceFile); if (fileContents != null) { - fileContents.put("rootpath", getPathToRoot(sourceFile)); - fileContents.put("sha1", sha1); - fileContents.put("rendered", false); - if (fileContents.get("tags") != null) { + fileContents.put(Attributes.ROOTPATH, getPathToRoot(sourceFile)); + fileContents.put(Attributes.SHA1, sha1); + fileContents.put(Attributes.RENDERED, false); + if (fileContents.get(Attributes.TAGS) != null) { // store them as a String[] - String[] tags = (String[]) fileContents.get("tags"); - fileContents.put("tags", tags); + String[] tags = (String[]) fileContents.get(Attributes.TAGS); + fileContents.put(Attributes.TAGS, tags); } - fileContents.put("file", sourceFile.getPath()); - fileContents.put("uri", uri.substring(0, uri.lastIndexOf(".")) + FileUtil.findExtension(config, fileContents.get("type").toString())); - - String documentType = (String) fileContents.get("type"); - if (fileContents.get("status").equals("published-date")) { - if (fileContents.get("date") != null && (fileContents.get("date") instanceof Date)) { - if (new Date().after((Date) fileContents.get("date"))) { - fileContents.put("status", "published"); + + fileContents.put(Attributes.FILE, sourceFile.getPath()); + fileContents.put(Attributes.URI, uri); + + String documentType = (String) fileContents.get(Attributes.TYPE); + if (fileContents.get(Attributes.STATUS).equals(Status.PUBLISHED_DATE)) { + if (fileContents.get(Attributes.DATE) != null && (fileContents.get(Attributes.DATE) instanceof Date)) { + if (new Date().after((Date) fileContents.get(Attributes.DATE))) { + fileContents.put(Attributes.STATUS, Status.PUBLISHED); } } } + + if (!config.getString(Keys.URI_NO_EXTENSION).equals("false")) { + fileContents.put("noExtensionUri", uri.replace("/index.html", "/")); + } + ODocument doc = new ODocument(documentType); doc.fields(fileContents); - boolean cached = fileContents.get("cached") != null ? Boolean.valueOf((String)fileContents.get("cached")):true; - doc.field("cached", cached); + boolean cached = fileContents.get(Attributes.CACHED) != null ? Boolean.valueOf((String)fileContents.get(Attributes.CACHED)):true; + doc.field(Attributes.CACHED, cached); doc.save(); } else { LOGGER.warn("{} has an invalid header, it has been ignored!", sourceFile); @@ -162,12 +206,12 @@ public String getPathToRoot(File sourceFile) { public int getDocumentCount(String docType) { return (int) db.countClass(docType); } - + public Set getTags() { List query = db.getAllTagsFromPublishedPosts(); //query(new OSQLSynchQuery("select tags from post where status='published'")); Set result = new HashSet(); for (ODocument document : query) { - String[] tags = DBUtil.toStringArray(document.field("tags")); + String[] tags = DBUtil.toStringArray(document.field(Attributes.TAGS)); Collections.addAll(result, tags); } return result; @@ -177,8 +221,8 @@ private DocumentStatus findDocumentStatus(String docType, String uri, String sha List match = db.getDocumentStatus(docType, uri); if (!match.isEmpty()) { ODocument entries = match.get(0); - String oldHash = entries.field("sha1"); - if (!(oldHash.equals(sha1)) || Boolean.FALSE.equals(entries.field("rendered"))) { + String oldHash = entries.field(Attributes.SHA1); + if (!(oldHash.equals(sha1)) || Boolean.FALSE.equals(entries.field(Attributes.RENDERED))) { return DocumentStatus.UPDATED; } else { return DocumentStatus.IDENTICAL; diff --git a/src/main/java/org/jbake/app/DBUtil.java b/src/main/java/org/jbake/app/DBUtil.java index f4e1efe88..a9bd9f5b9 100644 --- a/src/main/java/org/jbake/app/DBUtil.java +++ b/src/main/java/org/jbake/app/DBUtil.java @@ -4,6 +4,7 @@ import com.orientechnologies.orient.core.record.impl.ODocument; import com.orientechnologies.orient.core.sql.OCommandSQL; import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; +import org.jbake.model.DocumentTypes; import java.util.HashMap; import java.util.Iterator; @@ -11,11 +12,17 @@ import java.util.Map; public class DBUtil { - public static ContentStore createDB(final String type, String name) { - return new ContentStore(type, name); - } + private static ContentStore contentStore; + public static ContentStore createDataStore(final String type, String name) { - return new ContentStore(type, name); + if (contentStore == null) { + contentStore = new ContentStore(type, name); + } + return contentStore; + } + + public static void closeDataStore() { + contentStore = null; } public static void updateSchema(final ContentStore db) { diff --git a/src/main/java/org/jbake/app/Oven.java b/src/main/java/org/jbake/app/Oven.java index de0cae2d9..78760cbef 100644 --- a/src/main/java/org/jbake/app/Oven.java +++ b/src/main/java/org/jbake/app/Oven.java @@ -7,6 +7,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.ServiceLoader; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -17,6 +18,8 @@ import org.apache.commons.configuration.CompositeConfiguration; import org.jbake.app.ConfigUtil.Keys; import org.jbake.model.DocumentTypes; +import org.jbake.render.RenderingTool; +import org.jbake.template.RenderingException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; @@ -150,61 +153,13 @@ public void bake() { } Renderer renderer = new Renderer(db, destination, templatesPath, config); - - for (String docType : DocumentTypes.getDocumentTypes()) { - for (ODocument document: db.getUnrenderedContent(docType)) { - try { - renderer.render(DBUtil.documentToModel(document)); - renderedCount++; - } catch (Exception e) { - errors.add(e.getMessage()); - } - } - } - - // write index file - if (config.getBoolean(Keys.RENDER_INDEX)) { - try { - renderer.renderIndex(config.getString(Keys.INDEX_FILE)); - } catch (Exception e) { - errors.add(e.getMessage()); - } - } - - // write feed file - if (config.getBoolean(Keys.RENDER_FEED)) { - try { - renderer.renderFeed(config.getString(Keys.FEED_FILE)); - } catch (Exception e) { - errors.add(e.getMessage()); - } - } - - // write sitemap file - if (config.getBoolean(Keys.RENDER_SITEMAP)) { - try { - renderer.renderSitemap(config.getString(Keys.SITEMAP_FILE)); - } catch (Exception e) { - errors.add(e.getMessage()); - } - } - - // write master archive file - if (config.getBoolean(Keys.RENDER_ARCHIVE)) { - try { - renderer.renderArchive(config.getString(Keys.ARCHIVE_FILE)); - } catch (Exception e) { - errors.add(e.getMessage()); - } - } - - // write tag files - if (config.getBoolean(Keys.RENDER_TAGS)) { - try { - renderer.renderTags(crawler.getTags(), config.getString(Keys.TAG_PATH)); - } catch (Exception e) { - errors.add(e.getMessage()); - } + + for(RenderingTool tool : ServiceLoader.load(RenderingTool.class)) { + try { + renderedCount += tool.render(renderer, db, destination, templatesPath, config); + } catch(RenderingException e) { + errors.add(e.getMessage()); + } } // mark docs as rendered diff --git a/src/main/java/org/jbake/app/Parser.java b/src/main/java/org/jbake/app/Parser.java index 981bd2e6e..5a18ed64c 100644 --- a/src/main/java/org/jbake/app/Parser.java +++ b/src/main/java/org/jbake/app/Parser.java @@ -1,28 +1,14 @@ package org.jbake.app; -import org.apache.commons.configuration.CompositeConfiguration; -import org.apache.commons.io.IOUtils; -import org.jbake.app.ConfigUtil.Keys; import org.jbake.parser.Engines; -import org.jbake.parser.MarkupEngine; -import org.jbake.parser.ParserContext; -import org.json.simple.JSONValue; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; import java.util.Map; +import org.apache.commons.configuration.Configuration; +import org.jbake.parser.ParserEngine; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Parses a File for content. * @@ -31,13 +17,13 @@ public class Parser { private final static Logger LOGGER = LoggerFactory.getLogger(Parser.class); - private CompositeConfiguration config; + private Configuration config; private String contentPath; /** * Creates a new instance of Parser. */ - public Parser(CompositeConfiguration config, String contentPath) { + public Parser(Configuration config, String contentPath) { this.config = config; this.contentPath = contentPath; } @@ -49,192 +35,12 @@ public Parser(CompositeConfiguration config, String contentPath) { * @return The contents of the file */ public Map processFile(File file) { - Map content = new HashMap(); - InputStream is = null; - List fileContents = null; - try { - is = new FileInputStream(file); - fileContents = IOUtils.readLines(is, config.getString(Keys.RENDER_ENCODING)); - } catch (IOException e) { - LOGGER.error("Error while opening file {}: {}", file, e); - - return null; - } finally { - IOUtils.closeQuietly(is); - } - - boolean hasHeader = hasHeader(fileContents); - ParserContext context = new ParserContext( - file, - fileContents, - config, - contentPath, - hasHeader, - content - ); - - MarkupEngine engine = Engines.get(FileUtil.fileExt(file)); - if (engine==null) { - LOGGER.warn("Unable to find suitable markup engine for {}",file); - return null; - } - - if (hasHeader) { - // read header from file - processHeader(fileContents, content); - } - // then read engine specific headers - engine.processHeader(context); - - if (config.getString(Keys.DEFAULT_STATUS) != null) { - // default status has been set - if (content.get("status") == null) { - // file hasn't got status so use default - content.put("status", config.getString(Keys.DEFAULT_STATUS)); - } - } - - if (content.get("type")==null||content.get("status")==null) { - // output error - LOGGER.warn("Error parsing meta data from header (missing type or status value) for file {}!", file); - return null; - } - - // generate default body - processBody(fileContents, content); - - // eventually process body using specific engine - if (engine.validate(context)) { - engine.processBody(context); - } else { - LOGGER.warn("Incomplete source file ({}) for markup engine:", file, engine.getClass().getSimpleName()); - return null; - } - - if (content.get("tags") != null) { - String[] tags = (String[]) content.get("tags"); - for( int i=0; i contents) { - boolean headerValid = false; - boolean headerSeparatorFound = false; - boolean statusFound = false; - boolean typeFound = false; - - List header = new ArrayList(); - - for (String line : contents) { - header.add(line); - if (line.contains("=")) { - if (line.startsWith("type=")) { - typeFound = true; - } - if (line.startsWith("status=")) { - statusFound = true; - } - } - if (line.equals("~~~~~~")) { - headerSeparatorFound = true; - header.remove(line); - break; - } - } - - if (headerSeparatorFound) { - headerValid = true; - for (String headerLine : header) { - if (!headerLine.contains("=")) { - headerValid = false; - break; - } - } - } - - if (!headerValid || !statusFound || !typeFound) { - return false; - } - return true; - } - - /** - * Process the header of the file. - * - * @param contents Contents of file - * @param content - */ - private void processHeader(List contents, final Map content) { - for (String line : contents) { - if (line.equals("~~~~~~")) { - break; - } else { - String[] parts = line.split("=",2); - if (parts.length == 2) { - if (parts[0].equalsIgnoreCase("date")) { - DateFormat df = new SimpleDateFormat(config.getString(Keys.DATE_FORMAT)); - Date date = null; - try { - date = df.parse(parts[1]); - content.put(parts[0], date); - } catch (ParseException e) { - e.printStackTrace(); - } - } else if (parts[0].equalsIgnoreCase("tags")) { - String[] tags = parts[1].split(","); - content.put(parts[0], tags); - } else if (parts[1].startsWith("{") && parts[1].endsWith("}")) { - // Json type - content.put(parts[0], JSONValue.parse(parts[1])); - } else { - content.put(parts[0], parts[1]); - } - } - } - } - } - - /** - * Process the body of the file. - * - * @param contents Contents of file - * @param content - */ - private void processBody(List contents, final Map content) { - StringBuilder body = new StringBuilder(); - boolean inBody = false; - for (String line : contents) { - if (inBody) { - body.append(line).append("\n"); - } - if (line.equals("~~~~~~")) { - inBody = true; - } - } - - if (body.length() == 0) { - for (String line : contents) { - body.append(line).append("\n"); - } - } - - content.put("body", body.toString()); - } - } diff --git a/src/main/java/org/jbake/app/Renderer.java b/src/main/java/org/jbake/app/Renderer.java index 04b41c267..da292edab 100644 --- a/src/main/java/org/jbake/app/Renderer.java +++ b/src/main/java/org/jbake/app/Renderer.java @@ -1,7 +1,5 @@ package org.jbake.app; -import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; - import org.apache.commons.configuration.CompositeConfiguration; import org.jbake.app.ConfigUtil.Keys; import org.jbake.template.DelegatingTemplateEngine; @@ -26,20 +24,110 @@ * @author Jonathan Bullock */ public class Renderer { + + private static interface RenderingConfig { + + File getPath(); + + String getName(); + + String getTemplate(); + + Map getModel(); + } + + private static abstract class AbstractRenderingConfig implements RenderingConfig{ + + protected final File path; + protected final String name; + protected final String template; + + public AbstractRenderingConfig(File path, String name, String template) { + super(); + this.path = path; + this.name = name; + this.template = template; + } + + @Override + public File getPath() { + return path; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getTemplate() { + return template; + } + + } + public static class ModelRenderingConfig extends AbstractRenderingConfig { + private final Map model; + + public ModelRenderingConfig(File path, String name, Map model, String template) { + super(path, name, template); + this.model = model; + } + + @Override + public Map getModel() { + return model; + } + } + + class DefaultRenderingConfig extends AbstractRenderingConfig { + + private final Object content; + + private DefaultRenderingConfig(File path, String allInOneName) { + super(path, allInOneName, findTemplateName(allInOneName)); + this.content = Collections.singletonMap("type",allInOneName); + } + + public DefaultRenderingConfig(String filename, String allInOneName) { + super(new File(destination.getPath() + File.separator + filename), allInOneName, findTemplateName(allInOneName)); + this.content = Collections.singletonMap("type",allInOneName); + } + + /** + * Constructor added due to known use of a allInOneName which is used for name, template and content + * @param path + * @param allInOneName + */ + public DefaultRenderingConfig(String allInOneName) { + this(new File(destination.getPath() + File.separator + allInOneName + config.getString(Keys.OUTPUT_EXTENSION)), + allInOneName); + } + + @Override + public Map getModel() { + Map model = new HashMap(); + model.put("renderer", renderingEngine); + model.put("content", content); + return model; + } + + } private final static Logger LOGGER = LoggerFactory.getLogger(Renderer.class); // TODO: should all content be made available to all templates via this class?? - private File destination; - private CompositeConfiguration config; + private final File destination; + private final CompositeConfiguration config; private final DelegatingTemplateEngine renderingEngine; /** * Creates a new instance of Renderer with supplied references to folders. * + * @param db The database holding the content * @param destination The destination folder * @param templatesPath The templates folder + * @param config */ public Renderer(ContentStore db, File destination, File templatesPath, CompositeConfiguration config) { this.destination = destination; @@ -48,7 +136,9 @@ public Renderer(ContentStore db, File destination, File templatesPath, Composite } private String findTemplateName(String docType) { - return config.getString("template."+docType+".file"); + String templateKey = "template."+docType+".file"; + String returned = config.getString(templateKey); + return returned; } /** @@ -58,8 +148,8 @@ private String findTemplateName(String docType) { * @throws Exception */ public void render(Map content) throws Exception { - String docType = (String) content.get("type"); - String outputFilename = destination.getPath() + File.separatorChar + (String) content.get("uri"); + String docType = (String) content.get(Crawler.Attributes.TYPE); + String outputFilename = destination.getPath() + File.separatorChar + (String) content.get(Crawler.Attributes.URI); if (outputFilename.lastIndexOf(".") > 0) { outputFilename = outputFilename.substring(0, outputFilename.lastIndexOf(".")); } @@ -75,7 +165,7 @@ public void render(Map content) throws Exception { publishedFile.delete(); } - if (content.get("status").equals("draft")) { + if (content.get(Crawler.Attributes.STATUS).equals(Crawler.Attributes.Status.DRAFT)) { outputFilename = outputFilename + config.getString(Keys.DRAFT_SUFFIX); } @@ -108,32 +198,85 @@ private Writer createWriter(File file) throws IOException { return new OutputStreamWriter(new FileOutputStream(file), config.getString(ConfigUtil.Keys.RENDER_ENCODING)); } - /** - * Render an index file using the supplied content. - * - * @param indexFile The name of the output file - * @throws Exception - */ - public void renderIndex(String indexFile) throws Exception { - File outputFile = new File(destination.getPath() + File.separator + indexFile); + private void render(RenderingConfig renderConfig) throws Exception { + File outputFile = renderConfig.getPath(); StringBuilder sb = new StringBuilder(); - sb.append("Rendering index [").append(outputFile).append("]..."); - Map model = new HashMap(); - model.put("renderer", renderingEngine); - model.put("content", buildSimpleModel("masterindex")); + sb.append("Rendering ").append(renderConfig.getName()).append(" [").append(outputFile).append("]..."); try { Writer out = createWriter(outputFile); - renderingEngine.renderDocument(model, findTemplateName("masterindex"), out); + renderingEngine.renderDocument(renderConfig.getModel(), renderConfig.getTemplate(), out); out.close(); sb.append("done!"); LOGGER.info(sb.toString()); } catch (Exception e) { sb.append("failed!"); LOGGER.error(sb.toString(), e); - throw new Exception("Failed to render index. Cause: " + e.getMessage()); + throw new Exception("Failed to render "+renderConfig.getName(), e); } } + + /** + * Render an index file using the supplied content. + * + * @param indexFile The name of the output file + * @throws Exception + */ + public void renderIndex(String indexFile, ContentStore db) throws Exception { + long totalPosts = db.countClass("post"); + boolean paginate = config.getBoolean(Keys.PAGINATE_INDEX, false); + int postsPerPage = config.getInt(Keys.POSTS_PER_PAGE, -1); + int start = 0; + + Map model = new HashMap(); + model.put("renderer", renderingEngine); + model.put("content", buildSimpleModel("masterindex")); + if (paginate) { + db.setLimit(postsPerPage); + } + + try { + int page = 1; + while (start < totalPosts) { + String fileName = indexFile; + + if (paginate) { + db.setStart(start); + int index = fileName.lastIndexOf("."); + if (page != 1) { + String previous = fileName.substring(0, index) + (page > 2 ? page-1 : "") + + fileName.substring(index); + model.put("previousFileName", previous); + } else { + model.remove("previousFileName"); + } + + // If this iteration won't consume the remaining posts, calculate + // the next file name + if ((start + postsPerPage) < totalPosts) { + model.put("nextFileName", fileName.substring(0, index) + (page+1) + + fileName.substring(index)); + } else { + model.remove("nextFileName"); + } + // Add page number to file name + fileName = fileName.substring(0, index) + (page > 1 ? page : "") + + fileName.substring(index); + } + render(new DefaultRenderingConfig(fileName, "masterindex")); + + if (paginate) { + start += postsPerPage; + page++; + } else { + break; // TODO: eww + } + } + db.resetPagination(); + } catch (Exception e) { + throw new Exception("Failed to render index. Cause: " + e.getMessage()); + } + } /** * Render an XML sitemap file using the supplied content. @@ -143,25 +286,7 @@ public void renderIndex(String indexFile) throws Exception { * @see Sitemap protocol */ public void renderSitemap(String sitemapFile) throws Exception { - File outputFile = new File(destination.getPath() + File.separator + sitemapFile); - StringBuilder sb = new StringBuilder(); - sb.append("Rendering sitemap [").append(outputFile).append("]... "); - - Map model = new HashMap(); - model.put("renderer", renderingEngine); - model.put("content", buildSimpleModel("sitemap")); - - try { - Writer out = createWriter(outputFile); - renderingEngine.renderDocument(model, findTemplateName("sitemap"), out); - sb.append("done!"); - out.close(); - LOGGER.info(sb.toString()); - } catch (Exception e) { - sb.append("failed!"); - LOGGER.error(sb.toString(), e); - throw new Exception("Failed to render sitemap. Cause: " + e.getMessage()); - } + render(new DefaultRenderingConfig(sitemapFile, "sitemap")); } /** @@ -171,24 +296,7 @@ public void renderSitemap(String sitemapFile) throws Exception { * @throws Exception */ public void renderFeed(String feedFile) throws Exception { - File outputFile = new File(destination.getPath() + File.separator + feedFile); - StringBuilder sb = new StringBuilder(); - sb.append("Rendering feed [").append(outputFile).append("]... "); - Map model = new HashMap(); - model.put("renderer", renderingEngine); - model.put("content", buildSimpleModel("feed")); - - try { - Writer out = createWriter(outputFile); - renderingEngine.renderDocument(model, findTemplateName("feed"), out); - out.close(); - sb.append("done!"); - LOGGER.info(sb.toString()); - } catch (Exception e) { - sb.append("failed!"); - LOGGER.error(sb.toString(), e); - throw new Exception("Failed to render feed. Cause: " + e.getMessage()); - } + render(new DefaultRenderingConfig(feedFile, "feed")); } /** @@ -198,24 +306,7 @@ public void renderFeed(String feedFile) throws Exception { * @throws Exception */ public void renderArchive(String archiveFile) throws Exception { - File outputFile = new File(destination.getPath() + File.separator + archiveFile); - StringBuilder sb = new StringBuilder(); - sb.append("Rendering archive [").append(outputFile).append("]... "); - Map model = new HashMap(); - model.put("renderer", renderingEngine); - model.put("content", buildSimpleModel("archive")); - - try { - Writer out = createWriter(outputFile); - renderingEngine.renderDocument(model, findTemplateName("archive"), out); - out.close(); - sb.append("done!"); - LOGGER.info(sb.toString()); - } catch (Exception e) { - sb.append("failed!"); - LOGGER.error(sb.toString(), e); - throw new Exception("Failed to render archive. Cause: " + e.getMessage()); - } + render(new DefaultRenderingConfig(archiveFile, "archive")); } /** @@ -225,30 +316,22 @@ public void renderArchive(String archiveFile) throws Exception { * @param tagPath The output path * @throws Exception */ - public void renderTags(Set tags, String tagPath) throws Exception { + public int renderTags(Set tags, String tagPath) throws Exception { + int renderedCount = 0; final List errors = new LinkedList(); for (String tag : tags) { - Map model = new HashMap(); - model.put("renderer", renderingEngine); - model.put("tag", tag); - Map map = buildSimpleModel("tag"); - map.put("rootpath", "../"); - model.put("content", map); - - File outputFile = new File(destination.getPath() + File.separator + tagPath + File.separator + tag + config.getString(Keys.OUTPUT_EXTENSION)); - StringBuilder sb = new StringBuilder(); - sb.append("Rendering tags [").append(outputFile).append("]... "); - try { - Writer out = createWriter(outputFile); - renderingEngine.renderDocument(model, findTemplateName("tag"), out); - out.close(); - sb.append("done!"); - LOGGER.info(sb.toString()); + Map model = new HashMap(); + model.put("renderer", renderingEngine); + model.put("tag", tag); + model.put("content", Collections.singletonMap("type","tag")); + + tag = tag.trim().replace(" ", "-"); + File path = new File(destination.getPath() + File.separator + tagPath + File.separator + tag + config.getString(Keys.OUTPUT_EXTENSION)); + render(new ModelRenderingConfig(path, "tag", model, findTemplateName("tag"))); + renderedCount++; } catch (Exception e) { - sb.append("failed!"); - LOGGER.error(sb.toString(), e); - errors.add(e.getMessage()); + errors.add(e.getCause().getMessage()); } } if (!errors.isEmpty()) { @@ -258,6 +341,8 @@ public void renderTags(Set tags, String tagPath) throws Exception { sb.append("\n" + error); } throw new Exception(sb.toString()); + } else { + return renderedCount; } } diff --git a/src/main/java/org/jbake/model/DocumentTypes.java b/src/main/java/org/jbake/model/DocumentTypes.java index 178019937..7e33b34e6 100644 --- a/src/main/java/org/jbake/model/DocumentTypes.java +++ b/src/main/java/org/jbake/model/DocumentTypes.java @@ -4,6 +4,8 @@ import java.util.LinkedHashSet; import java.util.Set; +import org.jbake.parser.Engines; + /** * Utility class used to determine the list of document types. Currently only supports "page", "post", "index", * "archive" and "feed". @@ -20,7 +22,14 @@ public static void addDocumentType(String docType) { DEFAULT_DOC_TYPES.add(docType); } + /** + * Notice additional document types are added automagically before returning them + * @return all supported document types + */ public static String[] getDocumentTypes() { + // TODO: is this needed? + // make sure engines are loaded before to get document types + Engines.getRecognizedExtensions(); return DEFAULT_DOC_TYPES.toArray(new String[DEFAULT_DOC_TYPES.size()]); } } diff --git a/src/main/java/org/jbake/parser/AsciidoctorEngine.java b/src/main/java/org/jbake/parser/AsciidoctorEngine.java index 407b9567a..8ff965a3f 100644 --- a/src/main/java/org/jbake/parser/AsciidoctorEngine.java +++ b/src/main/java/org/jbake/parser/AsciidoctorEngine.java @@ -18,9 +18,17 @@ import java.util.Date; import java.util.Iterator; import java.util.Map; -import java.util.Set; import java.util.concurrent.locks.ReentrantReadWriteLock; +import org.apache.commons.configuration.Configuration; +import org.asciidoctor.Asciidoctor; +import org.asciidoctor.AttributesBuilder; +import org.asciidoctor.Options; +import org.asciidoctor.ast.DocumentHeader; +import org.jbake.app.ConfigUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import static org.apache.commons.lang.BooleanUtils.toBooleanObject; import static org.apache.commons.lang.math.NumberUtils.isNumber; import static org.apache.commons.lang.math.NumberUtils.toInt; @@ -127,7 +135,7 @@ private void processAsciiDoc(ParserContext context) { } private Options getAsciiDocOptionsAndAttributes(ParserContext context) { - CompositeConfiguration config = context.getConfig(); + Configuration config = context.getConfig(); final AttributesBuilder attributes = attributes(config.getStringArray(Keys.ASCIIDOCTOR_ATTRIBUTES)); if (config.getBoolean(Keys.ASCIIDOCTOR_ATTRIBUTES_EXPORT, false)) { final String prefix = config.getString( Keys.ASCIIDOCTOR_ATTRIBUTES_EXPORT_PREFIX, ""); diff --git a/src/main/java/org/jbake/parser/Engines.java b/src/main/java/org/jbake/parser/Engines.java index 968043c32..febf77f0d 100644 --- a/src/main/java/org/jbake/parser/Engines.java +++ b/src/main/java/org/jbake/parser/Engines.java @@ -49,13 +49,13 @@ public class Engines { loadEngines(); } - private final Map parsers; + private final Map parsers; - public static MarkupEngine get(String fileExtension) { + public static ParserEngine get(String fileExtension) { return INSTANCE.getEngine(fileExtension); } - public static void register(String fileExtension, MarkupEngine engine) { + public static void register(String fileExtension, ParserEngine engine) { INSTANCE.registerEngine(fileExtension, engine); } @@ -64,17 +64,17 @@ public static Set getRecognizedExtensions() { } private Engines() { - parsers = new HashMap(); + parsers = new HashMap(); } - private void registerEngine(String fileExtension, MarkupEngine markupEngine) { - MarkupEngine old = parsers.put(fileExtension, markupEngine); + private void registerEngine(String fileExtension, ParserEngine markupEngine) { + ParserEngine old = parsers.put(fileExtension, markupEngine); if (old != null) { LOGGER.warn("Registered a markup engine for extension [.{}] but another one was already defined: {}", fileExtension, old); } } - private MarkupEngine getEngine(String fileExtension) { + private ParserEngine getEngine(String fileExtension) { return parsers.get(fileExtension); } @@ -85,10 +85,10 @@ private MarkupEngine getEngine(String fileExtension) { * @param engineClassName engine class, used both as a hint to find it and to create the engine itself. * @return null if the engine is not available, an instance of the engine otherwise */ - private static MarkupEngine tryLoadEngine(String engineClassName) { + private static ParserEngine tryLoadEngine(String engineClassName) { try { @SuppressWarnings("unchecked") - Class engineClass = (Class) Class.forName(engineClassName, false, Engines.class.getClassLoader()); + Class engineClass = (Class) Class.forName(engineClassName, false, Engines.class.getClassLoader()); return engineClass.newInstance(); } catch (ClassNotFoundException e) { return new ErrorEngine(engineClassName); @@ -126,7 +126,7 @@ private static void loadEngines() { } private static void registerEngine(String className, String... extensions) { - MarkupEngine engine = tryLoadEngine(className); + ParserEngine engine = tryLoadEngine(className); if (engine != null) { for (String extension : extensions) { register(extension, engine); diff --git a/src/main/java/org/jbake/parser/MarkupEngine.java b/src/main/java/org/jbake/parser/MarkupEngine.java index b9b76c3f1..3666be69d 100644 --- a/src/main/java/org/jbake/parser/MarkupEngine.java +++ b/src/main/java/org/jbake/parser/MarkupEngine.java @@ -1,5 +1,27 @@ package org.jbake.parser; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.io.IOUtils; +import org.jbake.app.ConfigUtil; +import org.jbake.app.ConfigUtil.Keys; +import org.jbake.app.Crawler; +import org.json.simple.JSONValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Base class for markup engine wrappers. A markup engine is responsible for rendering * markup in a source file and exporting the result into the {@link ParserContext#getContents() contents} map. @@ -8,7 +30,8 @@ * * @author Cédric Champeau */ -public abstract class MarkupEngine { +public abstract class MarkupEngine implements ParserEngine { + private static final Logger LOGGER = LoggerFactory.getLogger(MarkupEngine.class); /** * Tests if this markup engine can process the document. * @param context the parser context @@ -29,4 +52,196 @@ public void processHeader(final ParserContext context) {} * @param context the parser context */ public void processBody(final ParserContext context) {} + + /** + * Parse given file to extract as much infos as possible + * @param file file to process + * @return a map containing all infos. Returning null indicates an error, even if an exception would be better. + */ + public Map parse(Configuration config, File file, String contentPath) { + + Map content = new HashMap(); + InputStream is = null; + List fileContents = null; + try { + is = new FileInputStream(file); + fileContents = IOUtils.readLines(is, config.getString("render.encoding")); + } catch (IOException e) { + LOGGER.error("Error while opening file {}: {}", file, e); + + return null; + } finally { + IOUtils.closeQuietly(is); + } + + boolean hasHeader = hasHeader(fileContents); + ParserContext context = new ParserContext( + file, + fileContents, + config, + contentPath, + hasHeader, + content + ); + + if (hasHeader) { + // read header from file + processHeader(config, fileContents, content); + } + // then read engine specific headers + processHeader(context); + + if (config.getString(Keys.DEFAULT_STATUS) != null) { + // default status has been set + if (content.get(Crawler.Attributes.STATUS) == null) { + // file hasn't got status so use default + content.put(Crawler.Attributes.STATUS, config.getString(Keys.DEFAULT_STATUS)); + } + } + + if (content.get(Crawler.Attributes.TYPE)==null||content.get(Crawler.Attributes.STATUS)==null) { + // output error + LOGGER.warn("Error parsing meta data from header (missing type or status value) for file {}!", file); + return null; + } + + // generate default body + processBody(fileContents, content); + + // eventually process body using specific engine + if (validate(context)) { + processBody(context); + } else { + LOGGER.error("Incomplete source file ({}) for markup engine:", file, getClass().getSimpleName()); + return null; + } + + if (content.get("tags") != null) { + String[] tags = (String[]) content.get("tags"); + for( int i=0; i contents) { + boolean headerValid = false; + boolean headerSeparatorFound = false; + boolean statusFound = false; + boolean typeFound = false; + + List header = new ArrayList(); + + for (String line : contents) { + header.add(line); + if (line.contains("=")) { + if (line.startsWith("type=")) { + typeFound = true; + } + if (line.startsWith("status=")) { + statusFound = true; + } + } + if (line.equals("~~~~~~")) { + headerSeparatorFound = true; + header.remove(line); + break; + } + } + + if (headerSeparatorFound) { + headerValid = true; + for (String headerLine : header) { + if (!headerLine.contains("=")) { + headerValid = false; + break; + } + } + } + + if (!headerValid || !statusFound || !typeFound) { + return false; + } + return true; + } + + /** + * Process the header of the file. + * @param config + * + * @param contents Contents of file + * @param content + */ + private void processHeader(Configuration config, List contents, final Map content) { + for (String line : contents) { + if (line.equals("~~~~~~")) { + break; + } else { + String[] parts = line.split("=",2); + if (parts.length == 2) { + if (parts[0].equalsIgnoreCase("date")) { + DateFormat df = new SimpleDateFormat(config.getString(Keys.DATE_FORMAT)); + Date date = null; + try { + date = df.parse(parts[1]); + content.put(parts[0], date); + } catch (ParseException e) { + e.printStackTrace(); + } + } else if (parts[0].equalsIgnoreCase("tags")) { + String[] tags = parts[1].split(","); + for( int i=0; i contents, final Map content) { + StringBuilder body = new StringBuilder(); + boolean inBody = false; + for (String line : contents) { + if (inBody) { + body.append(line).append("\n"); + } + if (line.equals("~~~~~~")) { + inBody = true; + } + } + + if (body.length() == 0) { + for (String line : contents) { + body.append(line).append("\n"); + } + } + + content.put("body", body.toString()); + } } diff --git a/src/main/java/org/jbake/parser/ParserContext.java b/src/main/java/org/jbake/parser/ParserContext.java index ef4a99657..84368d15c 100644 --- a/src/main/java/org/jbake/parser/ParserContext.java +++ b/src/main/java/org/jbake/parser/ParserContext.java @@ -1,15 +1,15 @@ package org.jbake.parser; -import org.apache.commons.configuration.CompositeConfiguration; - import java.io.File; import java.util.List; import java.util.Map; +import org.apache.commons.configuration.Configuration; + public class ParserContext { private final File file; private final List fileLines; - private final CompositeConfiguration config; + private final Configuration config; private final String contentPath; private final boolean hasHeader; private final Map contents; @@ -17,7 +17,7 @@ public class ParserContext { public ParserContext( File file, List fileLines, - CompositeConfiguration config, + Configuration config, String contentPath, boolean hasHeader, Map contents) { @@ -37,7 +37,7 @@ public List getFileLines() { return fileLines; } - public CompositeConfiguration getConfig() { + public Configuration getConfig() { return config; } diff --git a/src/main/java/org/jbake/parser/ParserEngine.java b/src/main/java/org/jbake/parser/ParserEngine.java new file mode 100644 index 000000000..e5f500f9b --- /dev/null +++ b/src/main/java/org/jbake/parser/ParserEngine.java @@ -0,0 +1,12 @@ +package org.jbake.parser; + +import java.io.File; +import java.util.Map; + +import org.apache.commons.configuration.Configuration; + +public interface ParserEngine { + + Map parse(Configuration config, File file, String contentPath); + +} diff --git a/src/main/java/org/jbake/render/ArchiveRenderer.java b/src/main/java/org/jbake/render/ArchiveRenderer.java new file mode 100644 index 000000000..aaec78546 --- /dev/null +++ b/src/main/java/org/jbake/render/ArchiveRenderer.java @@ -0,0 +1,29 @@ +package org.jbake.render; + +import java.io.File; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.jbake.app.ConfigUtil.Keys; +import org.jbake.app.ContentStore; +import org.jbake.app.Renderer; +import org.jbake.template.RenderingException; + +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; + +public class ArchiveRenderer implements RenderingTool { + + @Override + public int render(Renderer renderer, ContentStore db, File destination, File templatesPath, CompositeConfiguration config) throws RenderingException { + if (config.getBoolean(Keys.RENDER_ARCHIVE)) { + try { + renderer.renderArchive(config.getString(Keys.ARCHIVE_FILE)); + return 1; + } catch (Exception e) { + throw new RenderingException(e); + } + } else { + return 0; + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/render/DocumentsRenderer.java b/src/main/java/org/jbake/render/DocumentsRenderer.java new file mode 100644 index 000000000..9a81a29cc --- /dev/null +++ b/src/main/java/org/jbake/render/DocumentsRenderer.java @@ -0,0 +1,44 @@ +package org.jbake.render; + +import java.io.File; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.jbake.app.ContentStore; +import org.jbake.app.DocumentIterator; +import org.jbake.app.Renderer; +import org.jbake.model.DocumentTypes; +import org.jbake.template.RenderingException; + +public class DocumentsRenderer implements RenderingTool { + + @Override + public int render(Renderer renderer, ContentStore db, File destination, File templatesPath, CompositeConfiguration config) throws RenderingException { + int renderedCount = 0; + final List errors = new LinkedList(); + for (String docType : DocumentTypes.getDocumentTypes()) { + DocumentIterator pagesIt = new DocumentIterator(db.getUnrenderedContent(docType).iterator()); + while (pagesIt.hasNext()) { + Map page = pagesIt.next(); + try { + renderer.render(page); + renderedCount++; + } catch (Exception e) { + errors.add(e.getMessage()); + } + } + } + if (!errors.isEmpty()) { + StringBuilder sb = new StringBuilder(); + sb.append("Failed to render documents. Cause(s):"); + for(String error: errors) { + sb.append("\n" + error); + } + throw new RenderingException(sb.toString()); + } else { + return renderedCount; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/jbake/render/FeedRenderer.java b/src/main/java/org/jbake/render/FeedRenderer.java new file mode 100644 index 000000000..9dc4a37cf --- /dev/null +++ b/src/main/java/org/jbake/render/FeedRenderer.java @@ -0,0 +1,29 @@ +package org.jbake.render; + +import java.io.File; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.jbake.app.ConfigUtil.Keys; +import org.jbake.app.ContentStore; +import org.jbake.app.Renderer; +import org.jbake.template.RenderingException; + +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; + +public class FeedRenderer implements RenderingTool { + + @Override + public int render(Renderer renderer, ContentStore db, File destination, File templatesPath, CompositeConfiguration config) throws RenderingException { + if (config.getBoolean(Keys.RENDER_FEED)) { + try { + renderer.renderFeed(config.getString(Keys.FEED_FILE)); + return 1; + } catch (Exception e) { + throw new RenderingException(e); + } + } else { + return 0; + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/render/IndexRenderer.java b/src/main/java/org/jbake/render/IndexRenderer.java new file mode 100644 index 000000000..f6dec2929 --- /dev/null +++ b/src/main/java/org/jbake/render/IndexRenderer.java @@ -0,0 +1,29 @@ +package org.jbake.render; + +import java.io.File; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.jbake.app.ConfigUtil.Keys; +import org.jbake.app.ContentStore; +import org.jbake.app.Renderer; +import org.jbake.template.RenderingException; + +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; + +public class IndexRenderer implements RenderingTool { + + @Override + public int render(Renderer renderer, ContentStore db, File destination, File templatesPath, CompositeConfiguration config) throws RenderingException { + if (config.getBoolean(Keys.RENDER_INDEX)) { + try { + renderer.renderIndex(config.getString(Keys.INDEX_FILE), db); + return 1; + } catch (Exception e) { + throw new RenderingException(e); + } + } else { + return 0; + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/render/RenderingTool.java b/src/main/java/org/jbake/render/RenderingTool.java new file mode 100644 index 000000000..4db43b10f --- /dev/null +++ b/src/main/java/org/jbake/render/RenderingTool.java @@ -0,0 +1,14 @@ +package org.jbake.render; + +import java.io.File; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.jbake.app.ContentStore; +import org.jbake.app.Renderer; +import org.jbake.template.RenderingException; + +public interface RenderingTool { + + int render(Renderer renderer, ContentStore db, File destination, File templatesPath, CompositeConfiguration config) throws RenderingException; + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/render/SitemapRenderer.java b/src/main/java/org/jbake/render/SitemapRenderer.java new file mode 100644 index 000000000..c70d2c46d --- /dev/null +++ b/src/main/java/org/jbake/render/SitemapRenderer.java @@ -0,0 +1,29 @@ +package org.jbake.render; + +import java.io.File; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.jbake.app.ConfigUtil.Keys; +import org.jbake.app.ContentStore; +import org.jbake.app.Renderer; +import org.jbake.template.RenderingException; + +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; + +public class SitemapRenderer implements RenderingTool { + + @Override + public int render(Renderer renderer, ContentStore db, File destination, File templatesPath, CompositeConfiguration config) throws RenderingException { + if (config.getBoolean(Keys.RENDER_SITEMAP)) { + try { + renderer.renderSitemap(config.getString(Keys.SITEMAP_FILE)); + return 1; + } catch (Exception e) { + throw new RenderingException(e); + } + } else { + return 0; + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/render/TagsRenderer.java b/src/main/java/org/jbake/render/TagsRenderer.java new file mode 100644 index 000000000..19caee53e --- /dev/null +++ b/src/main/java/org/jbake/render/TagsRenderer.java @@ -0,0 +1,30 @@ +package org.jbake.render; + +import java.io.File; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.jbake.app.ConfigUtil.Keys; +import org.jbake.app.ContentStore; +import org.jbake.app.Crawler; +import org.jbake.app.Renderer; +import org.jbake.template.RenderingException; + +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; + +public class TagsRenderer implements RenderingTool { + + @Override + public int render(Renderer renderer, ContentStore db, File destination, File templatesPath, CompositeConfiguration config) throws RenderingException { + if (config.getBoolean(Keys.RENDER_TAGS)) { + try { +// return renderer.renderTags(Crawler.getTags(db), config.getString("tag.path")); + return renderer.renderTags(db.getTags(), config.getString(Keys.TAG_PATH)); + } catch (Exception e) { + throw new RenderingException(e); + } + } else { + return 0; + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/AbstractTemplateEngine.java b/src/main/java/org/jbake/template/AbstractTemplateEngine.java index c5f16610e..f04cc6aba 100644 --- a/src/main/java/org/jbake/template/AbstractTemplateEngine.java +++ b/src/main/java/org/jbake/template/AbstractTemplateEngine.java @@ -1,13 +1,14 @@ package org.jbake.template; -import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; -import org.apache.commons.configuration.CompositeConfiguration; - import java.io.File; import java.io.Writer; import java.util.Map; import org.jbake.app.ContentStore; +import org.apache.commons.configuration.Configuration; + +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; + /** * A template is responsible for converting a model into a rendered document. The model * consists of key/value pairs, some of them potentially converted from a markup language @@ -26,12 +27,12 @@ */ public abstract class AbstractTemplateEngine { - protected final CompositeConfiguration config; + protected final Configuration config; protected final ContentStore db; protected final File destination; protected final File templatesPath; - protected AbstractTemplateEngine(final CompositeConfiguration config, final ContentStore db, final File destination, final File templatesPath) { + protected AbstractTemplateEngine(final Configuration config, final ContentStore db, final File destination, final File templatesPath) { this.config = config; this.db = db; this.destination = destination; diff --git a/src/main/java/org/jbake/template/FreemarkerTemplateEngine.java b/src/main/java/org/jbake/template/FreemarkerTemplateEngine.java index 8cf1b1a38..342bda1f3 100644 --- a/src/main/java/org/jbake/template/FreemarkerTemplateEngine.java +++ b/src/main/java/org/jbake/template/FreemarkerTemplateEngine.java @@ -1,7 +1,6 @@ package org.jbake.template; import com.orientechnologies.orient.core.record.impl.ODocument; -import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; import freemarker.template.Configuration; import freemarker.template.DefaultObjectWrapper; @@ -26,12 +25,16 @@ import java.io.IOException; import java.io.Writer; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + import org.jbake.app.ContentStore; /** @@ -41,6 +44,7 @@ */ public class FreemarkerTemplateEngine extends AbstractTemplateEngine { + private static ModelExtractors extractors = new ModelExtractors(); private Configuration templateCfg; public FreemarkerTemplateEngine(final CompositeConfiguration config, final ContentStore db, final File destination, final File templatesPath) { @@ -85,57 +89,25 @@ public LazyLoadingModel(final Map eagerModel, final ContentStore @Override public TemplateModel get(final String key) throws TemplateModelException { - if ("published_posts".equals(key)) { - List query = db.getPublishedPosts(); - return new SimpleSequence(DocumentList.wrap(query.iterator())); - } - if ("published_pages".equals(key)) { - List query = db.getPublishedPages(); - return new SimpleSequence(DocumentList.wrap(query.iterator())); - } - if ("published_content".equals(key)) { - List publishedContent = new ArrayList(); - String[] documentTypes = DocumentTypes.getDocumentTypes(); - for (String docType : documentTypes) { - List query = db.getPublishedContent(docType); - publishedContent.addAll(query); - } - return new SimpleSequence(DocumentList.wrap(publishedContent.iterator())); - } - if ("all_content".equals(key)) { - List allContent = new ArrayList(); - String[] documentTypes = DocumentTypes.getDocumentTypes(); - for (String docType : documentTypes) { - List query = db.getAllContent(docType); - allContent.addAll(query); - } - return new SimpleSequence(DocumentList.wrap(allContent.iterator())); - } - if ("alltags".equals(key)) { - List query = db.getAllTagsFromPublishedPosts(); - Set result = new HashSet(); - for (ODocument document : query) { - String[] tags = DBUtil.toStringArray(document.field("tags")); - Collections.addAll(result, tags); - } - return new SimpleCollection(result); - } - String[] documentTypes = DocumentTypes.getDocumentTypes(); - for (String docType : documentTypes) { - if ((docType+"s").equals(key)) { - return new SimpleSequence(DocumentList.wrap(db.getAllContent(docType).iterator())); - } - } - if ("tag_posts".equals(key)) { - String tag = eagerModel.get("tag").toString(); - // fetch the tag posts from db - List query = db.getPublishedPostsByTag(tag); - return new SimpleSequence(DocumentList.wrap(query.iterator())); - } - if ("published_date".equals(key)) { - return new SimpleDate(new Date(), TemplateDateModel.UNKNOWN); - } - return eagerModel.get(key); + try { + return extractors.extractAndTransform(db, key, eagerModel.toMap(), new TemplateEngineAdapter() { + + @Override + public TemplateModel adapt(String key, Object extractedValue) { + if(key.equals(ContentStore.ALLTAGS)) { + return new SimpleCollection((Collection) extractedValue); + } else if(key.equals(ContentStore.PUBLISHED_DATE)) { + return new SimpleDate((Date) extractedValue, TemplateDateModel.UNKNOWN); + } else { + // All other cases, as far as I know, are document collections + return new SimpleSequence((Collection) extractedValue); + } + + } + }); + } catch(NoModelExtractorException e) { + return eagerModel.get(key); + } } @Override diff --git a/src/main/java/org/jbake/template/GroovyMarkupTemplateEngine.java b/src/main/java/org/jbake/template/GroovyMarkupTemplateEngine.java new file mode 100644 index 000000000..db07e5841 --- /dev/null +++ b/src/main/java/org/jbake/template/GroovyMarkupTemplateEngine.java @@ -0,0 +1,86 @@ +package org.jbake.template; + +import com.orientechnologies.orient.core.record.impl.ODocument; + +import groovy.lang.GString; +import groovy.lang.Writable; +import groovy.text.Template; +import groovy.text.TemplateEngine; +import groovy.text.XmlTemplateEngine; +import groovy.text.markup.MarkupTemplateEngine; +import groovy.text.markup.TemplateConfiguration; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.codehaus.groovy.runtime.MethodClosure; +import org.jbake.app.ConfigUtil.Keys; +import org.jbake.app.ContentStore; +import org.jbake.app.DBUtil; +import org.jbake.app.DocumentList; +import org.jbake.model.DocumentTypes; +import org.xml.sax.SAXException; + +import javax.xml.parsers.ParserConfigurationException; + +import java.io.*; +import java.util.*; + +/** + * Renders documents using the GroovyMarkupTemplateEngine. + * + * @see Groovy MarkupTemplateEngine Documentation + * + * The file extension to activate this Engine is .tpl + */ +public class GroovyMarkupTemplateEngine extends AbstractTemplateEngine { + private static ModelExtractors extractors = new ModelExtractors(); + private TemplateConfiguration templateConfiguration; + private MarkupTemplateEngine templateEngine; + + public GroovyMarkupTemplateEngine(final CompositeConfiguration config, final ContentStore db, final File destination, final File templatesPath) { + super(config, db, destination, templatesPath); + setupTemplateConfiguration(); + initializeTemplateEngine(); + } + + private void setupTemplateConfiguration() { + templateConfiguration = new TemplateConfiguration(); + templateConfiguration.setUseDoubleQuotes(true); + templateConfiguration.setAutoIndent(true); + templateConfiguration.setAutoNewLine(true); + templateConfiguration.setAutoEscape(true); + } + + private void initializeTemplateEngine() { + templateEngine = new MarkupTemplateEngine(MarkupTemplateEngine.class.getClassLoader(),templatesPath,templateConfiguration); + } + + @Override + public void renderDocument(final Map model, final String templateName, final Writer writer) throws RenderingException { + try { + Template template = templateEngine.createTemplateByPath(templateName); + Map wrappedModel = wrap(model); + Writable writable = template.make(wrappedModel); + writable.writeTo(writer); + } catch (Exception e) { + throw new RenderingException(e); + } + } + + private Map wrap(final Map model) { + return new HashMap(model) { + @Override + public Object get(final Object property) { + if (property instanceof String || property instanceof GString) { + String key = property.toString(); + try { + put(key, extractors.extractAndTransform(db, key, model, new TemplateEngineAdapter.NoopAdapter())); + } catch (NoModelExtractorException e) { + // should never happen, as we iterate over existing extractors + } + } + + return super.get(property); + } + }; + } +} diff --git a/src/main/java/org/jbake/template/GroovyTemplateEngine.java b/src/main/java/org/jbake/template/GroovyTemplateEngine.java index 0217f37a3..1796a1eae 100644 --- a/src/main/java/org/jbake/template/GroovyTemplateEngine.java +++ b/src/main/java/org/jbake/template/GroovyTemplateEngine.java @@ -1,8 +1,6 @@ package org.jbake.template; -import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; import com.orientechnologies.orient.core.record.impl.ODocument; -import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; import groovy.lang.GString; import groovy.lang.Writable; @@ -22,13 +20,10 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.BufferedInputStream; -import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; -import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Collections; @@ -50,6 +45,7 @@ public class GroovyTemplateEngine extends AbstractTemplateEngine { private final Map cachedTemplates = new HashMap(); + private static ModelExtractors extractors = new ModelExtractors(); public GroovyTemplateEngine(final CompositeConfiguration config, final ContentStore db, final File destination, final File templatesPath) { super(config, db, destination, templatesPath); @@ -78,7 +74,7 @@ private Template findTemplate(final String templateName) throws SAXException, Pa } private Map wrap(final Map model) { - return new HashMap(model) { + return new HashMap(model) { @Override public Object get(final Object property) { if (property instanceof String || property instanceof GString) { @@ -86,59 +82,11 @@ public Object get(final Object property) { if ("include".equals(key)) { return new MethodClosure(GroovyTemplateEngine.this, "doInclude").curry(this); } - if ("db".equals(key)) { - return db; - } - if ("published_posts".equals(key)) { - List query = db.getPublishedPosts(); //query(new OSQLSynchQuery("select * from post where status='published' order by date desc")); - return DocumentList.wrap(query.iterator()); - } - if ("published_pages".equals(key)) { - List query = db.getPublishedPages(); //query(new OSQLSynchQuery("select * from page where status='published' order by date desc")); - return DocumentList.wrap(query.iterator()); - } - if ("published_content".equals(key)) { - List publishedContent = new ArrayList(); - String[] documentTypes = DocumentTypes.getDocumentTypes(); - for (String docType : documentTypes) { - List query = db.getPublishedContent(docType); - publishedContent.addAll(query); - } - return DocumentList.wrap(publishedContent.iterator()); - } - if ("all_content".equals(key)) { - List allContent = new ArrayList(); - String[] documentTypes = DocumentTypes.getDocumentTypes(); - for (String docType : documentTypes) { - List query = db.getAllContent(docType); - allContent.addAll(query); - } - return DocumentList.wrap(allContent.iterator()); - } - if ("alltags".equals(key)) { - List query = db.getAllTagsFromPublishedPosts(); - Set result = new HashSet(); - for (ODocument document : query) { - String[] tags = DBUtil.toStringArray(document.field("tags")); - Collections.addAll(result, tags); - } - return result; - } - String[] documentTypes = DocumentTypes.getDocumentTypes(); - for (String docType : documentTypes) { - if ((docType+"s").equals(key)) { - return DocumentList.wrap(db.getAllContent(docType).iterator()); - } - } - if ("tag_posts".equals(key)) { - String tag = model.get("tag").toString(); - // fetch the tag posts from db - List query = db.getPublishedPostsByTag(tag); - return DocumentList.wrap(query.iterator()); - } - if ("published_date".equals(key)) { - return new Date(); - } + try { + return extractors.extractAndTransform(db, key, model, new TemplateEngineAdapter.NoopAdapter()); + } catch(NoModelExtractorException e) { + // fallback to parent model + } } return super.get(property); diff --git a/src/main/java/org/jbake/template/JadeTemplateEngine.java b/src/main/java/org/jbake/template/JadeTemplateEngine.java new file mode 100644 index 000000000..d644612da --- /dev/null +++ b/src/main/java/org/jbake/template/JadeTemplateEngine.java @@ -0,0 +1,116 @@ +package org.jbake.template; + +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; + +import de.neuland.jade4j.Jade4J; +import de.neuland.jade4j.JadeConfiguration; +import de.neuland.jade4j.exceptions.JadeCompilerException; +import de.neuland.jade4j.filter.CDATAFilter; +import de.neuland.jade4j.filter.CssFilter; +import de.neuland.jade4j.filter.Filter; +import de.neuland.jade4j.filter.JsFilter; +import de.neuland.jade4j.model.JadeModel; +import de.neuland.jade4j.template.FileTemplateLoader; +import de.neuland.jade4j.template.JadeTemplate; +import de.neuland.jade4j.template.TemplateLoader; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.lang.StringEscapeUtils; +import org.jbake.app.ContentStore; +import org.jbake.app.DBUtil; +import org.jbake.app.DocumentList; +import org.jbake.app.ConfigUtil.Keys; +import org.jbake.model.DocumentTypes; + +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * Renders pages using the Jade template language. + * + * @author Aleksandar Vidakovic + * @author Mariusz Smykuła + */ +public class JadeTemplateEngine extends AbstractTemplateEngine { + private static final String FILTER_CDATA = "cdata"; + private static final String FILTER_STYLE = "css"; + private static final String FILTER_SCRIPT = "js"; + + private static ModelExtractors extractors = new ModelExtractors(); + private JadeConfiguration jadeConfiguration = new JadeConfiguration(); + + public JadeTemplateEngine(final CompositeConfiguration config, final ContentStore db, final File destination, final File templatesPath) { + super(config, db, destination, templatesPath); + + TemplateLoader loader = new FileTemplateLoader(templatesPath.getAbsolutePath() + File.separatorChar, config.getString(Keys.TEMPLATE_ENCODING)); + jadeConfiguration.setTemplateLoader(loader); + jadeConfiguration.setMode(Jade4J.Mode.XHTML); + jadeConfiguration.setPrettyPrint(true); + jadeConfiguration.setFilter(FILTER_CDATA, new CDATAFilter()); + jadeConfiguration.setFilter(FILTER_SCRIPT, new JsFilter()); + jadeConfiguration.setFilter(FILTER_STYLE, new CssFilter()); + jadeConfiguration.getSharedVariables().put("formatter", new FormatHelper()); + } + + @Override + public void renderDocument(Map model, String templateName, Writer writer) throws RenderingException { + try { + JadeTemplate template = jadeConfiguration.getTemplate(templateName); + + renderTemplate(template, model, writer); + } catch (IOException e) { + throw new RenderingException(e); + } + } + + public void renderTemplate(JadeTemplate template, Map model, Writer writer) throws JadeCompilerException { + JadeModel jadeModel = wrap(jadeConfiguration.getSharedVariables()); + jadeModel.putAll(model); + template.process(jadeModel, writer); + } + + private JadeModel wrap(final Map model) { + return new JadeModel(model) { + + @Override + public Object get(final Object property) { + String key = property.toString(); + try { + return extractors.extractAndTransform(db, key, model, new TemplateEngineAdapter.NoopAdapter()); + } catch(NoModelExtractorException e) { + // fallback to parent model + } + + return super.get(property); + } + }; + } + + public static class FormatHelper { + private Map formatters = new HashMap(); + + public String format(Date date, String pattern) { + if(date!=null && pattern!=null) { + SimpleDateFormat df = formatters.get(pattern); + + if(df==null) { + df = new SimpleDateFormat(pattern); + formatters.put(pattern, df); + } + + return df.format(date); + } else { + return ""; + } + } + + public String escape(String s) { + return StringEscapeUtils.escapeHtml(s); + } + } +} diff --git a/src/main/java/org/jbake/template/ModelExtractor.java b/src/main/java/org/jbake/template/ModelExtractor.java new file mode 100644 index 000000000..77168ea32 --- /dev/null +++ b/src/main/java/org/jbake/template/ModelExtractor.java @@ -0,0 +1,18 @@ +package org.jbake.template; + +import java.util.Map; + +import org.jbake.app.ContentStore; + + +/** + * + * @author ndx + * + * @param Type the type of data returned by this model extractor + */ +public interface ModelExtractor { + + Type get(ContentStore db, Map model, String key); + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/ModelExtractors.java b/src/main/java/org/jbake/template/ModelExtractors.java new file mode 100644 index 000000000..5fc106736 --- /dev/null +++ b/src/main/java/org/jbake/template/ModelExtractors.java @@ -0,0 +1,154 @@ +package org.jbake.template; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeMap; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.jbake.app.ContentStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; + +import freemarker.template.TemplateModel; + +/** + *

A singleton class giving access to model extractors. Model extractors are loaded based on classpath. New + * rendering may be registered either at runtime (not recommanded) or by putting a descriptor file on classpath + * (recommanded).

The descriptor file must be found in META-INF directory and named + * org.jbake.template.ModelExtractors.properties. The format of the file is easy:

+ * org.jbake.template.model.AllPosts=all_posts
org.jbake.template.model.AllContent=all_content

where the key + * is the class of the extractor (must implement {@link ModelExtractor} and the value is the key by which values + * are to be accessed in model.

+ *

+ * This class loads the engines only if they are found on classpath. If not, the engine is not registered. This allows + * JBake to support multiple rendering engines without the explicit need to have them on classpath. This is a better fit + * for embedding. + * + * @author ndx + * @author Cédric Champeau + */ +public class ModelExtractors { + private static final String PROPERTIES = "META-INF/org.jbake.template.ModelExtractors.properties"; + + private final static Logger LOGGER = LoggerFactory.getLogger(ModelExtractors.class); + + private final Map extractors; + + public Set getRecognizedExtensions() { + return Collections.unmodifiableSet(extractors.keySet()); + } + + public ModelExtractors() { + extractors = new TreeMap(); + loadEngines(); + } + + private void registerEngine(String key, ModelExtractor extractor) { + ModelExtractor old = extractors.put(key, extractor); + if (old != null) { + LOGGER.warn("Registered a model extractor for key [.{}] but another one was already defined: {}", key, old); + } + } + + public ModelExtractor getEngine(String fileExtension) { + return extractors.get(fileExtension); + } + + /** + * This method is used to search for a specific class, telling if loading the engine would succeed. This is + * typically used to avoid loading optional modules. + * + * + * @param config the configuration + * @param db database instance + * @param destination target directory + * @param templatesPath path to template directory + * @param engineClassName engine class, used both as a hint to find it and to create the engine itself. @return null if the engine is not available, an instance of the engine otherwise + */ + private static ModelExtractor tryLoadEngine(String engineClassName) { + try { + @SuppressWarnings("unchecked") + Class engineClass = (Class) Class.forName(engineClassName, false, ModelExtractors.class.getClassLoader()); + return engineClass.newInstance(); + } catch (ClassNotFoundException e) { + return null; + } catch (InstantiationException e) { + return null; + } catch (IllegalAccessException e) { + return null; + } catch (NoClassDefFoundError e) { + // a dependency of the engine may not be found on classpath + return null; + } + } + + /** + * This method is used internally to load markup engines. Markup engines are found using descriptor files on + * classpath, so adding an engine is as easy as adding a jar on classpath with the descriptor file included. + */ + private void loadEngines() { + try { + ClassLoader cl = ModelExtractors.class.getClassLoader(); + Enumeration resources = cl.getResources(PROPERTIES); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + Properties props = new Properties(); + props.load(url.openStream()); + for (Map.Entry entry : props.entrySet()) { + String className = (String) entry.getKey(); + String[] extensions = ((String) entry.getValue()).split(","); + loadAndRegisterEngine(className, extensions); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void loadAndRegisterEngine(String className, String...extensions) { + ModelExtractor engine = tryLoadEngine(className); + if (engine != null) { + for (String extension : extensions) { + registerEngine(extension, engine); + } + } + } + + public Type extractAndTransform(ContentStore db, String key, Map map, TemplateEngineAdapter adapter) throws NoModelExtractorException{ + if(extractors.containsKey(key)) { + Object extractedValue = extractors.get(key).get(db, map, key); + return adapter.adapt(key, extractedValue); + } else { + throw new NoModelExtractorException("no model extractor for key \""+key+"\""); + } + } + + /** + * @param key + * @return + * @see java.util.Map#containsKey(java.lang.Object) + * @category delegate + */ + public boolean containsKey(Object key) { + return extractors.containsKey(key); + } + + /** + * @return + * @see java.util.Map#keySet() + * @category delegate + */ + public Set keySet() { + return extractors.keySet(); + } +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/NoModelExtractorException.java b/src/main/java/org/jbake/template/NoModelExtractorException.java new file mode 100644 index 000000000..73fb627b2 --- /dev/null +++ b/src/main/java/org/jbake/template/NoModelExtractorException.java @@ -0,0 +1,17 @@ +package org.jbake.template; + +public class NoModelExtractorException extends RenderingException { + + public NoModelExtractorException(String message) { + super(message); + } + + public NoModelExtractorException(Throwable cause) { + super(cause); + } + + public NoModelExtractorException(String message, Throwable cause) { + super(message, cause); + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/PebbleTemplateEngine.java b/src/main/java/org/jbake/template/PebbleTemplateEngine.java new file mode 100644 index 000000000..346d72221 --- /dev/null +++ b/src/main/java/org/jbake/template/PebbleTemplateEngine.java @@ -0,0 +1,127 @@ +package org.jbake.template; + +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.jbake.app.ContentStore; +import org.jbake.app.DBUtil; +import org.jbake.app.DocumentList; +import org.jbake.model.DocumentTypes; + +import com.mitchellbosecke.pebble.PebbleEngine; +import com.mitchellbosecke.pebble.error.PebbleException; +import com.mitchellbosecke.pebble.extension.escaper.EscaperExtension; +import com.mitchellbosecke.pebble.loader.FileLoader; +import com.mitchellbosecke.pebble.loader.Loader; +import com.mitchellbosecke.pebble.template.PebbleTemplate; +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +import com.orientechnologies.orient.core.record.impl.ODocument; +import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; + +/** + * Renders pages using the Pebble template engine. + * + * @author Mitchell Bosecke + */ +public class PebbleTemplateEngine extends AbstractTemplateEngine { + + private PebbleEngine engine; + private static ModelExtractors extractors = new ModelExtractors(); + + public PebbleTemplateEngine(final CompositeConfiguration config, final ContentStore db, + final File destination, final File templatesPath) { + super(config, db, destination, templatesPath); + initializeTemplateEngine(config, templatesPath); + } + + private void initializeTemplateEngine(final CompositeConfiguration config, final File templatesPath) { + Loader loader = new FileLoader(); + loader.setPrefix(templatesPath.getAbsolutePath()); + engine = new PebbleEngine(loader); + + /* + * Turn off the autoescaper because I believe that we can assume all + * data is safe considering it is all statically generated. + */ + EscaperExtension escaper = engine.getExtension(EscaperExtension.class); + escaper.setAutoEscaping(false); + } + + @Override + public void renderDocument(final Map model, final String templateName, final Writer writer) + throws RenderingException { + + PebbleTemplate template; + try { + template = engine.getTemplate(templateName); + template.evaluate(writer, wrap(model)); + } catch (PebbleException e) { + throw new RenderingException(e); + } catch (IOException e) { + throw new RenderingException(e); + } + + } + + private Map wrap(final Map model) { + Map result = new HashMap(model) { + + private static final long serialVersionUID = -5489285491728950547L; + + @Override + public Object get(final Object property) { + if (property instanceof String) { + String key = property.toString(); + try { + return extractors.extractAndTransform(db, key, model, new TemplateEngineAdapter.NoopAdapter()); + } catch(NoModelExtractorException e) { + // fallback to parent model + } + } + + return super.get(property); + } + + @Override + public boolean containsKey(Object property) { + if (property instanceof String) { + String key = property.toString(); + List lazyKeys = new ArrayList(); + lazyKeys.add(ContentStore.DATABASE); + lazyKeys.add(ContentStore.PUBLISHED_POSTS); + lazyKeys.add(ContentStore.PUBLISHED_PAGES); + lazyKeys.add(ContentStore.PUBLISHED_CONTENT); + lazyKeys.add(ContentStore.ALL_CONTENT); + lazyKeys.add(ContentStore.ALLTAGS); + lazyKeys.add(ContentStore.TAG_POSTS); + lazyKeys.add(ContentStore.PUBLISHED_DATE); + + String[] documentTypes = DocumentTypes.getDocumentTypes(); + for (String docType : documentTypes) { + lazyKeys.add(docType + "s"); + } + + if (lazyKeys.contains(key)) { + return true; + } + } + + return super.containsKey(property); + } + }; + + return result; + } + +} diff --git a/src/main/java/org/jbake/template/TemplateEngineAdapter.java b/src/main/java/org/jbake/template/TemplateEngineAdapter.java new file mode 100644 index 000000000..598b06c81 --- /dev/null +++ b/src/main/java/org/jbake/template/TemplateEngineAdapter.java @@ -0,0 +1,27 @@ +package org.jbake.template; + +/** + * Adapts model extractor output to used template engine. + * This method typiocally wraps results of model extractions into data types suited to template engine + * @author ndx + * + */ +public interface TemplateEngineAdapter { + public static class NoopAdapter implements TemplateEngineAdapter { + + @Override + public Object adapt(String key, Object extractedValue) { + return extractedValue; + } + + } + + /** + * Adapt value to expected output + * @param key + * @param extractedValue + * @return + */ + Type adapt(String key, Object extractedValue); + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/TemplateEngines.java b/src/main/java/org/jbake/template/TemplateEngines.java index 1c3bc97ac..d32f762d8 100644 --- a/src/main/java/org/jbake/template/TemplateEngines.java +++ b/src/main/java/org/jbake/template/TemplateEngines.java @@ -1,10 +1,5 @@ package org.jbake.template; -import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; -import org.apache.commons.configuration.CompositeConfiguration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.File; import java.io.IOException; import java.lang.reflect.Constructor; @@ -18,6 +13,13 @@ import java.util.Set; import org.jbake.app.ContentStore; +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.Configuration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; + /** *

A singleton class giving access to rendering engines. Rendering engines are loaded based on classpath. New * rendering may be registered either at runtime (not recommanded) or by putting a descriptor file on classpath @@ -44,7 +46,7 @@ public Set getRecognizedExtensions() { return Collections.unmodifiableSet(templateEngines.keySet()); } - public TemplateEngines(final CompositeConfiguration config, final ContentStore db, final File destination, final File templatesPath) { + public TemplateEngines(final Configuration config, final ContentStore db, final File destination, final File templatesPath) { templateEngines = new HashMap(); loadEngines(config, db, destination, templatesPath); } @@ -71,7 +73,7 @@ public AbstractTemplateEngine getEngine(String fileExtension) { * @param templatesPath path to template directory * @param engineClassName engine class, used both as a hint to find it and to create the engine itself. @return null if the engine is not available, an instance of the engine otherwise */ - private static AbstractTemplateEngine tryLoadEngine(final CompositeConfiguration config, final ContentStore db, final File destination, final File templatesPath, String engineClassName) { + private static AbstractTemplateEngine tryLoadEngine(final Configuration config, final ContentStore db, final File destination, final File templatesPath, String engineClassName) { try { @SuppressWarnings("unchecked") Class engineClass = (Class) Class.forName(engineClassName, false, TemplateEngines.class.getClassLoader()); @@ -97,7 +99,7 @@ private static AbstractTemplateEngine tryLoadEngine(final CompositeConfiguration * This method is used internally to load markup engines. Markup engines are found using descriptor files on * classpath, so adding an engine is as easy as adding a jar on classpath with the descriptor file included. */ - private void loadEngines(final CompositeConfiguration config, final ContentStore db, final File destination, final File templatesPath) { + private void loadEngines(final Configuration config, final ContentStore db, final File destination, final File templatesPath) { try { ClassLoader cl = TemplateEngines.class.getClassLoader(); Enumeration resources = cl.getResources("META-INF/org.jbake.parser.TemplateEngines.properties"); @@ -116,7 +118,7 @@ private void loadEngines(final CompositeConfiguration config, final ContentStore } } - private void registerEngine(final CompositeConfiguration config, final ContentStore db, final File destination, final File templatesPath, String className, String... extensions) { + private void registerEngine(final Configuration config, final ContentStore db, final File destination, final File templatesPath, String className, String... extensions) { AbstractTemplateEngine engine = tryLoadEngine(config, db, destination, templatesPath, className); if (engine != null) { for (String extension : extensions) { diff --git a/src/main/java/org/jbake/template/ThymeleafTemplateEngine.java b/src/main/java/org/jbake/template/ThymeleafTemplateEngine.java index cbcaa73a3..4a9f412d6 100644 --- a/src/main/java/org/jbake/template/ThymeleafTemplateEngine.java +++ b/src/main/java/org/jbake/template/ThymeleafTemplateEngine.java @@ -1,7 +1,6 @@ package org.jbake.template; import com.orientechnologies.orient.core.record.impl.ODocument; -import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery; import org.apache.commons.configuration.CompositeConfiguration; import org.apache.commons.lang.LocaleUtils; @@ -31,7 +30,7 @@ /** *

A template engine which renders pages using Thymeleaf.

* - *

This template engine is not recommanded for large sites because the whole model + *

This template engine is not recommended for large sites because the whole model * is loaded into memory due to Thymeleaf internal limitations.

* *

The default rendering mode is "HTML5", but it is possible to use another mode @@ -44,6 +43,7 @@ * @author Cédric Champeau */ public class ThymeleafTemplateEngine extends AbstractTemplateEngine { + private static ModelExtractors extractors = new ModelExtractors(); private final ReentrantLock lock = new ReentrantLock(); private TemplateEngine templateEngine; @@ -100,66 +100,15 @@ private VariablesMap wrap(final Map model) { } private class JBakeVariablesMap extends VariablesMap { - - public JBakeVariablesMap(final Map model) { + public JBakeVariablesMap(final Map model) { super(model); - completeModel(); - } - - private void completeModel() { - put("db", db); - put("alltags", getAllTags()); - put("tag_posts", getTagPosts()); - put("published_date", new Date()); - String[] documentTypes = DocumentTypes.getDocumentTypes(); - for (String docType : documentTypes) { - put(docType + "s", DocumentList.wrap(db.getAllContent(docType).iterator())); - put("published_" + docType + "s", DocumentList.wrap(db.getPublishedContent(docType).iterator())); - } - put("published_content", getPublishedContent()); - put("all_content", getAllContent()); - } - - private Object getTagPosts() { - Object tagName = get("tag"); - if (tagName != null) { - String tag = tagName.toString(); - // fetch the tag posts from db - List query = db.getPublishedPostsByTag(tag); - return DocumentList.wrap(query.iterator()); - } else { - return Collections.emptyList(); + for(String key : extractors.keySet()) { + try { + put(key, extractors.extractAndTransform(db, key, model, new TemplateEngineAdapter.NoopAdapter())); + } catch (NoModelExtractorException e) { + // should never happen, as we iterate over existing extractors + } } } - - private Object getAllTags() { - List query = db.getAllTagsFromPublishedPosts(); - Set result = new HashSet(); - for (ODocument document : query) { - String[] tags = DBUtil.toStringArray(document.field("tags")); - Collections.addAll(result, tags); - } - return result; - } - - private Object getPublishedContent() { - List publishedContent = new ArrayList(); - String[] documentTypes = DocumentTypes.getDocumentTypes(); - for (String docType : documentTypes) { - List query = db.getPublishedContent(docType); - publishedContent.addAll(query); - } - return DocumentList.wrap(publishedContent.iterator()); - } - - private Object getAllContent() { - List allContent = new ArrayList(); - String[] documentTypes = DocumentTypes.getDocumentTypes(); - for (String docType : documentTypes) { - List query = db.getAllContent(docType); - allContent.addAll(query); - } - return DocumentList.wrap(allContent.iterator()); - } } } diff --git a/src/main/java/org/jbake/template/model/AllContentExtractor.java b/src/main/java/org/jbake/template/model/AllContentExtractor.java new file mode 100644 index 000000000..698801bd0 --- /dev/null +++ b/src/main/java/org/jbake/template/model/AllContentExtractor.java @@ -0,0 +1,27 @@ +package org.jbake.template.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.jbake.app.ContentStore; +import org.jbake.app.DocumentList; +import org.jbake.model.DocumentTypes; +import org.jbake.template.ModelExtractor; + +import com.orientechnologies.orient.core.record.impl.ODocument; + +public class AllContentExtractor implements ModelExtractor { + + @Override + public DocumentList get(ContentStore db, Map model, String key) { + List allContent = new ArrayList(); + String[] documentTypes = DocumentTypes.getDocumentTypes(); + for (String docType : documentTypes) { + List query = db.getAllContent(docType); + allContent.addAll(query); + } + return DocumentList.wrap(allContent.iterator()); + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/model/AllTagsExtractor.java b/src/main/java/org/jbake/template/model/AllTagsExtractor.java new file mode 100644 index 000000000..278b0358f --- /dev/null +++ b/src/main/java/org/jbake/template/model/AllTagsExtractor.java @@ -0,0 +1,29 @@ +package org.jbake.template.model; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jbake.app.ContentStore; +import org.jbake.app.Crawler; +import org.jbake.app.DBUtil; +import org.jbake.template.ModelExtractor; + +import com.orientechnologies.orient.core.record.impl.ODocument; + +public class AllTagsExtractor implements ModelExtractor> { + + @Override + public Set get(ContentStore db, Map model, String key) { + List query = db.getAllTagsFromPublishedPosts(); + Set result = new HashSet(); + for (ODocument document : query) { + String[] tags = DBUtil.toStringArray(document.field(Crawler.Attributes.TAGS)); + Collections.addAll(result, tags); + } + return result; + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/model/DBExtractor.java b/src/main/java/org/jbake/template/model/DBExtractor.java new file mode 100644 index 000000000..32ad95783 --- /dev/null +++ b/src/main/java/org/jbake/template/model/DBExtractor.java @@ -0,0 +1,15 @@ +package org.jbake.template.model; + +import java.util.Map; + +import org.jbake.app.ContentStore; +import org.jbake.template.ModelExtractor; + +public class DBExtractor implements ModelExtractor { + + @Override + public ContentStore get(ContentStore db, Map model, String key) { + return db; + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/model/ModelExtractionUtils.java b/src/main/java/org/jbake/template/model/ModelExtractionUtils.java new file mode 100644 index 000000000..2d70556c1 --- /dev/null +++ b/src/main/java/org/jbake/template/model/ModelExtractionUtils.java @@ -0,0 +1,16 @@ +package org.jbake.template.model; + +import org.jbake.model.DocumentTypes; + +public class ModelExtractionUtils { + public static String unpluralizeDocumentType(String pluralized) { + String[] documentTypes = DocumentTypes.getDocumentTypes(); + for (String docType : documentTypes) { + if ((docType+"s").equals(pluralized)) { + return docType; + } + } + throw new UnsupportedOperationException("there is no document type we can pluralize as \""+pluralized+"\"\n" + + "We only have "+documentTypes); + } +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/model/PublishedContentExtractor.java b/src/main/java/org/jbake/template/model/PublishedContentExtractor.java new file mode 100644 index 000000000..6953ec78c --- /dev/null +++ b/src/main/java/org/jbake/template/model/PublishedContentExtractor.java @@ -0,0 +1,27 @@ +package org.jbake.template.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.jbake.app.ContentStore; +import org.jbake.app.DocumentList; +import org.jbake.model.DocumentTypes; +import org.jbake.template.ModelExtractor; + +import com.orientechnologies.orient.core.record.impl.ODocument; + +public class PublishedContentExtractor implements ModelExtractor { + + @Override + public DocumentList get(ContentStore db, Map model, String key) { + List publishedContent = new ArrayList(); + String[] documentTypes = DocumentTypes.getDocumentTypes(); + for (String docType : documentTypes) { + List query = db.getPublishedContent(docType); + publishedContent.addAll(query); + } + return DocumentList.wrap(publishedContent.iterator()); + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/model/PublishedDateExtractor.java b/src/main/java/org/jbake/template/model/PublishedDateExtractor.java new file mode 100644 index 000000000..0cc9b9896 --- /dev/null +++ b/src/main/java/org/jbake/template/model/PublishedDateExtractor.java @@ -0,0 +1,16 @@ +package org.jbake.template.model; + +import java.util.Date; +import java.util.Map; + +import org.jbake.app.ContentStore; +import org.jbake.template.ModelExtractor; + +public class PublishedDateExtractor implements ModelExtractor { + + @Override + public Date get(ContentStore db, Map model, String key) { + return new Date(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/model/PublishedPagesExtractor.java b/src/main/java/org/jbake/template/model/PublishedPagesExtractor.java new file mode 100644 index 000000000..7ed59ea25 --- /dev/null +++ b/src/main/java/org/jbake/template/model/PublishedPagesExtractor.java @@ -0,0 +1,20 @@ +package org.jbake.template.model; + +import java.util.List; +import java.util.Map; + +import org.jbake.app.ContentStore; +import org.jbake.app.DocumentList; +import org.jbake.template.ModelExtractor; + +import com.orientechnologies.orient.core.record.impl.ODocument; + +public class PublishedPagesExtractor implements ModelExtractor { + + @Override + public DocumentList get(ContentStore db, Map model, String key) { + List query = db.getPublishedPages(); + return DocumentList.wrap(query.iterator()); + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/model/PublishedPostsExtractor.java b/src/main/java/org/jbake/template/model/PublishedPostsExtractor.java new file mode 100644 index 000000000..19565aabe --- /dev/null +++ b/src/main/java/org/jbake/template/model/PublishedPostsExtractor.java @@ -0,0 +1,20 @@ +package org.jbake.template.model; + +import java.util.List; +import java.util.Map; + +import org.jbake.app.ContentStore; +import org.jbake.app.DocumentList; +import org.jbake.template.ModelExtractor; + +import com.orientechnologies.orient.core.record.impl.ODocument; + +public class PublishedPostsExtractor implements ModelExtractor { + + @Override + public DocumentList get(ContentStore db, Map model, String key) { + List query = db.getPublishedPosts(); + return DocumentList.wrap(query.iterator()); + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/model/TagPostsExtractor.java b/src/main/java/org/jbake/template/model/TagPostsExtractor.java new file mode 100644 index 000000000..c06c945c8 --- /dev/null +++ b/src/main/java/org/jbake/template/model/TagPostsExtractor.java @@ -0,0 +1,26 @@ +package org.jbake.template.model; + +import java.util.List; +import java.util.Map; + +import org.jbake.app.ContentStore; +import org.jbake.app.Crawler; +import org.jbake.app.DocumentList; +import org.jbake.template.ModelExtractor; + +import com.orientechnologies.orient.core.record.impl.ODocument; + +public class TagPostsExtractor implements ModelExtractor { + + @Override + public DocumentList get(ContentStore db, Map model, String key) { + String tag = null; + if (model.get(Crawler.Attributes.TAG) != null) { + tag = model.get(Crawler.Attributes.TAG).toString(); + } + // fetch the tag posts from db + List query = db.getPublishedPostsByTag(tag); + return DocumentList.wrap(query.iterator()); + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/model/TypedDocumentsExtractor.java b/src/main/java/org/jbake/template/model/TypedDocumentsExtractor.java new file mode 100644 index 000000000..5f0b6c371 --- /dev/null +++ b/src/main/java/org/jbake/template/model/TypedDocumentsExtractor.java @@ -0,0 +1,27 @@ +package org.jbake.template.model; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.jbake.app.ContentStore; +import org.jbake.app.DocumentList; +import org.jbake.template.ModelExtractor; + +import com.orientechnologies.orient.core.record.impl.ODocument; + +public class TypedDocumentsExtractor implements ModelExtractor { + + @Override + public DocumentList get(ContentStore db, Map model, String key) { + // document types are pluralized in model, so unpluralize + try { + String type = ModelExtractionUtils.unpluralizeDocumentType(key); + return DocumentList.wrap(db.getAllContent(type).iterator()); + } catch(UnsupportedOperationException e) { + List none = Collections.emptyList(); + return DocumentList.wrap(none.iterator()); + } + } + +} \ No newline at end of file diff --git a/src/main/resources/META-INF/org.jbake.parser.TemplateEngines.properties b/src/main/resources/META-INF/org.jbake.parser.TemplateEngines.properties index e1ba0e56e..934245673 100644 --- a/src/main/resources/META-INF/org.jbake.parser.TemplateEngines.properties +++ b/src/main/resources/META-INF/org.jbake.parser.TemplateEngines.properties @@ -1,3 +1,7 @@ org.jbake.template.FreemarkerTemplateEngine=ftl org.jbake.template.GroovyTemplateEngine=groovy,gsp,gxml -org.jbake.template.ThymeleafTemplateEngine=thyme,html \ No newline at end of file +org.jbake.template.GroovyMarkupTemplateEngine=tpl +org.jbake.template.ThymeleafTemplateEngine=thyme +org.jbake.template.PebbleTemplateEngine=pebble,peb +org.jbake.template.ThymeleafTemplateEngine=thyme,html +org.jbake.template.JadeTemplateEngine=jade diff --git a/src/main/resources/META-INF/org.jbake.template.ModelExtractors.properties b/src/main/resources/META-INF/org.jbake.template.ModelExtractors.properties new file mode 100644 index 000000000..2bf03c478 --- /dev/null +++ b/src/main/resources/META-INF/org.jbake.template.ModelExtractors.properties @@ -0,0 +1,9 @@ +org.jbake.template.model.PublishedPostsExtractor=published_posts +org.jbake.template.model.PublishedPagesExtractor=published_pages +org.jbake.template.model.PublishedContentExtractor=published_content +org.jbake.template.model.AllContentExtractor=all_content +org.jbake.template.model.AllTagsExtractor=alltags +org.jbake.template.model.TypedDocumentsExtractor=pages,posts,indexs,archives,feeds +org.jbake.template.model.PublishedDateExtractor=published_date +org.jbake.template.model.DBExtractor=db +org.jbake.template.model.TagPostsExtractor=tag_posts \ No newline at end of file diff --git a/src/main/resources/META-INF/services/org.jbake.render.RenderingTool.properties b/src/main/resources/META-INF/services/org.jbake.render.RenderingTool.properties new file mode 100644 index 000000000..7e390da07 --- /dev/null +++ b/src/main/resources/META-INF/services/org.jbake.render.RenderingTool.properties @@ -0,0 +1,7 @@ +org.jbake.render.ArchiveRenderer +org.jbake.render.DocumentsRenderer +org.jbake.render.FeedRenderer +org.jbake.render.IndexRenderer +org.jbake.render.SitemapRenderer +org.jbake.render.TagsRenderer +org.jbake.render.DatesRenderer \ No newline at end of file diff --git a/src/main/resources/default.properties b/src/main/resources/default.properties index 450767fd5..b7a52a0c5 100644 --- a/src/main/resources/default.properties +++ b/src/main/resources/default.properties @@ -64,6 +64,8 @@ example.project.freemarker=example_project_freemarker.zip example.project.groovy=example_project_groovy.zip # zip file containing example project structure using thymeleaf templates example.project.thymeleaf=example_project_thymeleaf.zip +# zip file containing example project structure using jade templates +example.project.jade=example_project_jade.zip # default asciidoctor options asciidoctor.attributes=source-highlighter=prettify # should JBake config options be exported to Asciidoctor engine? @@ -82,4 +84,6 @@ markdown.maxParsingTimeInMillis=2000 # database store (local, memory) db.store=memory # database path -db.path=cache \ No newline at end of file +db.path=cache +# Set to a path (starting with a slash) for which to generate a folder and index.html +uri.noExtension=false diff --git a/src/test/java/org/jbake/app/CrawlerTest.java b/src/test/java/org/jbake/app/CrawlerTest.java index 42ccdd488..048c24515 100644 --- a/src/test/java/org/jbake/app/CrawlerTest.java +++ b/src/test/java/org/jbake/app/CrawlerTest.java @@ -1,5 +1,6 @@ package org.jbake.app; +import static org.junit.Assert.assertThat; import java.io.File; import java.io.IOException; @@ -14,6 +15,15 @@ import org.apache.commons.configuration.CompositeConfiguration; import org.apache.commons.configuration.ConfigurationException; import org.jbake.app.ConfigUtil.Keys; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.MapConfiguration; +import org.apache.commons.io.FilenameUtils; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -37,7 +47,7 @@ public void setup() throws Exception, IOException, URISyntaxException { config = ConfigUtil.load(new File(this.getClass().getResource("/").getFile())); Assert.assertEquals(".html", config.getString(Keys.OUTPUT_EXTENSION)); - db = DBUtil.createDB("memory", "documents"+System.currentTimeMillis()); + db = DBUtil.createDataStore("memory", "documents"+System.currentTimeMillis()); } @After @@ -58,10 +68,57 @@ public void crawl() throws ConfigurationException { DocumentList list = DocumentList.wrap(results.iterator()); for (Map content : list) { assertThat(content) - .containsKey("rootpath") + .containsKey(Crawler.Attributes.ROOTPATH) .containsValue("../../"); } } - + @Test + public void renderWithPrettyUrls() throws Exception { + Map testProperties = new HashMap(); + testProperties.put(Keys.URI_NO_EXTENSION, "/blog"); + + CompositeConfiguration config = new CompositeConfiguration(); + config.addConfiguration(new MapConfiguration(testProperties)); + config.addConfiguration(ConfigUtil.load(new File(this.getClass().getResource("/").getFile()))); + + URL contentUrl = this.getClass().getResource("/"); + File content = new File(contentUrl.getFile()); + Crawler crawler = new Crawler(db, content, config); + crawler.crawl(new File(content.getPath() + File.separator + "content")); + + Assert.assertEquals(2, crawler.getDocumentCount("post")); + Assert.assertEquals(3, crawler.getDocumentCount("page")); + DocumentIterator documents = new DocumentIterator(db.getPublishedPosts().iterator()); + while (documents.hasNext()) { + Map model = documents.next(); + String noExtensionUri = "blog/\\d{4}/" + FilenameUtils.getBaseName((String) model.get("file")) + "/"; + + assertThat(model.get("noExtensionUri"), RegexMatcher.matches(noExtensionUri)); + assertThat(model.get("uri"), RegexMatcher.matches(noExtensionUri + "index\\.html")); + } + } + + private static class RegexMatcher extends BaseMatcher { + private final String regex; + + public RegexMatcher(String regex){ + this.regex = regex; + } + + @Override + public boolean matches(Object o){ + return ((String)o).matches(regex); + + } + + @Override + public void describeTo(Description description){ + description.appendText("matches regex: " + regex); + } + + public static RegexMatcher matches(String regex){ + return new RegexMatcher(regex); + } + } } diff --git a/src/test/java/org/jbake/app/PaginationTest.java b/src/test/java/org/jbake/app/PaginationTest.java new file mode 100644 index 000000000..552943f2a --- /dev/null +++ b/src/test/java/org/jbake/app/PaginationTest.java @@ -0,0 +1,101 @@ +/* + * The MIT License + * + * Copyright 2015 jdlee. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jbake.app; + +import com.orientechnologies.orient.core.record.impl.ODocument; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import org.apache.commons.configuration.CompositeConfiguration; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * + * @author jdlee + */ +public class PaginationTest { + + private CompositeConfiguration config; + private ContentStore db; + + @Before + public void setup() throws Exception, IOException, URISyntaxException { + config = ConfigUtil.load(new File(this.getClass().getResource("/").getFile())); + Iterator keys = config.getKeys(); + while (keys.hasNext()) { + String key = keys.next(); + if (key.startsWith("template") && key.endsWith(".file")) { + String old = (String) config.getProperty(key); + config.setProperty(key, old.substring(0, old.length() - 4) + ".ftl"); + } + } + config.setProperty(ConfigUtil.Keys.PAGINATE_INDEX, true); + config.setProperty(ConfigUtil.Keys.POSTS_PER_PAGE, 1); + db = DBUtil.createDataStore("memory", "documents" + System.currentTimeMillis()); + } + + @After + public void cleanup() throws InterruptedException { + db.drop(); + db.close(); + } + + @Test + public void testPagination() { + Map fileContents = new HashMap(); + final int TOTAL_POSTS = 5; + final int PER_PAGE = 2; + + for (int i = 1; i <= TOTAL_POSTS; i++) { + fileContents.put("name", "dummyfile" + i); + + ODocument doc = new ODocument("post"); + doc.fields(fileContents); + boolean cached = fileContents.get("cached") != null ? Boolean.valueOf((String) fileContents.get("cached")) : true; + doc.field("cached", cached); + doc.save(); + } + + int iterationCount = 0; + int start = 0; + db.setLimit(PER_PAGE); + + while (start < TOTAL_POSTS) { + db.setStart(start); + List posts = db.getAllContent("post"); + Assert.assertEquals("dummyfile" + (1 + (PER_PAGE * iterationCount)), posts.get(0).field("name")); +// Assert.assertEquals("dummyfile" + (PER_PAGE + (PER_PAGE * iterationCount)), posts.get(posts.size()-1).field("name")); + iterationCount++; + start += PER_PAGE; + } + Assert.assertEquals(Math.round(TOTAL_POSTS / (1.0*PER_PAGE) + 0.4), iterationCount); + } +} diff --git a/src/test/java/org/jbake/app/RendererTest.java b/src/test/java/org/jbake/app/RendererTest.java deleted file mode 100644 index c41546572..000000000 --- a/src/test/java/org/jbake/app/RendererTest.java +++ /dev/null @@ -1,228 +0,0 @@ -package org.jbake.app; - -import org.jbake.app.ConfigUtil.Keys; -import org.jbake.model.DocumentTypes; -import org.junit.After; - -import java.io.File; - -import static org.assertj.core.api.Assertions.*; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Map; - -import org.apache.commons.configuration.CompositeConfiguration; -import org.apache.commons.io.FileUtils; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -public class RendererTest { - - @Rule - public TemporaryFolder folder = new TemporaryFolder(); - - private File sourceFolder; - private File destinationFolder; - private File templateFolder; - private CompositeConfiguration config; - private ContentStore db; - - @Before - public void setup() throws Exception, IOException, URISyntaxException { - URL sourceUrl = this.getClass().getResource("/"); - - sourceFolder = new File(sourceUrl.getFile()); - if (!sourceFolder.exists()) { - throw new Exception("Cannot find sample data structure!"); - } - - destinationFolder = folder.getRoot(); - - templateFolder = new File(sourceFolder, "templates"); - if (!templateFolder.exists()) { - throw new Exception("Cannot find template folder!"); - } - - config = ConfigUtil.load(new File(this.getClass().getResource("/").getFile())); - Assert.assertEquals(".html", config.getString(Keys.OUTPUT_EXTENSION)); - db = DBUtil.createDB("memory", "documents"+System.currentTimeMillis()); - } - - @After - public void cleanup() throws InterruptedException { - db.drop(); - db.close(); - } - - @Test - public void renderPost() throws Exception { - Crawler crawler = new Crawler(db, sourceFolder, config); - crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); - Parser parser = new Parser(config, sourceFolder.getPath()); - Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); - - File sampleFile = new File(sourceFolder.getPath() + File.separator + "content" + File.separator + "blog" + File.separator + "2013" + File.separator + "second-post.html"); - Map content = parser.processFile(sampleFile); - content.put("uri", "/second-post.html"); - renderer.render(content); - File outputFile = new File(destinationFolder, "second-post.html"); - Assert.assertTrue(outputFile.exists()); - - // verify - String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("

Second Post

") - .contains("

28") - .contains("2013

") - .contains("Lorem ipsum dolor sit amet") - .contains("
Published Posts
") - .contains("blog/2012/first-post.html") - .contains(""); - } - - @Test - public void renderPage() throws Exception { - // setup - Crawler crawler = new Crawler(db, sourceFolder, config); - crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); - Parser parser = new Parser(config, sourceFolder.getPath()); - Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); - String filename = "about.html"; - - File sampleFile = new File(sourceFolder.getPath() + File.separator + "content" + File.separator + filename); - Map content = parser.processFile(sampleFile); - content.put("uri", "/" + filename); - renderer.render(content); - File outputFile = new File(destinationFolder, filename); - Assert.assertTrue(outputFile.exists()); - - // verify - String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("

About

") - .contains("All about stuff!") - .contains("
Published Pages
") - .contains("/projects.html"); - } - - @Test - public void renderIndex() throws Exception { - //setup - Crawler crawler = new Crawler(db, sourceFolder, config); - crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); - Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); - //exec - renderer.renderIndex("index.html"); - - //validate - File outputFile = new File(destinationFolder, "index.html"); - Assert.assertTrue(outputFile.exists()); - - // verify - String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("

First Post

") - .contains("

Second Post

"); - } - - @Test - public void renderFeed() throws Exception { - Crawler crawler = new Crawler(db, sourceFolder, config); - crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); - Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); - renderer.renderFeed("feed.xml"); - File outputFile = new File(destinationFolder, "feed.xml"); - Assert.assertTrue(outputFile.exists()); - - // verify - String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("My corner of the Internet") - .contains("Second Post") - .contains("First Post"); - } - - @Test - public void renderArchive() throws Exception { - Crawler crawler = new Crawler(db, sourceFolder, config); - crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); - Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); - renderer.renderArchive("archive.html"); - File outputFile = new File(destinationFolder, "archive.html"); - Assert.assertTrue(outputFile.exists()); - - // verify - String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("Second Post") - .contains("First Post"); - } - - @Test - public void renderTags() throws Exception { - Crawler crawler = new Crawler(db, sourceFolder, config); - crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); - Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); - renderer.renderTags(crawler.getTags(), "tags"); - - // verify - File outputFile = new File(destinationFolder + File.separator + "tags" + File.separator + "blog.html"); - Assert.assertTrue(outputFile.exists()); - String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("Second Post") - .contains("First Post"); - } - - @Test - public void renderSitemap() throws Exception { - DocumentTypes.addDocumentType("paper"); - DBUtil.updateSchema(db); - - Crawler crawler = new Crawler(db, sourceFolder, config); - crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); - Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); - renderer.renderSitemap("sitemap.xml"); - File outputFile = new File(destinationFolder, "sitemap.xml"); - Assert.assertTrue(outputFile.exists()); - - // verify - String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("blog/2013/second-post.html") - .contains("blog/2012/first-post.html") - .contains("papers/published-paper.html") - .doesNotContain("draft-paper.html"); - } - - @Test - public void renderAllContent() throws Exception { - DocumentTypes.addDocumentType("paper"); - DBUtil.updateSchema(db); - - Crawler crawler = new Crawler(db, sourceFolder, config); - crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); - Parser parser = new Parser(config, sourceFolder.getPath()); - Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); - - File sampleFile = new File(sourceFolder.getPath() + File.separator + "content" + File.separator + "allcontent.html"); - Map content = parser.processFile(sampleFile); - content.put("uri", "/allcontent.html"); - renderer.render(content); - File outputFile = new File(destinationFolder, "allcontent.html"); - Assert.assertTrue(outputFile.exists()); - - // verify - String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("blog/2013/second-post.html") - .contains("blog/2012/first-post.html") - .contains("papers/published-paper.html") - .contains("draft-paper.html"); - } -} diff --git a/src/test/java/org/jbake/app/ThymeleafRendererTest.java b/src/test/java/org/jbake/app/ThymeleafRendererTest.java deleted file mode 100644 index fed656e95..000000000 --- a/src/test/java/org/jbake/app/ThymeleafRendererTest.java +++ /dev/null @@ -1,215 +0,0 @@ -package org.jbake.app; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; - -import org.apache.commons.configuration.CompositeConfiguration; -import org.apache.commons.io.FileUtils; -import org.jbake.app.ConfigUtil.Keys; -import org.jbake.model.DocumentTypes; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Iterator; -import java.util.Map; -import java.util.Scanner; - -public class ThymeleafRendererTest { - - @Rule - public TemporaryFolder folder = new TemporaryFolder(); - - private File sourceFolder; - private File destinationFolder; - private File templateFolder; - private CompositeConfiguration config; - private ContentStore db; - - @Before - public void setup() throws Exception, IOException, URISyntaxException { - URL sourceUrl = this.getClass().getResource("/"); - - sourceFolder = new File(sourceUrl.getFile()); - if (!sourceFolder.exists()) { - throw new Exception("Cannot find sample data structure!"); - } - - destinationFolder = folder.getRoot(); - - templateFolder = new File(sourceFolder, "thymeleafTemplates"); - if (!templateFolder.exists()) { - throw new Exception("Cannot find template folder!"); - } - - config = ConfigUtil.load(new File(this.getClass().getResource("/").getFile())); - Iterator keys = config.getKeys(); - while (keys.hasNext()) { - String key = keys.next(); - if (key.startsWith("template") && key.endsWith(".file")) { - String old = (String)config.getProperty(key); - config.setProperty(key, old.substring(0, old.length()-4)+".thyme"); - } - } - Assert.assertEquals(".html", config.getString(Keys.OUTPUT_EXTENSION)); - db = DBUtil.createDB("memory", "documents"+System.currentTimeMillis()); - - } - - @After - public void cleanup() throws InterruptedException { - db.drop(); - db.close(); - } - - @Test - public void renderPost() throws Exception { - // setup - Crawler crawler = new Crawler(db, sourceFolder, config); - crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); - Parser parser = new Parser(config, sourceFolder.getPath()); - Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); - String filename = "second-post.html"; - - File sampleFile = new File(sourceFolder.getPath() + File.separator + "content" + File.separator + "blog" + File.separator + "2013" + File.separator + filename); - Map content = parser.processFile(sampleFile); - content.put("uri", "/" + filename); - renderer.render(content); - File outputFile = new File(destinationFolder, filename); - Assert.assertTrue(outputFile.exists()); - - // verify - String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("

Second Post

") - .contains("

28") - .contains("2013

") - .contains("Lorem ipsum dolor sit amet") - .contains("
Published Posts
") - .contains("blog/2012/first-post.html"); - } - - @Test - public void renderPage() throws Exception { - // setup - Crawler crawler = new Crawler(db, sourceFolder, config); - crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); - Parser parser = new Parser(config, sourceFolder.getPath()); - Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); - String filename = "about.html"; - - File sampleFile = new File(sourceFolder.getPath() + File.separator + "content" + File.separator + filename); - Map content = parser.processFile(sampleFile); - content.put("uri", "/" + filename); - renderer.render(content); - File outputFile = new File(destinationFolder, filename); - Assert.assertTrue(outputFile.exists()); - - // verify - String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("

About

") - .contains("All about stuff!") - .contains("
Published Pages
") - .contains("/projects.html"); - } - - @Test - public void renderIndex() throws Exception { - //setup - Crawler crawler = new Crawler(db, sourceFolder, config); - crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); - Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); - //exec - renderer.renderIndex("index.html"); - - //validate - File outputFile = new File(destinationFolder, "index.html"); - Assert.assertTrue(outputFile.exists()); - - // verify - String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("

First Post

") - .contains("

Second Post

"); - } - - @Test - public void renderFeed() throws Exception { - Crawler crawler = new Crawler(db, sourceFolder, config); - crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); - Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); - renderer.renderFeed("feed.xml"); - File outputFile = new File(destinationFolder, "feed.xml"); - Assert.assertTrue(outputFile.exists()); - - // verify - String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("My corner of the Internet") - .contains("Second Post") - .contains("First Post"); - } - - @Test - public void renderArchive() throws Exception { - Crawler crawler = new Crawler(db, sourceFolder, config); - crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); - Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); - renderer.renderArchive("archive.html"); - File outputFile = new File(destinationFolder, "archive.html"); - Assert.assertTrue(outputFile.exists()); - - // verify - String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("Second Post") - .contains("First Post"); - } - - @Test - public void renderTags() throws Exception { - Crawler crawler = new Crawler(db, sourceFolder, config); - crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); - Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); - renderer.renderTags(crawler.getTags(), "tags"); - - // verify - File outputFile = new File(destinationFolder + File.separator + "tags" + File.separator + "blog.html"); - Assert.assertTrue(outputFile.exists()); - String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("Second Post") - .contains("First Post"); - } - - @Test - public void renderSitemap() throws Exception { - // cannot be put in @Before as ThmyeLeaf can't lazy load from DB, and expects content there under this type - DocumentTypes.addDocumentType("paper"); - DBUtil.updateSchema(db); - - Crawler crawler = new Crawler(db, sourceFolder, config); - crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); - Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); - renderer.renderSitemap("sitemap.xml"); - File outputFile = new File(destinationFolder, "sitemap.xml"); - Assert.assertTrue(outputFile.exists()); - - // verify - String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("blog/2013/second-post.html") - .contains("blog/2012/first-post.html") - .contains("papers/published-paper.html") - .doesNotContain("draft-paper.html"); - } -} diff --git a/src/test/java/org/jbake/app/GroovyRendererTest.java b/src/test/java/org/jbake/app/template/AbstractTemplateEngineRenderingTest.java similarity index 60% rename from src/test/java/org/jbake/app/GroovyRendererTest.java rename to src/test/java/org/jbake/app/template/AbstractTemplateEngineRenderingTest.java index 53d4cb264..ea14b09d3 100644 --- a/src/test/java/org/jbake/app/GroovyRendererTest.java +++ b/src/test/java/org/jbake/app/template/AbstractTemplateEngineRenderingTest.java @@ -1,12 +1,45 @@ -package org.jbake.app; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; +/* + * The MIT License + * + * Copyright 2015 jdlee. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jbake.app.template; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import org.apache.commons.configuration.CompositeConfiguration; import org.apache.commons.io.FileUtils; -import org.jbake.app.ConfigUtil.Keys; +import static org.assertj.core.api.Assertions.assertThat; +import org.jbake.app.ConfigUtil; +import org.jbake.app.ContentStore; +import org.jbake.app.Crawler; +import org.jbake.app.DBUtil; +import org.jbake.app.Parser; +import org.jbake.app.Renderer; import org.jbake.model.DocumentTypes; import org.junit.After; import org.junit.Assert; @@ -15,24 +48,29 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Iterator; -import java.util.Map; -import java.util.Scanner; - -public class GroovyRendererTest { +/** + * + * @author jdlee + */ +public abstract class AbstractTemplateEngineRenderingTest { @Rule public TemporaryFolder folder = new TemporaryFolder(); - private File sourceFolder; - private File destinationFolder; - private File templateFolder; - private CompositeConfiguration config; - private ContentStore db; + protected File sourceFolder; + protected File destinationFolder; + protected File templateFolder; + protected CompositeConfiguration config; + protected ContentStore db; + + protected final String templateDir; + protected final String templateExtension; + protected final Map> outputStrings = new HashMap>(); + + public AbstractTemplateEngineRenderingTest(String templateDir, String templateExtension) { + this.templateDir = templateDir; + this.templateExtension = templateExtension; + } @Before public void setup() throws Exception, IOException, URISyntaxException { @@ -45,7 +83,7 @@ public void setup() throws Exception, IOException, URISyntaxException { destinationFolder = folder.getRoot(); - templateFolder = new File(sourceFolder, "groovyTemplates"); + templateFolder = new File(sourceFolder, templateDir); if (!templateFolder.exists()) { throw new Exception("Cannot find template folder!"); } @@ -55,12 +93,12 @@ public void setup() throws Exception, IOException, URISyntaxException { while (keys.hasNext()) { String key = keys.next(); if (key.startsWith("template") && key.endsWith(".file")) { - String old = (String)config.getProperty(key); - config.setProperty(key, old.substring(0, old.length()-4)+".gsp"); + String old = (String) config.getProperty(key); + config.setProperty(key, old.substring(0, old.length() - 4) + "." + templateExtension); } } - Assert.assertEquals(".html", config.getString(Keys.OUTPUT_EXTENSION)); - db = DBUtil.createDB("memory", "documents"+System.currentTimeMillis()); + Assert.assertEquals(".html", config.getString(ConfigUtil.Keys.OUTPUT_EXTENSION)); + db = DBUtil.createDataStore("memory", "documents"+System.currentTimeMillis()); } @After @@ -71,34 +109,31 @@ public void cleanup() throws InterruptedException { @Test public void renderPost() throws Exception { - // setup + // setup Crawler crawler = new Crawler(db, sourceFolder, config); crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); Parser parser = new Parser(config, sourceFolder.getPath()); Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); String filename = "second-post.html"; - File sampleFile = new File(sourceFolder.getPath() + File.separator + "content" + File.separator + "blog" + File.separator + "2013" + File.separator + filename); + File sampleFile = new File(sourceFolder.getPath() + File.separator + "content" + + File.separator + "blog" + File.separator + "2013" + File.separator + filename); Map content = parser.processFile(sampleFile); - content.put("uri", "/" + filename); + content.put(Crawler.Attributes.URI, "/" + filename); renderer.render(content); File outputFile = new File(destinationFolder, filename); Assert.assertTrue(outputFile.exists()); - + // verify String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("

Second Post

") - .contains("

28") - .contains("2013

") - .contains("Lorem ipsum dolor sit amet") - .contains("
Published Posts
") - .contains("blog/2012/first-post.html"); + for (String string : getOutputStrings("post")) { + assertThat(output).contains(string); + } } - + @Test public void renderPage() throws Exception { - // setup + // setup Crawler crawler = new Crawler(db, sourceFolder, config); crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); Parser parser = new Parser(config, sourceFolder.getPath()); @@ -107,18 +142,16 @@ public void renderPage() throws Exception { File sampleFile = new File(sourceFolder.getPath() + File.separator + "content" + File.separator + filename); Map content = parser.processFile(sampleFile); - content.put("uri", "/" + filename); + content.put(Crawler.Attributes.URI, "/" + filename); renderer.render(content); File outputFile = new File(destinationFolder, filename); Assert.assertTrue(outputFile.exists()); - + // verify String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("

About

") - .contains("All about stuff!") - .contains("
Published Pages
") - .contains("/projects.html"); + for (String string : getOutputStrings("page")) { + assertThat(output).contains(string); + } } @Test @@ -128,17 +161,17 @@ public void renderIndex() throws Exception { crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); //exec - renderer.renderIndex("index.html"); + renderer.renderIndex("index.html", db); //validate File outputFile = new File(destinationFolder, "index.html"); Assert.assertTrue(outputFile.exists()); - + // verify String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("

First Post

") - .contains("

Second Post

"); + for (String string : getOutputStrings("index")) { + assertThat(output).contains(string); + } } @Test @@ -149,13 +182,12 @@ public void renderFeed() throws Exception { renderer.renderFeed("feed.xml"); File outputFile = new File(destinationFolder, "feed.xml"); Assert.assertTrue(outputFile.exists()); - + // verify String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("My corner of the Internet") - .contains("Second Post") - .contains("First Post"); + for (String string : getOutputStrings("feed")) { + assertThat(output).contains(string); + } } @Test @@ -166,12 +198,12 @@ public void renderArchive() throws Exception { renderer.renderArchive("archive.html"); File outputFile = new File(destinationFolder, "archive.html"); Assert.assertTrue(outputFile.exists()); - + // verify String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("Second Post") - .contains("First Post"); + for (String string : getOutputStrings("archive")) { + assertThat(output).contains(string); + } } @Test @@ -180,22 +212,22 @@ public void renderTags() throws Exception { crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); renderer.renderTags(crawler.getTags(), "tags"); - + // verify File outputFile = new File(destinationFolder + File.separator + "tags" + File.separator + "blog.html"); Assert.assertTrue(outputFile.exists()); String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("Second Post") - .contains("First Post"); + for (String string : getOutputStrings("tags")) { + assertThat(output).contains(string); + } } @Test public void renderSitemap() throws Exception { - DocumentTypes.addDocumentType("paper"); - DBUtil.updateSchema(db); - - Crawler crawler = new Crawler(db, sourceFolder, config); + DocumentTypes.addDocumentType("paper"); + DBUtil.updateSchema(db); + + Crawler crawler = new Crawler(db, sourceFolder, config); crawler.crawl(new File(sourceFolder.getPath() + File.separator + "content")); Renderer renderer = new Renderer(db, destinationFolder, templateFolder, config); renderer.renderSitemap("sitemap.xml"); @@ -204,10 +236,14 @@ public void renderSitemap() throws Exception { // verify String output = FileUtils.readFileToString(outputFile); - assertThat(output) - .contains("blog/2013/second-post.html") - .contains("blog/2012/first-post.html") - .contains("papers/published-paper.html") - .doesNotContain("draft-paper.html"); + for (String string : getOutputStrings("sitemap")) { + assertThat(output).contains(string); + } + assertThat(output).doesNotContain("draft-paper.html"); + } + + private List getOutputStrings(String type) { + return outputStrings.get(type); + } } diff --git a/src/test/java/org/jbake/app/template/FreemarkerTemplateEngineRenderingTest.java b/src/test/java/org/jbake/app/template/FreemarkerTemplateEngineRenderingTest.java new file mode 100644 index 000000000..bf2cf0558 --- /dev/null +++ b/src/test/java/org/jbake/app/template/FreemarkerTemplateEngineRenderingTest.java @@ -0,0 +1,81 @@ +/* + * The MIT License + * + * Copyright 2015 jdlee. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jbake.app.template; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.util.Arrays; + +import org.apache.commons.io.FileUtils; +import org.jbake.app.Crawler; +import org.jbake.app.Renderer; +import org.jbake.app.ConfigUtil.Keys; +import org.junit.Assert; +import org.junit.Test; + +/** + * + * @author jdlee + */ +public class FreemarkerTemplateEngineRenderingTest extends AbstractTemplateEngineRenderingTest{ + + public FreemarkerTemplateEngineRenderingTest() { + super("freemarkerTemplates", "ftl"); + + outputStrings.put("post", Arrays.asList("

Second Post

", + "

28 February 2013

", + "Lorem ipsum dolor sit amet", "")); + outputStrings.put("page", Arrays.asList("About", + "All about stuff!")); + outputStrings.put("index", Arrays.asList("", + "")); + outputStrings.put("feed", Arrays.asList("My corner of the Internet", + "Second Post", + "First Post")); + outputStrings.put("archive", Arrays.asList("", + "")); + outputStrings.put("tags", Arrays.asList("", + "")); + outputStrings.put("sitemap", Arrays.asList("blog/2013/second-post.html", + "blog/2012/first-post.html", + "papers/published-paper.html")); + } + + @Test + @Override + public void renderIndex() throws Exception { + config.setProperty(Keys.PAGINATE_INDEX, true); + config.setProperty(Keys.POSTS_PER_PAGE, 1); + outputStrings.put("index", Arrays.asList("")); + super.renderIndex(); + + File outputFile2 = new File(destinationFolder, "index2.html"); + Assert.assertTrue(outputFile2.exists()); + + String output2 = FileUtils.readFileToString(outputFile2); + assertThat(output2).contains(""); + } + +} diff --git a/src/test/java/org/jbake/app/template/GroovyMarkupTemplateEngineRenderingTest.java b/src/test/java/org/jbake/app/template/GroovyMarkupTemplateEngineRenderingTest.java new file mode 100644 index 000000000..d4d342bc1 --- /dev/null +++ b/src/test/java/org/jbake/app/template/GroovyMarkupTemplateEngineRenderingTest.java @@ -0,0 +1,33 @@ +package org.jbake.app.template; + +import java.util.Arrays; + +public class GroovyMarkupTemplateEngineRenderingTest extends AbstractTemplateEngineRenderingTest { + + public GroovyMarkupTemplateEngineRenderingTest() { + super("groovyMarkupTemplates", "tpl"); + + outputStrings.put("post", Arrays.asList("

Second Post

", + "

28", + "2013

", + "Lorem ipsum dolor sit amet", + "
Published Posts
", + "blog/2012/first-post.html")); + outputStrings.put("page", Arrays.asList("

About

", + "All about stuff!", + "
Published Pages
", + "/projects.html")); + outputStrings.put("index", Arrays.asList("

First Post

", + "

Second Post

")); + outputStrings.put("feed", Arrays.asList("My corner of the Internet", + "Second Post", + "First Post")); + outputStrings.put("archive", Arrays.asList("Second Post", + "First Post")); + outputStrings.put("tags", Arrays.asList("Second Post", + "First Post")); + outputStrings.put("sitemap", Arrays.asList("blog/2013/second-post.html", + "blog/2012/first-post.html", + "papers/published-paper.html")); + } +} diff --git a/src/test/java/org/jbake/app/template/GroovyTemplateEngineRenderingTest.java b/src/test/java/org/jbake/app/template/GroovyTemplateEngineRenderingTest.java new file mode 100644 index 000000000..2eb238874 --- /dev/null +++ b/src/test/java/org/jbake/app/template/GroovyTemplateEngineRenderingTest.java @@ -0,0 +1,61 @@ +/* + * The MIT License + * + * Copyright 2015 jdlee. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jbake.app.template; + +import java.util.Arrays; + +/** + * + * @author jdlee + */ +public class GroovyTemplateEngineRenderingTest extends AbstractTemplateEngineRenderingTest{ + + public GroovyTemplateEngineRenderingTest() { + super("groovyTemplates", "gsp"); + + outputStrings.put("post", Arrays.asList("

Second Post

", + "

28", + "2013

", + "Lorem ipsum dolor sit amet", + "
Published Posts
", + "blog/2012/first-post.html")); + outputStrings.put("page", Arrays.asList("

About

", + "All about stuff!", + "
Published Pages
", + "/projects.html")); + outputStrings.put("index", Arrays.asList("

First Post

", + "

Second Post

")); + outputStrings.put("feed", Arrays.asList("My corner of the Internet", + "Second Post", + "First Post")); + outputStrings.put("archive", Arrays.asList("Second Post", + "First Post")); + outputStrings.put("tags", Arrays.asList("Second Post", + "First Post")); + outputStrings.put("sitemap", Arrays.asList("blog/2013/second-post.html", + "blog/2012/first-post.html", + "papers/published-paper.html")); + } + +} diff --git a/src/test/java/org/jbake/app/template/JadeTemplateEngineRenderingTest.java b/src/test/java/org/jbake/app/template/JadeTemplateEngineRenderingTest.java new file mode 100644 index 000000000..0e561b04c --- /dev/null +++ b/src/test/java/org/jbake/app/template/JadeTemplateEngineRenderingTest.java @@ -0,0 +1,33 @@ +package org.jbake.app.template; + +import java.util.Arrays; + +public class JadeTemplateEngineRenderingTest extends AbstractTemplateEngineRenderingTest { + + public JadeTemplateEngineRenderingTest() { + super("jadeTemplates", "jade"); + + outputStrings.put("post", Arrays.asList("

Second Post

", + "

28", + "2013

", + "Lorem ipsum dolor sit amet", + "
Published Posts
", + "blog/2012/first-post.html")); + outputStrings.put("page", Arrays.asList("

About

", + "All about stuff!", + "
Published Pages
", + "/projects.html")); + outputStrings.put("index", Arrays.asList("First Post", + "Second Post")); + outputStrings.put("feed", Arrays.asList("My corner of the Internet", + "Second Post", + "First Post")); + outputStrings.put("archive", Arrays.asList("Second Post", + "First Post")); + outputStrings.put("tags", Arrays.asList("Second Post", + "First Post")); + outputStrings.put("sitemap", Arrays.asList("blog/2013/second-post.html", + "blog/2012/first-post.html", + "papers/published-paper.html")); + } +} diff --git a/src/test/java/org/jbake/app/template/PebbleTemplateEngineRenderingTest.java b/src/test/java/org/jbake/app/template/PebbleTemplateEngineRenderingTest.java new file mode 100644 index 000000000..97f173252 --- /dev/null +++ b/src/test/java/org/jbake/app/template/PebbleTemplateEngineRenderingTest.java @@ -0,0 +1,33 @@ +package org.jbake.app.template; + +import java.util.Arrays; + +public class PebbleTemplateEngineRenderingTest extends AbstractTemplateEngineRenderingTest { + + public PebbleTemplateEngineRenderingTest() { + super("pebbleTemplates", "pebble"); + + outputStrings.put("post", Arrays.asList("

Second Post

", + "

28", + "2013

", + "Lorem ipsum dolor sit amet", + "
Published Posts
", + "blog/2012/first-post.html")); + outputStrings.put("page", Arrays.asList("

About

", + "All about stuff!", + "
Published Pages
", + "/projects.html")); + outputStrings.put("index", Arrays.asList("

First Post

", + "

Second Post

")); + outputStrings.put("feed", Arrays.asList("My corner of the Internet", + "Second Post", + "First Post")); + outputStrings.put("archive", Arrays.asList("Second Post", + "First Post")); + outputStrings.put("tags", Arrays.asList("Second Post", + "First Post")); + outputStrings.put("sitemap", Arrays.asList("blog/2013/second-post.html", + "blog/2012/first-post.html", + "papers/published-paper.html")); + } +} diff --git a/src/test/java/org/jbake/app/template/ThymeleafTemplateEngineRenderingTest.java b/src/test/java/org/jbake/app/template/ThymeleafTemplateEngineRenderingTest.java new file mode 100644 index 000000000..626b1f34d --- /dev/null +++ b/src/test/java/org/jbake/app/template/ThymeleafTemplateEngineRenderingTest.java @@ -0,0 +1,60 @@ +/* + * The MIT License + * + * Copyright 2015 jdlee. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jbake.app.template; + +import java.util.Arrays; + +/** + * + * @author jdlee + */ +public class ThymeleafTemplateEngineRenderingTest extends AbstractTemplateEngineRenderingTest{ + + public ThymeleafTemplateEngineRenderingTest() { + super("thymeleafTemplates", "thyme"); + outputStrings.put("post", Arrays.asList("

Second Post

", + "

28", + "2013

", + "Lorem ipsum dolor sit amet", + "
Published Posts
", + "blog/2012/first-post.html")); + outputStrings.put("page", Arrays.asList("

About

", + "All about stuff!", + "
Published Pages
", + "/projects.html")); + outputStrings.put("index", Arrays.asList("

First Post

", + "

Second Post

")); + outputStrings.put("feed", Arrays.asList("My corner of the Internet", + "Second Post", + "First Post")); + outputStrings.put("archive", Arrays.asList("Second Post", + "First Post")); + outputStrings.put("tags", Arrays.asList("Second Post", + "First Post")); + outputStrings.put("sitemap", Arrays.asList("blog/2013/second-post.html", + "blog/2012/first-post.html", + "papers/published-paper.html")); + } + +} diff --git a/src/test/resources/freemarkerTemplates/archive.ftl b/src/test/resources/freemarkerTemplates/archive.ftl new file mode 100644 index 000000000..edb155c16 --- /dev/null +++ b/src/test/resources/freemarkerTemplates/archive.ftl @@ -0,0 +1,27 @@ +<#include "header.ftl"> + + <#include "menu.ftl"> + + + + + <#list published_posts as post> + <#if (last_month)??> + <#if post.date?string("MMMM yyyy") != last_month> + +

${post.date?string("MMMM yyyy")}

+
    + + <#else> +

    ${post.date?string("MMMM yyyy")}

    + + +<#include "footer.ftl"> \ No newline at end of file diff --git a/src/test/resources/templates/feed.ftl b/src/test/resources/freemarkerTemplates/feed.ftl similarity index 66% rename from src/test/resources/templates/feed.ftl rename to src/test/resources/freemarkerTemplates/feed.ftl index 5bba85667..3caedb9e3 100644 --- a/src/test/resources/templates/feed.ftl +++ b/src/test/resources/freemarkerTemplates/feed.ftl @@ -1,18 +1,18 @@ - JonathanBullock.com - http://jonathanbullock.com/ - + JBake + ${config.site_host} + My corner of the Internet en-gb ${published_date?string("EEE, d MMM yyyy HH:mm:ss Z")} ${published_date?string("EEE, d MMM yyyy HH:mm:ss Z")} - <#list posts as post> + <#list published_posts as post> - ${post.title} - http://jonathanbullock.com${post.uri} + <#escape x as x?xml>${post.title}</#escape> + ${config.site_host}/${post.uri} ${post.date?string("EEE, d MMM yyyy HH:mm:ss Z")} ${post.uri} diff --git a/src/test/resources/freemarkerTemplates/footer.ftl b/src/test/resources/freemarkerTemplates/footer.ftl new file mode 100644 index 000000000..ae4715170 --- /dev/null +++ b/src/test/resources/freemarkerTemplates/footer.ftl @@ -0,0 +1,19 @@ + +
    + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/freemarkerTemplates/header.ftl b/src/test/resources/freemarkerTemplates/header.ftl new file mode 100644 index 000000000..1ac9742c9 --- /dev/null +++ b/src/test/resources/freemarkerTemplates/header.ftl @@ -0,0 +1,35 @@ + + + + + <#if (content.title)??><#escape x as x?xml>${content.title}</#escape><#else>JBake</#if> + + + + + + + + + + + + + + + + + + + <#if content?? && content.og??> + + + + +
    + \ No newline at end of file diff --git a/src/test/resources/freemarkerTemplates/index.ftl b/src/test/resources/freemarkerTemplates/index.ftl new file mode 100644 index 000000000..e4e2cd519 --- /dev/null +++ b/src/test/resources/freemarkerTemplates/index.ftl @@ -0,0 +1,20 @@ +<#include "header.ftl"> + + <#include "menu.ftl"> + + + <#list posts as post> + <#if (post.status == "published")> +

    <#escape x as x?xml>${post.title}

    +

    ${post.date?string("dd MMMM yyyy")}

    +

    ${post.body}

    + + + +
    + +

    Older posts are available in the archive.

    + +<#include "footer.ftl"> \ No newline at end of file diff --git a/src/test/resources/freemarkerTemplates/menu.ftl b/src/test/resources/freemarkerTemplates/menu.ftl new file mode 100644 index 000000000..050523f16 --- /dev/null +++ b/src/test/resources/freemarkerTemplates/menu.ftl @@ -0,0 +1,34 @@ + + +
    \ No newline at end of file diff --git a/src/test/resources/freemarkerTemplates/page.ftl b/src/test/resources/freemarkerTemplates/page.ftl new file mode 100644 index 000000000..c797ad985 --- /dev/null +++ b/src/test/resources/freemarkerTemplates/page.ftl @@ -0,0 +1,15 @@ +<#include "header.ftl"> + + <#include "menu.ftl"> + + + +

    ${content.date?string("dd MMMM yyyy")}

    + +

    ${content.body}

    + +
    + +<#include "footer.ftl"> \ No newline at end of file diff --git a/src/test/resources/freemarkerTemplates/post.ftl b/src/test/resources/freemarkerTemplates/post.ftl new file mode 100644 index 000000000..3a8dd5339 --- /dev/null +++ b/src/test/resources/freemarkerTemplates/post.ftl @@ -0,0 +1,15 @@ +<#include "header.ftl"> + + <#include "menu.ftl"> + + + +

    ${content.date?string("dd MMMM yyyy")}

    + +

    ${content.body}

    + +
    + +<#include "footer.ftl"> \ No newline at end of file diff --git a/src/test/resources/templates/sitemap.ftl b/src/test/resources/freemarkerTemplates/sitemap.ftl similarity index 100% rename from src/test/resources/templates/sitemap.ftl rename to src/test/resources/freemarkerTemplates/sitemap.ftl diff --git a/src/test/resources/freemarkerTemplates/tags.ftl b/src/test/resources/freemarkerTemplates/tags.ftl new file mode 100644 index 000000000..c27384509 --- /dev/null +++ b/src/test/resources/freemarkerTemplates/tags.ftl @@ -0,0 +1,27 @@ +<#include "header.ftl"> + + <#include "menu.ftl"> + + + + + <#list tag_posts as post> + <#if (last_month)??> + <#if post.date?string("MMMM yyyy") != last_month> +
+

${post.date?string("MMMM yyyy")}

+
    + + <#else> +

    ${post.date?string("MMMM yyyy")}

    +
      + + +
    • ${post.date?string("dd")} - ${post.title}
    • + <#assign last_month = post.date?string("MMMM yyyy")> + +
    + +<#include "footer.ftl"> \ No newline at end of file diff --git a/src/test/resources/groovyMarkupTemplates/allcontent.tpl b/src/test/resources/groovyMarkupTemplates/allcontent.tpl new file mode 100644 index 000000000..907fcaa1a --- /dev/null +++ b/src/test/resources/groovyMarkupTemplates/allcontent.tpl @@ -0,0 +1,9 @@ +xmlDeclaration() +list{ + all_content.each { cntnt -> + content { + uri("${config.site_host}${cntnt.uri}") + date(${cntnt.date.format("yyyy-MM-dd")}) + } + } +} diff --git a/src/test/resources/groovyMarkupTemplates/archive.tpl b/src/test/resources/groovyMarkupTemplates/archive.tpl new file mode 100644 index 000000000..cf2f4b852 --- /dev/null +++ b/src/test/resources/groovyMarkupTemplates/archive.tpl @@ -0,0 +1,26 @@ +layout 'layout/main.tpl', + bodyContents: contents { + div(class:"row-fluid marketing"){ + div(class:"span12"){ + h2('Archive') + + def last_month + posts.each {post -> + if (last_month) { + if (post.date.format("MMMM yyyy") != last_month) { + h3("${post.date.format("MMMM yyyy")}") + } + } + else { + h3("${post.date.format("MMMM yyyy")}") + } + + h4 { + yield "${post.date.format("dd MMMM")} - " + a(href:"${post.uri}","${post.title}") + } + last_month = post.date.format("MMMM yyyy") + } + } + } + } \ No newline at end of file diff --git a/src/test/resources/groovyMarkupTemplates/feed.tpl b/src/test/resources/groovyMarkupTemplates/feed.tpl new file mode 100644 index 000000000..de1494133 --- /dev/null +++ b/src/test/resources/groovyMarkupTemplates/feed.tpl @@ -0,0 +1,22 @@ +xmlDeclaration() +rss(version:"2.0", 'xmlns:atom':'http://www.w3.org/2005/Atom') { + channel { + title('JonathanBullock.com') + link('http://jonathanbullock.com/') + atom:link(href:"http://jonathanbullock.com/feed.xml", rel:"self", type:"application/rss+xml") + description('My corner of the Internet') + language('en-gb') + pubDate("${published_date.format("EEE, d MMM yyyy HH:mm:ss Z")}") + lastBuildDate("${published_date.format("EEE, d MMM yyyy HH:mm:ss Z")}") + + posts.each { post -> + item{ + title("${post.title}") + link("http://jonathanbullock.com${post.uri}") + pubDate("${post.date.format("EEE, d MMM yyyy HH:mm:ss Z")}") + guid(isPermaLink:"false","${post.uri}") + description("${post.body}") + } + } + } +} diff --git a/src/test/resources/groovyMarkupTemplates/footer.tpl b/src/test/resources/groovyMarkupTemplates/footer.tpl new file mode 100644 index 000000000..ffd301b38 --- /dev/null +++ b/src/test/resources/groovyMarkupTemplates/footer.tpl @@ -0,0 +1,8 @@ +div(class:"footer"){ + p{ + yieldUnescaped '© Jonathan Bullock 2013 | Mixed with ' + a(href:"http://twitter.github.com/bootstrap/",'Bootstrap v2.3.1') + yieldUnescaped '| Baked with ' + a(href:"http://jbake.org","JBake ${version}") + } +} diff --git a/src/test/resources/groovyMarkupTemplates/header.tpl b/src/test/resources/groovyMarkupTemplates/header.tpl new file mode 100644 index 000000000..0ce16f508 --- /dev/null +++ b/src/test/resources/groovyMarkupTemplates/header.tpl @@ -0,0 +1,59 @@ +meta(charset:'utf-8') +title('Jonathan Bullock') +meta(name:"viewport", content:"width=device-width, initial-scale=1.0") +meta(name:"description", content:"") +meta(name:"author", content:"Jonathan Bullock") + +yieldUnescaped '' +link(href:"/css/bootstrap.min.css", rel:"stylesheet") +style(type:"text/css") { + yieldUnescaped """body { + padding-top: 20px; + padding-bottom: 40px; + } + + /* Custom container */ + .container-narrow { + margin: 0 auto; + max-width: 700px; + } + .container-narrow > hr { + margin: 30px 0; + } + + /* Main marketing message and sign up button */ + .jumbotron { + margin: 60px 0; + text-align: center; + } + .jumbotron h1 { + font-size: 72px; + line-height: 1; + } + .jumbotron .btn { + font-size: 21px; + padding: 14px 24px; + } + + /* Supporting marketing content */ + .marketing { + margin: 60px 0; + } + .marketing p + h4 { + margin-top: 28px; + }""" +} +link(href:"/css/bootstrap-responsive.min.css", rel:"stylesheet") + +yieldUnescaped '' +yieldUnescaped '' + +yieldUnescaped '' +yieldUnescaped '' diff --git a/src/test/resources/groovyMarkupTemplates/index.tpl b/src/test/resources/groovyMarkupTemplates/index.tpl new file mode 100644 index 000000000..d57bb41e5 --- /dev/null +++ b/src/test/resources/groovyMarkupTemplates/index.tpl @@ -0,0 +1,13 @@ +layout 'layout/main.tpl', + bodyContents: contents { + + div(class:"row-fluid marketing"){ + div(class:"span12"){ + posts[0..<2].each { post -> + h4 { a(href:"${post.uri}","${post.title}") } + p("${post.date.format("dd MMMM yyyy")} - ${post.body.substring(0, 150)}...") + } + a(href:"/archive.html",'Archive') + } + } + } \ No newline at end of file diff --git a/src/test/resources/groovyMarkupTemplates/layout/main.tpl b/src/test/resources/groovyMarkupTemplates/layout/main.tpl new file mode 100644 index 000000000..449a699d1 --- /dev/null +++ b/src/test/resources/groovyMarkupTemplates/layout/main.tpl @@ -0,0 +1,28 @@ +yieldUnescaped '' +html(lang:'en'){ + + head { + include template: "header.tpl" + } + + body { + div(class:"container-narrow"){ + + include template: 'menu.tpl' + + hr() + + bodyContents() + + hr() + + include template: 'footer.tpl' + + } + + script(src:"/js/jquery-1.9.1.min.js"){} + newLine() + script(src:"/js/bootstrap.min.js"){} + } +} +newLine() diff --git a/src/test/resources/groovyMarkupTemplates/menu.tpl b/src/test/resources/groovyMarkupTemplates/menu.tpl new file mode 100644 index 000000000..30837e9d6 --- /dev/null +++ b/src/test/resources/groovyMarkupTemplates/menu.tpl @@ -0,0 +1,9 @@ +div(class:"masthead"){ + ul(class:"nav nav-pills pull-right"){ + li { a(href:"/",'Home') } + li { a(href:"/about.html",'About') } + li { a(href:"/projects.html",'Projects') } + li { a(href:"/feed.xml",'Subscribe') } + } + h3(class:"muted",'Jonathan Bullock') +} \ No newline at end of file diff --git a/src/test/resources/groovyMarkupTemplates/page.tpl b/src/test/resources/groovyMarkupTemplates/page.tpl new file mode 100644 index 000000000..d01d7f057 --- /dev/null +++ b/src/test/resources/groovyMarkupTemplates/page.tpl @@ -0,0 +1,22 @@ +layout 'layout/main.tpl', + bodyContents: contents { + + div(class:"row-fluid marketing"){ + div(class:"span12"){ + h4("${content.title}") + p("${content.body}") + } + } + + hr() + + h5('Published Pages') + published_pages.each {page -> + a(href:"${config.site_host}${page.uri}","${page.title}") + } + + } + + + + diff --git a/src/test/resources/groovyMarkupTemplates/post.tpl b/src/test/resources/groovyMarkupTemplates/post.tpl new file mode 100644 index 000000000..fe3755855 --- /dev/null +++ b/src/test/resources/groovyMarkupTemplates/post.tpl @@ -0,0 +1,20 @@ +layout 'layout/main.tpl', + bodyContents: contents { + + div(class:"row-fluid marketing"){ + div(class:"span12"){ + h2("${content.title}") + p(class:"post-date", "${content.date.format("dd MMMM yyyy")}") + p("${content.body}") + } + } + + hr() + + h5('Published Posts') + + published_posts.each { post -> + a(href:"${config.site_host}${post.uri}","${post.title}") + } + + } \ No newline at end of file diff --git a/src/test/resources/groovyMarkupTemplates/sitemap.tpl b/src/test/resources/groovyMarkupTemplates/sitemap.tpl new file mode 100644 index 000000000..f44e48531 --- /dev/null +++ b/src/test/resources/groovyMarkupTemplates/sitemap.tpl @@ -0,0 +1,12 @@ +xmlDeclaration() +urlset( xmlns:"http://www.sitemaps.org/schemas/sitemap/0.9", + 'xmlns:xsi':"http://www.w3.org/2001/XMLSchema-instance", + 'xsi:schemaLocation':"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"){ + + published_content.each { content -> + url { + loc("${config.site_host}${content.uri}") + lastmod("${content.date.format("yyyy-MM-dd")}") + } + } +} \ No newline at end of file diff --git a/src/test/resources/groovyMarkupTemplates/tags.tpl b/src/test/resources/groovyMarkupTemplates/tags.tpl new file mode 100644 index 000000000..807b6a5db --- /dev/null +++ b/src/test/resources/groovyMarkupTemplates/tags.tpl @@ -0,0 +1,41 @@ +layout 'layout/main.tpl', + bodyContents: contents { + + div(class:"row-fluid marketing"){ + + div(class:"span12"){ + h1("Taglist") + div{ + alltags.sort().each { tag -> + span{ + a(href:"tags/${tag.replace(' ', '-')}.html", class:"label", "${tag}") + } + } + } + } + + div(class:"span12"){ + h2('Tags') + def last_month + + tag_posts.each { post -> + if (last_month) { + if (post.date.format("MMMM yyyy") != last_month) { + h3("${post.date.format("MMMM yyyy")}") + } + } + else { + h3("${post.date.format("MMMM yyyy")}") + } + + h4 { + yield "${post.date.format("dd MMMM")} - " + a(href:"${post.uri}","${post.title}") + } + last_month = post.date.format("MMMM yyyy") + } + } + } + + hr() + } \ No newline at end of file diff --git a/src/test/resources/groovyTemplates/allcontent.gsp b/src/test/resources/groovyTemplates/allcontent.gsp new file mode 100644 index 000000000..4762cbc9f --- /dev/null +++ b/src/test/resources/groovyTemplates/allcontent.gsp @@ -0,0 +1,9 @@ + + + <% all_content.each { content -> %> + + ${config.site_host}${content.uri} + ${content.date.format("yyyy-MM-dd")} + + <%}%> + diff --git a/src/test/resources/groovyTemplates/tags.gsp b/src/test/resources/groovyTemplates/tags.gsp index 29466cb2f..a04d9533b 100644 --- a/src/test/resources/groovyTemplates/tags.gsp +++ b/src/test/resources/groovyTemplates/tags.gsp @@ -9,10 +9,23 @@
    -->
    -
    + +
    +

    Taglist

    +
    + <% alltags.sort().each { tag -> %> + + + ${tag} + + <%}%> +
    +
    + +

    Tags

    <%def last_month=null;%> - <%posts.each {post ->%> + <%tag_posts.each {post ->%> <%if (last_month) {%> <%if (post.date.format("MMMM yyyy") != last_month) {%>

    ${post.date.format("MMMM yyyy")}

    diff --git a/src/test/resources/jadeTemplates/archive.jade b/src/test/resources/jadeTemplates/archive.jade new file mode 100644 index 000000000..95c404797 --- /dev/null +++ b/src/test/resources/jadeTemplates/archive.jade @@ -0,0 +1,15 @@ +extends layout.jade + +block content + .row-fluid.marketing + .span12 + h2 Archive + each post, index in posts + if last_month != formatter.format(post.date, 'MMMM yyyy') + h3 #{formatter.format(post.date, 'MMMM yyyy')} + h4 + | #{formatter.format(post.date, 'dd MMMM')} + |  -  + a(href='#{post.uri}') #{post.title} + - var last_month = formatter.format(post.date, 'MMMM yyyy') + hr diff --git a/src/test/resources/jadeTemplates/feed.jade b/src/test/resources/jadeTemplates/feed.jade new file mode 100644 index 000000000..125a1ab00 --- /dev/null +++ b/src/test/resources/jadeTemplates/feed.jade @@ -0,0 +1,17 @@ +rss(version='2.0', xmlns:atom='http://www.w3.org/2005/Atom') + channel + title JonathanBullock.com + link http://jonathanbullock.com/ + atom:link(href='http://jonathanbullock.com/feed.xml', rel='self', type='application/rss+xml') + description My corner of the Internet + language en-gb + pubdate #{formatter.format(published_date, 'EEE, d MMM yyyy HH:mm:ss Z')} + lastbuilddate #{formatter.format(published_date, 'EEE, d MMM yyyy HH:mm:ss Z')} + for post in posts + item + title #{post.title} + link http://jonathanbullock.com#{post.uri} + pubdate #{formatter.format(post.date, 'EEE, d MMM yyyy HH:mm:ss Z')} + guid(ispermalink='false') #{post.uri} + description + | #{formatter.escape(post.body)} diff --git a/src/test/resources/jadeTemplates/footer.jade b/src/test/resources/jadeTemplates/footer.jade new file mode 100644 index 000000000..44c3e1cea --- /dev/null +++ b/src/test/resources/jadeTemplates/footer.jade @@ -0,0 +1,11 @@ +.footer + p + | © Jonathan Bullock 2013 | Mixed with + a(href='http://twitter.github.com/bootstrap/') Bootstrap v2.3.1 + | | Baked with + a(href='http://jbake.org') JBake #{version} +// Le javascript +// ================================================== +// Placed at the end of the document so the pages load faster +script(src='/js/jquery-1.9.1.min.js') +script(src='/js/bootstrap.min.js') diff --git a/src/test/resources/jadeTemplates/header.jade b/src/test/resources/jadeTemplates/header.jade new file mode 100644 index 000000000..98aa8ba5c --- /dev/null +++ b/src/test/resources/jadeTemplates/header.jade @@ -0,0 +1,59 @@ +head + meta(charset='utf-8') + title Jonathan Bullock + meta(name='viewport', content='width=device-width, initial-scale=1.0') + meta(name='description', content='') + meta(name='author', content='Jonathan Bullock') + // Le styles + link(href='/css/bootstrap.min.css', rel='stylesheet') + style(type='text/css'). + body { + padding-top: 20px; + padding-bottom: 40px; + } + + /* Custom container */ + .container-narrow { + margin: 0 auto; + max-width: 700px; + } + + .container-narrow > hr { + margin: 30px 0; + } + + /* Main marketing message and sign up button */ + .jumbotron { + margin: 60px 0; + text-align: center; + } + + .jumbotron h1 { + font-size: 72px; + line-height: 1; + } + + .jumbotron .btn { + font-size: 21px; + padding: 14px 24px; + } + + /* Supporting marketing content */ + .marketing { + margin: 60px 0; + } + + .marketing p + h4 { + margin-top: 28px; + } + link(href='/css/bootstrap-responsive.min.css', rel='stylesheet') + // HTML5 shim, for IE6-8 support of HTML5 elements + //if lt IE 9 + script(src='/js/html5shiv.js') + // Fav and touch icons + // + + + + + diff --git a/src/test/resources/jadeTemplates/index.jade b/src/test/resources/jadeTemplates/index.jade new file mode 100644 index 000000000..7760b2d38 --- /dev/null +++ b/src/test/resources/jadeTemplates/index.jade @@ -0,0 +1,13 @@ +extends layout.jade + +block content + .row-fluid.marketing + .span12 + each post, index in posts + if index < 3 + h4 + a(href='#{post.uri}') #{post.title} + p #{formatter.format(post.date, 'dd MMMM yyyy')} - #{post.body.substring(0, 150)}... + + a(href='/archive.html') Archive + hr diff --git a/src/test/resources/jadeTemplates/layout.jade b/src/test/resources/jadeTemplates/layout.jade new file mode 100644 index 000000000..2e9942367 --- /dev/null +++ b/src/test/resources/jadeTemplates/layout.jade @@ -0,0 +1,20 @@ +html + include header + body + .container-narrow + .masthead + ul.nav.nav-pills.pull-right + li + a(href='/') Home + li + a(href='/about.html') About + li + a(href='/projects.html') Projects + li + a(href='/feed.xml') Subscribe + h3.muted Jonathan Bullock + hr + + block content + + include footer \ No newline at end of file diff --git a/src/test/resources/jadeTemplates/page.jade b/src/test/resources/jadeTemplates/page.jade new file mode 100644 index 000000000..6a7e93ed6 --- /dev/null +++ b/src/test/resources/jadeTemplates/page.jade @@ -0,0 +1,12 @@ +extends layout.jade + +block content + .row-fluid.marketing + .span12 + h4 #{content.title} + p #{content.body} + hr + h5 Published Pages + + for page in published_pages + a(href="#{config.site_host}/#{page.uri}") #{page.title} diff --git a/src/test/resources/jadeTemplates/post.jade b/src/test/resources/jadeTemplates/post.jade new file mode 100644 index 000000000..ceba3d5d0 --- /dev/null +++ b/src/test/resources/jadeTemplates/post.jade @@ -0,0 +1,13 @@ +extends layout.jade + +block content + .row-fluid.marketing + .span12 + h2 #{content.title} + p.post-date #{formatter.format(content.date,'dd MMMM yyyy')} + p #{content.body} + hr + h5 Published Posts + + for post in published_posts + a(href="#{config.site_host}/#{post.uri}") #{post.title} diff --git a/src/test/resources/jadeTemplates/sitemap.jade b/src/test/resources/jadeTemplates/sitemap.jade new file mode 100644 index 000000000..c7a178ec1 --- /dev/null +++ b/src/test/resources/jadeTemplates/sitemap.jade @@ -0,0 +1,5 @@ +urlset(xmlns='http://www.sitemaps.org/schemas/sitemap/0.9', xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance', xsi:schemalocation='http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd') + for content in published_content + url + loc #{config.site_host}#{content.uri} + lastmod #{formatter.format(content.date, 'yyyy-MM-dd')} diff --git a/src/test/resources/jadeTemplates/tags.jade b/src/test/resources/jadeTemplates/tags.jade new file mode 100644 index 000000000..d5b257245 --- /dev/null +++ b/src/test/resources/jadeTemplates/tags.jade @@ -0,0 +1,15 @@ +extends layout.jade + +block content + .row-fluid.marketing + .span12 + h2 Tags + each post, index in posts + if last_month != formatter.format(post.date, 'MMMM yyyy') + h3 #{formatter.format(post.date, 'MMMM yyyy')} + h4 + | #{formatter.format(post.date, 'dd MMMM')} + |  -  + a(href='#{post.uri}') #{post.title} + - var last_month = formatter.format(post.date, 'MMMM yyyy') + hr diff --git a/src/test/resources/pebbleTemplates/archive.pebble b/src/test/resources/pebbleTemplates/archive.pebble new file mode 100644 index 000000000..2ff526bcf --- /dev/null +++ b/src/test/resources/pebbleTemplates/archive.pebble @@ -0,0 +1,29 @@ +{% extends 'base.pebble' %} + +{% block header %}Archive{% endblock %} + +{% block primary %} + +
    +
    + {% set last_month = null %} + {% for post in posts %} + {% if (last_month is not null) %} + {% if post.date | date("MMMM yyyy") != last_month %} +
+

{{ post.date | date("MMMM yyyy") }}

+
    + {% endif %} + {% else %} +

    {{ post.date | date("MMMM yyyy") }}

    +
      + {% endif %} + +
    • {{ post.date | date("dd") }} - {{ post.title }}
    • + + {% set last_month = post.date | date("MMMM yyyy") %} + {% endfor %} + + + +{% endblock %} diff --git a/src/test/resources/pebbleTemplates/base.pebble b/src/test/resources/pebbleTemplates/base.pebble new file mode 100644 index 000000000..cbd19fc31 --- /dev/null +++ b/src/test/resources/pebbleTemplates/base.pebble @@ -0,0 +1,54 @@ + + + + + {% block title %}JBake{% endblock %} + + + + + + + + + + + + + + + + + + + +
      + + {% include 'menu.pebble' %} + +
      + + + {% block primary %}{% endblock %} +
      + +
      + +
      + + {% include 'footer.pebble' %} + + + + + + + + diff --git a/src/test/resources/pebbleTemplates/feed.pebble b/src/test/resources/pebbleTemplates/feed.pebble new file mode 100644 index 000000000..e0e5d4eea --- /dev/null +++ b/src/test/resources/pebbleTemplates/feed.pebble @@ -0,0 +1,25 @@ + + + + JBake + {{ config.site_host }} + + My corner of the Internet + en-gb + {{ published_date | date("EEE, d MMM yyyy HH:mm:ss Z") }} + {{ published_date | date("EEE, d MMM yyyy HH:mm:ss Z") }} + + {% for post in posts %} + + {{ post.title }} + {{ config.site_host }}/{{ post.uri }} + {{ post.date | date("EEE, d MMM yyyy HH:mm:ss Z") }} + {{ post.uri }} + + {{ post.body }} + + + {% endfor %} + + + diff --git a/src/test/resources/pebbleTemplates/footer.pebble b/src/test/resources/pebbleTemplates/footer.pebble new file mode 100644 index 000000000..85050b8b9 --- /dev/null +++ b/src/test/resources/pebbleTemplates/footer.pebble @@ -0,0 +1,6 @@ + + diff --git a/src/test/resources/templates/header.ftl b/src/test/resources/pebbleTemplates/header.pebble similarity index 93% rename from src/test/resources/templates/header.ftl rename to src/test/resources/pebbleTemplates/header.pebble index 1b9210f75..31a2d0dc4 100644 --- a/src/test/resources/templates/header.ftl +++ b/src/test/resources/pebbleTemplates/header.pebble @@ -47,9 +47,9 @@ } - <#if content?? && content.og??> - - + {% if content is not empty and content.og is not null %} + + {% endif %} + diff --git a/src/test/resources/pebbleTemplates/page.pebble b/src/test/resources/pebbleTemplates/page.pebble new file mode 100644 index 000000000..04ea3a989 --- /dev/null +++ b/src/test/resources/pebbleTemplates/page.pebble @@ -0,0 +1,18 @@ +{% extends 'base.pebble' %} + +{% block title %}{{ content.title }}{% endblock %} +{% block header %}{{ content.title }}{% endblock %} + +{% block primary %} + +

      {{ content.date | date("dd MMMM yyyy") }}

      + +

      {{ content.body }}

      + +
      + +
      Published Pages
      + {% for page in published_pages %} + {{ page.title }} + {% endfor %} +{% endblock %} diff --git a/src/test/resources/pebbleTemplates/post.pebble b/src/test/resources/pebbleTemplates/post.pebble new file mode 100644 index 000000000..bffb821fb --- /dev/null +++ b/src/test/resources/pebbleTemplates/post.pebble @@ -0,0 +1,18 @@ +{% extends 'base.pebble' %} + +{% block title %}{{ content.title }}{% endblock %} +{% block header %}{{ content.title }}{% endblock %} + +{% block primary %} + + + +

      {{ content.body }}

      + +
      + +
      Published Posts
      + {% for post in published_posts %} + {{ post.title }} + {% endfor %} +{% endblock %} diff --git a/src/test/resources/pebbleTemplates/sitemap.pebble b/src/test/resources/pebbleTemplates/sitemap.pebble new file mode 100644 index 000000000..f15996c21 --- /dev/null +++ b/src/test/resources/pebbleTemplates/sitemap.pebble @@ -0,0 +1,9 @@ + + +{% for content in published_content %} + + {{ config.site_host }}{{ content.uri }} + {{ content.date | date("yyyy-MM-dd") }} + +{% endfor %} + \ No newline at end of file diff --git a/src/test/resources/pebbleTemplates/tags.pebble b/src/test/resources/pebbleTemplates/tags.pebble new file mode 100644 index 000000000..028f679fd --- /dev/null +++ b/src/test/resources/pebbleTemplates/tags.pebble @@ -0,0 +1,26 @@ +{% extends 'base.pebble' %} + +{% block header %}{{ tag }}{% endblock %} + +{% block primary %} + + {% set last_month = null %} + {% for post in posts %} + {% if last_month is not null %} + {% if post.date | date("MMMM yyyy") != last_month %} +
    +

    {{ post.date | date("MMMM yyyy") }}

    +
      + {% endif %} + {% else %} +

      {{ post.date | date("MMMM yyyy") }}

      +
        + {% endif %} + +
      • {{ post.date | date("dd MMMM") }} - {{ post.title }}
      • + {% set last_month = post.date | date("MMMM yyyy") %} + + {% endfor %} +
      + +{% endblock %} diff --git a/src/test/resources/templates/allcontent.ftl b/src/test/resources/templates/allcontent.ftl deleted file mode 100644 index 6e5b8ad29..000000000 --- a/src/test/resources/templates/allcontent.ftl +++ /dev/null @@ -1,7 +0,0 @@ - -<#list all_content as content> - - ${config.site_host}${content.uri} - ${content.date?string("yyyy-MM-dd")} - - diff --git a/src/test/resources/templates/archive.ftl b/src/test/resources/templates/archive.ftl deleted file mode 100644 index 90f78a5e7..000000000 --- a/src/test/resources/templates/archive.ftl +++ /dev/null @@ -1,31 +0,0 @@ -<#include "header.ftl"> - - - -
      -
      -

      Archive

      - <#list posts as post> - <#if (last_month)??> - <#if post.date?string("MMMM yyyy") != last_month> -

      ${post.date?string("MMMM yyyy")}

      - - <#else> -

      ${post.date?string("MMMM yyyy")}

      - - -

      ${post.date?string("dd MMMM")} - ${post.title}

      - <#assign last_month = post.date?string("MMMM yyyy")> - -
      -
      - -
      - -<#include "footer.ftl"> \ No newline at end of file diff --git a/src/test/resources/templates/footer.ftl b/src/test/resources/templates/footer.ftl deleted file mode 100644 index f39c5e9bb..000000000 --- a/src/test/resources/templates/footer.ftl +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/test/resources/templates/index.ftl b/src/test/resources/templates/index.ftl deleted file mode 100644 index f390ded95..000000000 --- a/src/test/resources/templates/index.ftl +++ /dev/null @@ -1,24 +0,0 @@ -<#include "header.ftl"> - - - -
      -
      - <#list posts as post> -

      ${post.title}

      -

      ${post.date?string("dd MMMM yyyy")} - ${post.body?substring(0, 150)}...

      - <#if post_index = 2><#break> - - Archive -
      -
      - -
      - -<#include "footer.ftl"> \ No newline at end of file diff --git a/src/test/resources/templates/page.ftl b/src/test/resources/templates/page.ftl deleted file mode 100644 index 43e4ccf37..000000000 --- a/src/test/resources/templates/page.ftl +++ /dev/null @@ -1,18 +0,0 @@ -<#include "header.ftl"> - -
      -
      -

      ${content.title}

      -

      ${content.body}

      -
      - -
      - -
      - -
      Published Pages
      - <#list published_pages as page> - ${page.title} - - -<#include "footer.ftl"> \ No newline at end of file diff --git a/src/test/resources/templates/post.ftl b/src/test/resources/templates/post.ftl deleted file mode 100644 index 6b49a16b1..000000000 --- a/src/test/resources/templates/post.ftl +++ /dev/null @@ -1,18 +0,0 @@ -<#include "header.ftl"> - -
      -
      -

      ${content.title}

      - -

      ${content.body}

      -
      -
      - -
      - -
      Published Posts
      - <#list published_posts as post> - ${post.title} - - -<#include "footer.ftl"> diff --git a/src/test/resources/templates/tags.ftl b/src/test/resources/templates/tags.ftl deleted file mode 100644 index 0b7305992..000000000 --- a/src/test/resources/templates/tags.ftl +++ /dev/null @@ -1,31 +0,0 @@ -<#include "header.ftl"> - - - -
      -
      -

      Tags

      - <#list posts as post> - <#if (last_month)??> - <#if post.date?string("MMMM yyyy") != last_month> -

      ${post.date?string("MMMM yyyy")}

      - - <#else> -

      ${post.date?string("MMMM yyyy")}

      - - -

      ${post.date?string("dd MMMM")} - ${post.title}

      - <#assign last_month = post.date?string("MMMM yyyy")> - -
      -
      - -
      - -<#include "footer.ftl"> \ No newline at end of file diff --git a/src/test/resources/thymeleafTemplates/allcontent.thyme b/src/test/resources/thymeleafTemplates/allcontent.thyme new file mode 100644 index 000000000..97e258efb --- /dev/null +++ b/src/test/resources/thymeleafTemplates/allcontent.thyme @@ -0,0 +1,7 @@ + + + + /blog/content.html + + + diff --git a/src/test/resources/thymeleafTemplates/tags.thyme b/src/test/resources/thymeleafTemplates/tags.thyme index 52dc573d5..322025d8f 100644 --- a/src/test/resources/thymeleafTemplates/tags.thyme +++ b/src/test/resources/thymeleafTemplates/tags.thyme @@ -7,9 +7,19 @@
      + +
      +

      Taglist

      +
      + + blog + +
      +
      +

      Tags

      -
      +

      June 2014

      - Post title