diff --git a/src/main/java/org/jbake/app/ConfigUtil.java b/src/main/java/org/jbake/app/ConfigUtil.java index 63b9f986e..7884977cd 100644 --- a/src/main/java/org/jbake/app/ConfigUtil.java +++ b/src/main/java/org/jbake/app/ConfigUtil.java @@ -157,6 +157,11 @@ public interface Keys { */ String RENDER_TAGS = "render.tags"; + /** + * Flag indicating if category files should be generated + */ + String RENDER_CATEGORIES = "render.categories"; + /** * Port used when running Jetty server */ @@ -211,6 +216,26 @@ public interface Keys { * The configured base URI for the hosted content */ String SITE_HOST = "site.host"; + + /** + * Should Category generation and usage be enabled? Default is true. Posts without categories will be marked as 'Uncategorized'. Default category can be changed with {#link #CATEGORY_DEFAULT}. + */ + String CATEGORIES_ENABLE = "categories.enable"; + + /** + * Default Category for posts when {@link CATEGORIES_ENABLE} is {@code true} and no category is specified for post. + */ + String CATEGORY_DEFAULT = "category.default"; + + /** + * Tags output path, used only when {@link #RENDER_CATEGORIES} is true + */ + String CATEGORY_PATH = "categories.path"; + + /** + * Should Categories be sanitized for space? + */ + String CATEGORY_SANITIZE = "categories.sanitize"; } 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 9ccd9fec5..201112838 100644 --- a/src/main/java/org/jbake/app/ContentStore.java +++ b/src/main/java/org/jbake/app/ContentStore.java @@ -23,11 +23,20 @@ */ package org.jbake.app; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jbake.model.DocumentAttributes; +import org.jbake.model.DocumentTypes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.orientechnologies.orient.core.Orient; import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal; -import com.orientechnologies.orient.core.db.OPartitionedDatabasePool; import com.orientechnologies.orient.core.db.OPartitionedDatabasePoolFactory; -import com.orientechnologies.orient.core.db.document.ODatabaseDocumentPool; import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx; import com.orientechnologies.orient.core.metadata.schema.OClass; import com.orientechnologies.orient.core.metadata.schema.OSchema; @@ -35,16 +44,6 @@ 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.DocumentAttributes; -import org.jbake.model.DocumentTypes; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; /** * @author jdlee @@ -145,6 +144,10 @@ public DocumentList getPublishedPostsByTag(String tag) { return query("select * from post where status='published' and ? in tags order by date desc", tag); } + public DocumentList getPublishedPostsByCategories(String category) { + return query("select * from post where status='published' and ? in categories order by date desc", category); + } + public DocumentList getPublishedDocumentsByTag(String tag) { final DocumentList documents = new DocumentList(); for (final String docType : DocumentTypes.getDocumentTypes()) { @@ -153,6 +156,15 @@ public DocumentList getPublishedDocumentsByTag(String tag) { } return documents; } + + public DocumentList getPublishedDocumentsByCategory(String category) { + final DocumentList documents = new DocumentList(); + for (final String docType : DocumentTypes.getDocumentTypes()) { + DocumentList documentsByTag = query("select * from " + docType + " where status='published' and ? in categories order by date desc", category); + documents.addAll(documentsByTag); + } + return documents; + } public DocumentList getPublishedPages() { return getPublishedContent("page"); @@ -174,6 +186,10 @@ public DocumentList getAllContent(String docType) { return query(query); } + public DocumentList getAllCategoriesFromPublishedPosts() { + return query("select categories from post where status='published'"); + } + public DocumentList getAllTagsFromPublishedPosts() { return query("select tags from post where status='published'"); } @@ -242,6 +258,16 @@ public Set getAllTags() { return result; } + public Set getCategories() { + DocumentList docs = this.getAllCategoriesFromPublishedPosts(); + Set result = new HashSet(); + for (Map document : docs) { + String[] categories = DBUtil.toStringArray(document.get(Crawler.Attributes.CATEGORIES)); + Collections.addAll(result, categories); + } + return result; + } + private void createDocType(final OSchema schema, final String doctype) { logger.debug("Create document class '{}'", doctype ); diff --git a/src/main/java/org/jbake/app/Crawler.java b/src/main/java/org/jbake/app/Crawler.java index bdf68cb91..4077ba02e 100644 --- a/src/main/java/org/jbake/app/Crawler.java +++ b/src/main/java/org/jbake/app/Crawler.java @@ -50,6 +50,8 @@ interface Status { String ALLTAGS = "alltags"; String PUBLISHED_DATE = "published_date"; String BODY = "body"; + String CATEGORIES = "categories"; + String CATEGORY = "category"; } private static final Logger LOGGER = LoggerFactory.getLogger(Crawler.class); diff --git a/src/main/java/org/jbake/app/Renderer.java b/src/main/java/org/jbake/app/Renderer.java index 633d2c05e..54f5b6860 100644 --- a/src/main/java/org/jbake/app/Renderer.java +++ b/src/main/java/org/jbake/app/Renderer.java @@ -364,7 +364,85 @@ public int renderTags(String tagPath) throws Exception { return renderedCount; } } + + /** + * Render tag files using the supplied content. + * + * @param categories The content to renderDocument + * @param categoriesPath The output path + * @throws Exception + */ + public int renderCategories(String categoriesPath) throws Exception { + int renderedCount = 0; + final List errors = new LinkedList(); + for (String category : db.getCategories()) { + try { + Map model = new HashMap(); + model.put("renderer", renderingEngine); + model.put(Attributes.CATEGORY, category); + Map map = buildSimpleModel(Attributes.CATEGORY); + map.put(Attributes.ROOTPATH, "../../"); + model.put("content", map); + + String pathCategory = toSanitizedUriName(category); + String uri = File.separator + categoriesPath + File.separator + pathCategory; + uri = getValidExtensionUri(uri,config); + File path = new File(destination.getPath() + uri); + + render(new ModelRenderingConfig(path, Attributes.CATEGORY, model, findTemplateName(Attributes.CATEGORY))); + + renderedCount++; + } catch (Exception e) { + errors.add(e.getCause().getMessage()); + } + } + + // Add an index file at root folder of categories. + // This will prevent directory listing and also provide an option to display all categories page. + Map model = new HashMap(); + model.put("renderer", renderingEngine); + Map map = buildSimpleModel(Attributes.CATEGORIES); + map.put(Attributes.ROOTPATH, "../"); + model.put("content", map); + + File path = new File(destination.getPath() + File.separator + categoriesPath + File.separator + "index" + config.getString(Keys.OUTPUT_EXTENSION)); + render(new ModelRenderingConfig(path, Attributes.CATEGORIES, model, findTemplateName(Attributes.CATEGORIES))); + renderedCount++; + + if (!errors.isEmpty()) { + StringBuilder sb = new StringBuilder(); + sb.append("Failed to render Categories. Cause(s):"); + for(String error: errors) { + sb.append("\n" + error); + } + throw new Exception(sb.toString()); + } else { + return renderedCount; + } + } + + public static String toSanitizedUriName(String name){ + return name.trim().replace(" ", "-").toLowerCase(); + } + + /** + * If uri.noExtension is set to true then generate extension less uri. This method is to be used where uri.noExtension.prefix doesn't matter like categories, tags etc. + * @param uri + * @param config + * @return + */ + public static String getValidExtensionUri(String uri, CompositeConfiguration config){ + boolean noExtensionUri = config.getBoolean(Keys.URI_NO_EXTENSION); + + if (noExtensionUri) { + uri = uri + "/index" + config.getString(Keys.OUTPUT_EXTENSION); + } else { + uri = uri + config.getString(Keys.OUTPUT_EXTENSION); + } + return uri; + } + /** * Builds simple map of values, which are exposed when rendering index/archive/sitemap/feed/tags. * diff --git a/src/main/java/org/jbake/parser/MarkupEngine.java b/src/main/java/org/jbake/parser/MarkupEngine.java index 803ca4023..afa9b54ea 100644 --- a/src/main/java/org/jbake/parser/MarkupEngine.java +++ b/src/main/java/org/jbake/parser/MarkupEngine.java @@ -15,6 +15,7 @@ import org.apache.commons.configuration.Configuration; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; import org.jbake.app.ConfigUtil.Keys; import org.jbake.app.Crawler; import org.json.simple.JSONValue; @@ -140,6 +141,31 @@ public Map parse(Configuration config, File file, String content content.put(Crawler.Attributes.TAGS, tags); } + // If categories are not disabled then add a default category + if(config.getBoolean(Keys.CATEGORIES_ENABLE)){ + if (content.get(Crawler.Attributes.CATEGORIES) != null) { + String[] categories = (String[]) content.get(Crawler.Attributes.CATEGORIES); + for( int i=0; i contents, final Ma } } else if (key.equalsIgnoreCase(Crawler.Attributes.TAGS)) { content.put(key, getTags(value)); - } else if (isJson(value)) { + } else if (key.equalsIgnoreCase(Crawler.Attributes.CATEGORIES)){ + List categories = new ArrayList(); + for (String category : parts[1].split(",")){ + categories.add(category.trim()); + } + content.put(parts[0], categories.toArray(new String[0])); + } else if (isJson(value)) { content.put(key, JSONValue.parse(value)); } else { content.put(key, value); diff --git a/src/main/java/org/jbake/render/CategoriesRenderer.java b/src/main/java/org/jbake/render/CategoriesRenderer.java new file mode 100644 index 000000000..cd6a34f12 --- /dev/null +++ b/src/main/java/org/jbake/render/CategoriesRenderer.java @@ -0,0 +1,33 @@ +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; + +/** + * + * This class renders Post Categories. + * + * @author Manik Magar + * + */ +public class CategoriesRenderer implements RenderingTool { + + @Override + public int render(Renderer renderer, ContentStore db, File destination, File templatesPath, CompositeConfiguration config) throws RenderingException { + if (config.getBoolean(Keys.RENDER_CATEGORIES)) { + try { + return renderer.renderCategories(config.getString(Keys.CATEGORY_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/model/AllCategoriesExtractor.java b/src/main/java/org/jbake/template/model/AllCategoriesExtractor.java new file mode 100644 index 000000000..a723fb0c7 --- /dev/null +++ b/src/main/java/org/jbake/template/model/AllCategoriesExtractor.java @@ -0,0 +1,44 @@ +package org.jbake.template.model; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import org.jbake.app.ConfigUtil.Keys; +import org.jbake.app.ContentStore; +import org.jbake.app.DocumentList; +import org.jbake.app.Renderer; +import org.jbake.template.ModelExtractor; + + +/** + * + * This extractor model will list of categories from all published content. + * + * @author Manik Magar + * + */ +public class AllCategoriesExtractor implements ModelExtractor { + + @Override + public DocumentList get(ContentStore db, Map model, String key) { + DocumentList dl = new DocumentList(); + Map config = (Map) model.get("config"); + + String categoryPath = config.get(Keys.CATEGORY_PATH.replace(".", "_")).toString(); + + for (String category : db.getCategories()){ + Map newCategory = new HashMap(); + newCategory.put("name",category); + + String uri = categoryPath + File.separator + Renderer.toSanitizedUriName(category) + config.get(Keys.OUTPUT_EXTENSION.replace(".", "_")).toString(); + + newCategory.put("uri", uri); + newCategory.put("posts", db.getPublishedPostsByCategories(category)); + newCategory.put("documents", db.getPublishedDocumentsByCategory(category)); + dl.push(newCategory); + } + return dl; + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/model/CategoryDocumentsExtractor.java b/src/main/java/org/jbake/template/model/CategoryDocumentsExtractor.java new file mode 100644 index 000000000..2406a7772 --- /dev/null +++ b/src/main/java/org/jbake/template/model/CategoryDocumentsExtractor.java @@ -0,0 +1,23 @@ +package org.jbake.template.model; + +import java.util.Map; + +import org.jbake.app.ContentStore; +import org.jbake.app.Crawler; +import org.jbake.app.DocumentList; +import org.jbake.template.ModelExtractor; + + +public class CategoryDocumentsExtractor implements ModelExtractor { + + @Override + public DocumentList get(ContentStore db, Map model, String key) { + String category = null; + if (model.get(Crawler.Attributes.CATEGORY) != null) { + category = model.get(Crawler.Attributes.CATEGORY).toString(); + } + DocumentList query = db.getPublishedDocumentsByCategory(category); + return query; + } + +} \ No newline at end of file diff --git a/src/main/java/org/jbake/template/model/CategoryPostsExtractor.java b/src/main/java/org/jbake/template/model/CategoryPostsExtractor.java new file mode 100644 index 000000000..ca2fa6a0a --- /dev/null +++ b/src/main/java/org/jbake/template/model/CategoryPostsExtractor.java @@ -0,0 +1,23 @@ +package org.jbake.template.model; + +import java.util.Map; + +import org.jbake.app.ContentStore; +import org.jbake.app.Crawler; +import org.jbake.app.DocumentList; +import org.jbake.template.ModelExtractor; + + +public class CategoryPostsExtractor implements ModelExtractor { + + @Override + public DocumentList get(ContentStore db, Map model, String key) { + String category = null; + if (model.get(Crawler.Attributes.CATEGORY) != null) { + category = model.get(Crawler.Attributes.CATEGORY).toString(); + } + DocumentList query = db.getPublishedPostsByCategories(category); + return query; + } + +} \ No newline at end of file diff --git a/src/main/resources/META-INF/org.jbake.template.ModelExtractors.properties b/src/main/resources/META-INF/org.jbake.template.ModelExtractors.properties index 5a2f1dfde..3f997b4df 100644 --- a/src/main/resources/META-INF/org.jbake.template.ModelExtractors.properties +++ b/src/main/resources/META-INF/org.jbake.template.ModelExtractors.properties @@ -8,3 +8,6 @@ org.jbake.template.model.PublishedDateExtractor=published_date org.jbake.template.model.DBExtractor=db org.jbake.template.model.TagPostsExtractor=tag_posts org.jbake.template.model.TaggedDocumentsExtractor=tagged_documents +org.jbake.template.model.CategoryPostsExtractor=category_posts +org.jbake.template.model.CategoryDocumentsExtractor=category_documents +org.jbake.template.model.AllCategoriesExtractor=categories diff --git a/src/main/resources/META-INF/services/org.jbake.render.RenderingTool b/src/main/resources/META-INF/services/org.jbake.render.RenderingTool index ff03c55c7..adf4c2064 100644 --- a/src/main/resources/META-INF/services/org.jbake.render.RenderingTool +++ b/src/main/resources/META-INF/services/org.jbake.render.RenderingTool @@ -3,4 +3,5 @@ org.jbake.render.DocumentsRenderer org.jbake.render.FeedRenderer org.jbake.render.IndexRenderer org.jbake.render.SitemapRenderer -org.jbake.render.TagsRenderer \ No newline at end of file +org.jbake.render.TagsRenderer +org.jbake.render.CategoriesRenderer \ No newline at end of file diff --git a/src/main/resources/default.properties b/src/main/resources/default.properties index 97d90b3f2..5eb0c6adf 100644 --- a/src/main/resources/default.properties +++ b/src/main/resources/default.properties @@ -20,6 +20,10 @@ template.sitemap.file=sitemap.ftl template.post.file=post.ftl # filename of page template file template.page.file=page.ftl +# filename of page template file +template.category.file=category.ftl +# file name of page template for {site.url}/categories +template.categories.file=categories-index.ftl # folder that contains all content files content.folder=content # folder that contains all asset files @@ -90,4 +94,15 @@ db.path=cache # enable extension-less URI option? uri.noExtension=false # Set to a prefix path (starting with a slash) for which to generate extension-less URI's (i.e. a folder with index.html in) -uri.noExtension.prefix= \ No newline at end of file +uri.noExtension.prefix= +#Enable usage of categories. If disabled then nothing related to categories is processed or generated. +categories.enable=true +# Should categories pages be genetated +render.categories=true +#What should be the default category when no categories are specified for post and categories are enabled. +category.default=Uncategorized +# where to generate category pages +categories.path=categories +# Should spaces be replaced with hyphens +categories.sanitize=false + diff --git a/src/test/java/org/jbake/app/ParserTest.java b/src/test/java/org/jbake/app/ParserTest.java index dd38333c9..784402b27 100644 --- a/src/test/java/org/jbake/app/ParserTest.java +++ b/src/test/java/org/jbake/app/ParserTest.java @@ -2,14 +2,21 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.io.BufferedWriter; import java.io.File; +import java.io.FileWriter; +import java.io.IOException; import java.io.PrintWriter; +import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.Map; import org.apache.commons.configuration.CompositeConfiguration; import org.apache.commons.configuration.ConfigurationException; +import org.assertj.core.util.Arrays; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -125,6 +132,33 @@ public void createSampleFile() throws Exception { out.close(); } + public Map getCommonTestPostData(){ + Map data = new HashMap(); + data.put("title", "This is a test post"); + data.put("type", "post"); + data.put("date", new SimpleDateFormat("yyyy-MM-dd").format(new Date())); + data.put("tags", "tagA,tagB"); + data.put("status", "published"); + data.put("body", "This content is for body of the post."); + return data; + } + public String convertMapToStringHeader(Map data){ + if (data == null) return null; + StringBuffer header = new StringBuffer(); + Map lockedMap = Collections.unmodifiableMap(data); + for (String key : lockedMap.keySet()) { + if(key != "body") + header.append(key).append("=").append(lockedMap.get(key)).append("\n"); + } + if(!lockedMap.isEmpty()){ + header.append("~~~~~~\n"); + } + if(lockedMap.containsKey("body")){ + header.append(lockedMap.get("body")); + } + return header.toString(); + } + @Test public void parseValidHTMLFile() { Map map = parser.processFile(validHTMLFile); @@ -217,4 +251,81 @@ public void parseValidAsciiDocFileWithoutJBakeMetaDataUsingDefaultTypeAndStatus( assertThat(map.get("body").toString()) .contains("

JBake now supports AsciiDoc documents without JBake meta data.

"); } + + @Test + public void parseCategoriesCheckDefaultCategory() throws IOException{ + File tempHtml = folder.newFile("test.html"); + PrintWriter out = new PrintWriter(tempHtml); + out.print(convertMapToStringHeader(getCommonTestPostData())); + out.close(); + Map map = parser.processFile(tempHtml); + assertThat(map.get("categories")) + .isNotNull() + .isInstanceOf(String[].class) + .isEqualTo(Arrays.array("Uncategorized")); + + } + + @Test + public void parseCategoriesCheckNoDefaultCategory() throws IOException{ + File tempHtml = folder.newFile("test.html"); + PrintWriter out = new PrintWriter(tempHtml); + out.print(convertMapToStringHeader(getCommonTestPostData())); + out.close(); + config.setProperty("categories.enable", false); + parser = new Parser(config,rootPath.getPath()); + Map map = parser.processFile(tempHtml); + assertThat(map.get("categories")) + .isNull(); + } + + @Test + public void parseCategoriesCheckSpecificDefaultCategoryWithSanitize() throws IOException{ + File tempHtml = folder.newFile("test.html"); + BufferedWriter out = new BufferedWriter(new FileWriter(tempHtml)); + out.write(convertMapToStringHeader(getCommonTestPostData())); + out.flush(); + out.close(); + config.setProperty("category.default", "Java Collections"); + config.setProperty("categories.sanitize", true); + parser = new Parser(config,rootPath.getPath()); + Map map = parser.processFile(tempHtml); + assertThat(map.get("categories")) + .isNotNull() + .isInstanceOf(String[].class) + .isEqualTo(Arrays.array("Java-Collections")); + + } + + @Test + public void parseCategoriesCheckMultipleCategories() throws IOException{ + File tempHtml = folder.newFile("test.html"); + PrintWriter out = new PrintWriter(tempHtml); + Map data = getCommonTestPostData(); + data.put("categories", "Java, J2EE, Spring Framework"); + out.print(convertMapToStringHeader(data)); + out.close(); + parser = new Parser(config,rootPath.getPath()); + Map map = parser.processFile(tempHtml); + assertThat(map.get("categories")) + .isNotNull() + .isInstanceOf(String[].class) + .isEqualTo(Arrays.array("Java","J2EE","Spring Framework")); + } + @Test + public void parseCategoriesCheckMultipleCategoriesWithSanitize() throws IOException{ + File tempHtml = folder.newFile("test.html"); + PrintWriter out = new PrintWriter(tempHtml); + Map data = getCommonTestPostData(); + data.put("categories", "Java, J2EE, Spring Framework"); + out.print(convertMapToStringHeader(data)); + out.close(); + config.setProperty("categories.sanitize", true); + parser = new Parser(config,rootPath.getPath()); + Map map = parser.processFile(tempHtml); + assertThat(map.get("categories")) + .isNotNull() + .isInstanceOf(String[].class) + .isEqualTo(Arrays.array("Java","J2EE","Spring-Framework")); + } } diff --git a/src/test/java/org/jbake/app/template/AbstractTemplateEngineRenderingTest.java b/src/test/java/org/jbake/app/template/AbstractTemplateEngineRenderingTest.java index 732d3476a..c9d5e8fb1 100644 --- a/src/test/java/org/jbake/app/template/AbstractTemplateEngineRenderingTest.java +++ b/src/test/java/org/jbake/app/template/AbstractTemplateEngineRenderingTest.java @@ -143,6 +143,11 @@ private void setupExpectedOutputStrings() { outputStrings.put("sitemap", Arrays.asList("blog/2013/second-post.html", "blog/2012/first-post.html", "papers/published-paper.html")); + + + outputStrings.put("category", Arrays.asList("blog/2012/first-post.html")); + + outputStrings.put("categories", Arrays.asList("categories/technology.html")); } @@ -269,4 +274,26 @@ protected List getOutputStrings(String type) { return outputStrings.get(type); } + + @Test + public void renderCategories() throws Exception { + + renderer.renderCategories("categories"); + + // verify + File outputFile = new File(destinationFolder + File.separator + "categories" + File.separator + "Technology.html"); + Assert.assertTrue(outputFile.exists()); + String output = FileUtils.readFileToString(outputFile); + for (String string : outputStrings.get("category")) { + assertThat(output).contains(string); + } + + // verify index.html file + File indexFile = new File(destinationFolder + File.separator + "categories" + File.separator + "index.html"); + Assert.assertTrue(indexFile.exists()); + String indexData = FileUtils.readFileToString(indexFile); + for (String string : outputStrings.get("categories")) { + assertThat(indexData).contains(string); + } + } } diff --git a/src/test/java/org/jbake/app/template/FreemarkerTemplateEngineRenderingTest.java b/src/test/java/org/jbake/app/template/FreemarkerTemplateEngineRenderingTest.java index fa4d33b00..06f99ad64 100644 --- a/src/test/java/org/jbake/app/template/FreemarkerTemplateEngineRenderingTest.java +++ b/src/test/java/org/jbake/app/template/FreemarkerTemplateEngineRenderingTest.java @@ -25,8 +25,12 @@ import org.apache.commons.io.FileUtils; import org.jbake.app.ConfigUtil.Keys; +import org.jbake.app.Crawler; +import org.jbake.app.Renderer; import org.junit.Test; +import junit.framework.Assert; + import java.io.File; import java.nio.charset.Charset; import java.util.Arrays; @@ -81,5 +85,4 @@ public void shouldFallbackToRenderSingleIndexIfNoPostArePresent() throws Excepti assertTrue("index file exists",indexFile.exists()); } - } diff --git a/src/test/java/org/jbake/app/template/GroovyTemplateEngineRenderingTest.java b/src/test/java/org/jbake/app/template/GroovyTemplateEngineRenderingTest.java index 37d4958a0..769415bfc 100644 --- a/src/test/java/org/jbake/app/template/GroovyTemplateEngineRenderingTest.java +++ b/src/test/java/org/jbake/app/template/GroovyTemplateEngineRenderingTest.java @@ -23,8 +23,6 @@ */ package org.jbake.app.template; -import java.util.Arrays; - /** * * @author jdlee diff --git a/src/test/java/org/jbake/app/template/JadeTemplateEngineRenderingTest.java b/src/test/java/org/jbake/app/template/JadeTemplateEngineRenderingTest.java index 64504ea8f..e8229a8b7 100644 --- a/src/test/java/org/jbake/app/template/JadeTemplateEngineRenderingTest.java +++ b/src/test/java/org/jbake/app/template/JadeTemplateEngineRenderingTest.java @@ -1,10 +1,13 @@ package org.jbake.app.template; -import java.util.Arrays; - public class JadeTemplateEngineRenderingTest extends AbstractTemplateEngineRenderingTest { public JadeTemplateEngineRenderingTest() { super("jadeTemplates", "jade"); } + + @Override + public void renderCategories() throws Exception { + + } } diff --git a/src/test/java/org/jbake/template/ModelExtractorsTest.java b/src/test/java/org/jbake/template/ModelExtractorsTest.java index f106aff5d..3d0f37937 100644 --- a/src/test/java/org/jbake/template/ModelExtractorsTest.java +++ b/src/test/java/org/jbake/template/ModelExtractorsTest.java @@ -1,12 +1,27 @@ package org.jbake.template; +import static org.assertj.core.api.Assertions.*; + +import org.jbake.app.ContentStore; +import org.jbake.app.DocumentList; +import org.jbake.app.ConfigUtil.Keys; import org.jbake.model.DocumentTypes; +import org.jbake.render.support.MockCompositeConfiguration; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.mockito.Mockito; import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.is; +import static org.mockito.Mockito.mock; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.configuration.CompositeConfiguration; public class ModelExtractorsTest { @@ -31,10 +46,13 @@ public void shouldLoadExtractorsOnInstantiation() { "alltags", "db", "tag_posts", + "category_posts", + "category_documents", + "categories" }; for (String aKey : expectedKeys) { - assertThat(ModelExtractors.getInstance().containsKey(aKey)).isTrue(); + assertThat(ModelExtractors.getInstance().containsKey(aKey)).as("Extractor with key %s to exist", aKey).isTrue(); } } @@ -71,4 +89,39 @@ public void shouldThrowAnExceptionIfDocumentTypeIsUnknown() { String unknownDocumentType = "unknown"; ModelExtractors.getInstance().registerExtractorsForCustomTypes(unknownDocumentType); } + + @Test + public void shouldContainCategories() throws NoModelExtractorException{ + + Map config = new HashMap(); + config.put(Keys.CATEGORY_PATH.replace(".","_"), "categories"); + config.put(Keys.OUTPUT_EXTENSION.replace(".","_"), ".html"); + + ContentStore contentStore = mock(ContentStore.class); + Set cats = new HashSet<>(); + cats.add("Coding"); + + Mockito.when(contentStore.getCategories()).thenReturn(cats); + + Mockito.when(contentStore.getPublishedPostsByCategories(Mockito.anyString())).thenReturn(new DocumentList()); + + Mockito.when(contentStore.getPublishedDocumentsByCategory(Mockito.anyString())).thenReturn(new DocumentList()); + + DocumentList list = (DocumentList) ModelExtractors.getInstance() + .extractAndTransform(contentStore, "categories", + Collections.singletonMap("config", config), + new TemplateEngineAdapter.NoopAdapter()); + + assertThat(list) + .hasSize(1); + + for (Map cat : list){ + assertThat(cat) + .containsEntry("uri", "categories/coding.html") + .containsKeys("posts", "documents"); + } + + + + } } diff --git a/src/test/resources/fixture/content/blog/2012/first-post.html b/src/test/resources/fixture/content/blog/2012/first-post.html index 2dfba7a0a..ef14766cd 100644 --- a/src/test/resources/fixture/content/blog/2012/first-post.html +++ b/src/test/resources/fixture/content/blog/2012/first-post.html @@ -3,6 +3,7 @@ type=post tags=blog status=published +categories=Technology ~~~~~~ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque vel diam purus. Curabitur ut nisi lacus. diff --git a/src/test/resources/fixture/freemarkerTemplates/categories-index.ftl b/src/test/resources/fixture/freemarkerTemplates/categories-index.ftl new file mode 100644 index 000000000..e564eba02 --- /dev/null +++ b/src/test/resources/fixture/freemarkerTemplates/categories-index.ftl @@ -0,0 +1,15 @@ +<#include "header.ftl"> + + <#include "menu.ftl"> + + + + + +<#include "footer.ftl"> \ No newline at end of file diff --git a/src/test/resources/fixture/freemarkerTemplates/category.ftl b/src/test/resources/fixture/freemarkerTemplates/category.ftl new file mode 100644 index 000000000..e70a059df --- /dev/null +++ b/src/test/resources/fixture/freemarkerTemplates/category.ftl @@ -0,0 +1,27 @@ +<#include "header.ftl"> + + <#include "menu.ftl"> + + + + + <#list category_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/fixture/groovyMarkupTemplates/categories-index.tpl b/src/test/resources/fixture/groovyMarkupTemplates/categories-index.tpl new file mode 100644 index 000000000..0076d6c61 --- /dev/null +++ b/src/test/resources/fixture/groovyMarkupTemplates/categories-index.tpl @@ -0,0 +1,20 @@ +package fixture.groovyMarkupTemplates + +layout 'layout/main.tpl', + bodyContents: contents { + + div(class:"row-fluid marketing"){ + + div(class:"span12"){ + h1("Category List") + div{ + categories.each { cat -> + a(href:"${cat.uri}","${cat.name}") + } + + } + } + } + + hr() + } \ No newline at end of file diff --git a/src/test/resources/fixture/groovyMarkupTemplates/category.tpl b/src/test/resources/fixture/groovyMarkupTemplates/category.tpl new file mode 100644 index 000000000..c535aff9c --- /dev/null +++ b/src/test/resources/fixture/groovyMarkupTemplates/category.tpl @@ -0,0 +1,32 @@ +package fixture.groovyMarkupTemplates + +layout 'layout/main.tpl', + bodyContents: contents { + + div(class:"row-fluid marketing"){ + + div(class:"span12"){ + h2('Category') + def last_month + + category_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/fixture/groovyTemplates/categories-index.gsp b/src/test/resources/fixture/groovyTemplates/categories-index.gsp new file mode 100644 index 000000000..4cdbe81ae --- /dev/null +++ b/src/test/resources/fixture/groovyTemplates/categories-index.gsp @@ -0,0 +1,14 @@ +<%include 'header.gsp'%> + + +
    + +
    + +<%include "footer.gsp"%> diff --git a/src/test/resources/fixture/groovyTemplates/category.gsp b/src/test/resources/fixture/groovyTemplates/category.gsp new file mode 100644 index 000000000..6d56ead50 --- /dev/null +++ b/src/test/resources/fixture/groovyTemplates/category.gsp @@ -0,0 +1,27 @@ +<%include 'header.gsp'%> + + +
    + + <%def last_month=null;%> + <%category_posts.each {post ->%> + <%if (last_month) {%> + <%if (post.date.format("MMMM yyyy") != last_month) {%> +
+

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

+
    + <%}%> + <%} else {%> +

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

    +
      + <%}%> + +
    • ${post.date.format("dd")} - ${post.title}
    • + <% last_month = post.date.format("MMMM yyyy")%> + <%}%> +
    + + +<%include "footer.gsp"%> diff --git a/src/test/resources/fixture/jadeTemplates/categories-index.jade b/src/test/resources/fixture/jadeTemplates/categories-index.jade new file mode 100644 index 000000000..47649425a --- /dev/null +++ b/src/test/resources/fixture/jadeTemplates/categories-index.jade @@ -0,0 +1,11 @@ +extends layout.jade + +block content + .row-fluid.marketing + .span12 + h2 Category Posts + each category in categories + + a(href='#{category.uri}') #{category.name} + + hr \ No newline at end of file diff --git a/src/test/resources/fixture/jadeTemplates/category.jade b/src/test/resources/fixture/jadeTemplates/category.jade new file mode 100644 index 000000000..279043dbc --- /dev/null +++ b/src/test/resources/fixture/jadeTemplates/category.jade @@ -0,0 +1,16 @@ +extends layout.jade + +block content + .row-fluid.marketing + .span12 + h2 Category Posts + each post, index in category_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 + \ No newline at end of file diff --git a/src/test/resources/fixture/thymeleafTemplates/categories-index.thyme b/src/test/resources/fixture/thymeleafTemplates/categories-index.thyme new file mode 100644 index 000000000..9ea84e51b --- /dev/null +++ b/src/test/resources/fixture/thymeleafTemplates/categories-index.thyme @@ -0,0 +1,26 @@ + + + + + +
    + +
    + +
    +

    Taglist

    + +
    + +
    + +
    + +
    + + \ No newline at end of file diff --git a/src/test/resources/fixture/thymeleafTemplates/category.thyme b/src/test/resources/fixture/thymeleafTemplates/category.thyme new file mode 100644 index 000000000..bad989bac --- /dev/null +++ b/src/test/resources/fixture/thymeleafTemplates/category.thyme @@ -0,0 +1,26 @@ + + + + + +
    + +
    + +
    +

    Tags

    +
    +

    June 2014

    + +

    - Post title

    + +
    +
    +
    + +
    + +
    + + \ No newline at end of file