diff --git a/src/main/java/org/jbake/app/Crawler.java b/src/main/java/org/jbake/app/Crawler.java index 1927d18c2..b12c162d4 100644 --- a/src/main/java/org/jbake/app/Crawler.java +++ b/src/main/java/org/jbake/app/Crawler.java @@ -11,6 +11,7 @@ import static java.io.File.separator; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; @@ -20,6 +21,9 @@ import java.util.Set; import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateFormatUtils; import com.orientechnologies.orient.core.record.impl.ODocument; @@ -56,6 +60,7 @@ public static interface Status { static final String ROOTPATH = "rootpath"; static final String ID = "id"; static final String NO_EXTENSION_URI = "noExtensionUri"; + static final String PERMALINK = "permalink"; } private static final Logger LOGGER = LoggerFactory.getLogger(Crawler.class); @@ -178,9 +183,12 @@ private void crawlSourceFile(final File sourceFile, final String sha1, final Str } } - if (config.getBoolean(Keys.URI_NO_EXTENSION)) { + if (config.getBoolean(Keys.URI_NO_EXTENSION)) { fileContents.put(Attributes.NO_EXTENSION_URI, uri.replace("/index.html", "/")); } + + String permalink = buildPermalink(fileContents); + fileContents.put(Attributes.PERMALINK, permalink); ODocument doc = new ODocument(documentType); doc.fields(fileContents); @@ -191,6 +199,123 @@ private void crawlSourceFile(final File sourceFile, final String sha1, final Str LOGGER.warn("{} has an invalid header, it has been ignored!", sourceFile); } } + + /** + * This function generates permalinks if they are enabled in configuration. + * + * Default pattern is /:filepath + * + * Conditions - + * 1. String ending with ':' is treated as static strings. For example, permalink = /:blogdata:/:filepath, will generate all urls as /blogdata/{actual file path} + * 2. :filepath is reserved to add actual source file path (relative to content root) + * 3. :filename is reserved to add name of source file. + * 4. If the keyword values is array then all values of array are used for generation. For example, if /:tags is used and post has two tags tagA, tagB then url would be /tagA/tagB + * 5. :YEAR, :MONTH, :DAY are reserved to pull related part of content published date. + * + * If uri.noExtension is enabled then permalink generation will use it to generate extension less urls. + * + * on front end, permalinks can be accessed as {content.permalink} + * + * @param fileContents + * @author Manik Magar + * @return + */ + private String buildPermalink(Map fileContents){ + String permalink = ""; + + String separator = File.separator; + String permalinkPattern = config.getString(Attributes.PERMALINK,"/:filepath"); + if(config.containsKey(Attributes.PERMALINK +"."+ fileContents.get(Attributes.TYPE))){ + permalinkPattern = config.getString(Attributes.PERMALINK +"."+ fileContents.get(Attributes.TYPE)); + } + if (permalinkPattern != null && !permalinkPattern.trim().isEmpty()) { + + String pattern = permalinkPattern; + if(pattern.startsWith(":")) pattern = separator+pattern; + String[] parts = pattern.split("/:"); + List pLink = new ArrayList(); + + + //Check if permalink is specified in the content, Use it as final link + if(fileContents.containsKey(Attributes.PERMALINK) && !StringUtils.isBlank(fileContents.get(Attributes.PERMALINK).toString())){ + pLink.add(fileContents.get(Attributes.PERMALINK).toString()); + } else { + for (String part : parts){ + part = part.trim().replace("/", ""); + if (part.endsWith(":")){ + pLink.add(part.replace(":", "")); + } else if(part.equalsIgnoreCase("filepath")) { + String path = FileUtil.asPath(fileContents.get(Attributes.FILE).toString()).replace(FileUtil.asPath( contentPath), ""); + path = FilenameUtils.removeExtension(path); + // strip off leading / to enable generating non-root based sites + if (path.startsWith("/")) { + path = path.substring(1, path.length()); + } + pLink.add(path); + } else if(part.equalsIgnoreCase("filename")) { + String sourcePath = (String) fileContents.get(Attributes.SOURCE_URI); + String fileName = FilenameUtils.getBaseName(sourcePath); + pLink.add(fileName); + } else if(fileContents.containsKey(part)){ + Object value = fileContents.get(part); + if (value instanceof String){ + pLink.add(value.toString()); + } else if (value.getClass().equals(String[].class)){ + pLink.addAll(Arrays.asList((String[])value)); + } + } else if (Arrays.asList("YEAR","MONTH","DAY").contains(part.toUpperCase())) { + Date publishedDate = (Date) fileContents.get("date"); + if(publishedDate != null){ + String dateValue = null; + if(part.equalsIgnoreCase("YEAR")){ + dateValue = DateFormatUtils.format(publishedDate, "yyyy"); + } + if(part.equalsIgnoreCase("MONTH")){ + dateValue = DateFormatUtils.format(publishedDate, "MM"); + } + if(part.equalsIgnoreCase("DAY")){ + dateValue = DateFormatUtils.format(publishedDate, "dd"); + } + pLink.add(dateValue); + } + } + } + } + + permalink = StringUtils.join(pLink, separator); + permalink = sanitize(permalink).concat(separator); + String uri = permalink; + boolean noExtensionUri = config.getBoolean(Keys.URI_NO_EXTENSION); + if (noExtensionUri) { + uri = uri + "index.html"; + } else { + permalink = permalink.substring(0, permalink.length() -1 ); + permalink = permalink + config.getString(Keys.OUTPUT_EXTENSION); + uri = permalink; + } + if(uri.startsWith("/")){ + uri = uri.substring(1); + } + fileContents.put(Attributes.URI, uri); + + //Calculate the root path based on the permalink + File permaFile = new File(contentPath,uri); + String rootPath = getPathToRoot(permaFile); + fileContents.put(Attributes.ROOTPATH,rootPath); + } + + + return permalink; + } + + /** + * Replace the spaces with hyphens + * @return + */ + private String sanitize(String input){ + return input.replace(" ", "-"); + } + public String getPathToRoot(File sourceFile) { File rootPath = new File(contentPath); diff --git a/src/main/resources/default.properties b/src/main/resources/default.properties index 6e10c1b6e..0aa68ae86 100644 --- a/src/main/resources/default.properties +++ b/src/main/resources/default.properties @@ -88,4 +88,6 @@ 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= +# set the default permalink to source filepath. +permalink=/:filepath \ No newline at end of file diff --git a/src/test/java/org/jbake/app/CrawlerTest.java b/src/test/java/org/jbake/app/CrawlerTest.java index 7f9c209e5..2cabf0477 100644 --- a/src/test/java/org/jbake/app/CrawlerTest.java +++ b/src/test/java/org/jbake/app/CrawlerTest.java @@ -4,22 +4,17 @@ import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; -import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; 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.configuration.ConfigurationException; -import org.jbake.app.ConfigUtil.Keys; -import java.util.HashMap; -import java.util.Map; +import org.jbake.app.ConfigUtil.Keys; -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; @@ -134,4 +129,136 @@ public static RegexMatcher matches(String regex){ return new RegexMatcher(regex); } } + + + private Map getPermalinkPost(String permalinkPattern){ + config.setProperty("permalink", permalinkPattern); + Crawler crawler = new Crawler(db, sourceFolder, config); + crawler.crawl(new File(sourceFolder.getPath() + File.separator + config.getString(Keys.CONTENT_FOLDER))); + + List results = db.getPublishedPostsByTag("PermalinkTest"); + + assertThat(results.size()).isEqualTo(1); + + DocumentList list = DocumentList.wrap(results.iterator()); + Map content = list.getFirst(); + return content; + } + + @Test + public void testPermalinkFilePath(){ + + Map content = getPermalinkPost("/:filepath"); + + assertThat(content.get("uri")).isEqualTo("blog/2013/second-post.html"); + assertThat(content.get("uri")).isEqualTo(content.get("permalink")); + assertThat(content) + .containsKey(Crawler.Attributes.ROOTPATH) + .containsValue("../../"); + + } + + @Test + public void testPermalinkFilePathWithStatic(){ + + Map content = getPermalinkPost("/:data:/:filepath"); + + assertThat(content.get("uri")).isEqualTo("data/blog/2013/second-post.html"); + assertThat(content.get("uri")).isEqualTo(content.get("permalink")); + assertThat(content) + .containsKey(Crawler.Attributes.ROOTPATH) + .containsValue("../../../"); + } + + @Test + public void testPermalinkFilename(){ + + Map content = getPermalinkPost("/:blog:/:filename"); + + assertThat(content.get("uri")).isEqualTo("blog/second-post.html"); + assertThat(content.get("uri")).isEqualTo(content.get("permalink")); + assertThat(content) + .containsKey(Crawler.Attributes.ROOTPATH) + .containsValue("../"); + } + + @Test + public void testPermalinkWithDate(){ + + Map content = getPermalinkPost("/:blog:/:YEAR/:MONTH/:DAY/:filename"); + + assertThat(content.get("uri")).isEqualTo("blog/2013/02/28/second-post.html"); + assertThat(content.get("uri")).isEqualTo(content.get("permalink")); + assertThat(content) + .containsKey(Crawler.Attributes.ROOTPATH) + .containsValue("../../../../"); + } + + @Test + public void testPermalinkWithTagAndTitle(){ + + Map content = getPermalinkPost("/:data:/:tags/:title"); + + assertThat(content.get("uri")).isEqualTo("data/blog/PermalinkTest/Second-Post.html"); + assertThat(content.get("uri")).isEqualTo(content.get("permalink")); + assertThat(content) + .containsKey(Crawler.Attributes.ROOTPATH) + .containsValue("../../../"); + } + + + @Test + public void testPermalinkWithTagAndTitleWithNoExtension(){ + config.setProperty("uri.noExtension", true); + Map content = getPermalinkPost("/:data:/:tags/:title"); + + assertThat(content.get("uri")).isEqualTo("data/blog/PermalinkTest/Second-Post/index.html"); + assertThat(content.get("permalink")).isEqualTo("data/blog/PermalinkTest/Second-Post/"); + assertThat(content) + .containsKey(Crawler.Attributes.ROOTPATH) + .containsValue("../../../../"); + } + + @Test + public void testPermalinkWithTypePermalink(){ + config.setProperty("permalink", "/:filepath"); + config.setProperty("permalink.post", "/:blog:/:filename"); + Map content = getPermalinkPost("/:filepath"); + + assertThat(content.get("uri")).isEqualTo("blog/second-post.html"); + assertThat(content.get("uri")).isEqualTo(content.get("permalink")); + assertThat(content) + .containsKey(Crawler.Attributes.ROOTPATH) + .containsValue("../"); + } + + @Test + public void testPermalinkWithMultipleTypePermalink(){ + config.setProperty("permalink", "/:filepath"); + config.setProperty("permalink.post", "/:blog:/:filename"); + config.setProperty("permalink.page", "/:pages:/:filename"); + Crawler crawler = new Crawler(db, sourceFolder, config); + crawler.crawl(new File(sourceFolder.getPath() + File.separator + config.getString(Keys.CONTENT_FOLDER))); + + List results = db.getPublishedPostsByTag("PermalinkTest"); + + assertThat(results.size()).isEqualTo(1); + + DocumentList list = DocumentList.wrap(results.iterator()); + Map content = list.getFirst(); + + //Verify that post has used permalink.post pattern. + assertThat(content.get("uri")).isEqualTo("blog/second-post.html"); + assertThat(content.get("uri")).isEqualTo(content.get("permalink")); + + List pageResults = db.getPublishedPages(); + + DocumentList pages = DocumentList.wrap(pageResults.iterator()); + //Verify that page has used permalink.page pattern. + Map page = pages.getFirst(); + String url = "pages/"+FilenameUtils.getBaseName((String) page.get("file")) + ".html"; + assertThat(page.get("uri")).isEqualTo(url); + assertThat(page.get("uri")).isEqualTo(page.get("permalink")); + + } } diff --git a/src/test/resources/content/blog/2013/second-post.html b/src/test/resources/content/blog/2013/second-post.html index 05d6d7d42..f67d0dda9 100644 --- a/src/test/resources/content/blog/2013/second-post.html +++ b/src/test/resources/content/blog/2013/second-post.html @@ -1,7 +1,7 @@ title=Second Post date=2013-02-28 type=post -tags=blog +tags=blog,PermalinkTest status=published og={"description": "Something"} ~~~~~~