From 1557fb37ba20eeba9ffca50173a9252710d2a70c Mon Sep 17 00:00:00 2001
From: Philip Graf
Date: Wed, 9 Sep 2015 14:23:49 +0200
Subject: [PATCH 01/53] Fix AssetTest so the tests also run on Windows
---
src/test/java/org/jbake/app/AssetTest.java | 42 +++++++++++++++++-----
1 file changed, 34 insertions(+), 8 deletions(-)
diff --git a/src/test/java/org/jbake/app/AssetTest.java b/src/test/java/org/jbake/app/AssetTest.java
index fec0fb7aa..e8c277bde 100644
--- a/src/test/java/org/jbake/app/AssetTest.java
+++ b/src/test/java/org/jbake/app/AssetTest.java
@@ -1,11 +1,14 @@
package org.jbake.app;
import java.io.File;
+import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
+import java.util.Locale;
import org.apache.commons.configuration.CompositeConfiguration;
+import org.apache.commons.io.FileUtils;
import org.jbake.app.ConfigUtil.Keys;
import org.junit.Assert;
import org.junit.Before;
@@ -18,18 +21,14 @@ public class AssetTest {
private CompositeConfiguration config;
@Before
- public void setup() throws Exception, IOException, URISyntaxException {
+ public void setup() throws Exception, IOException, URISyntaxException {
config = ConfigUtil.load(new File(this.getClass().getResource("/").getFile()));
- Assert.assertEquals(".html", config.getString(Keys.OUTPUT_EXTENSION));
- readOnlyFolder.getRoot().setReadOnly();
+ Assert.assertEquals(".html", config.getString(Keys.OUTPUT_EXTENSION));
}
@Rule
public TemporaryFolder folder = new TemporaryFolder();
- @Rule
- public TemporaryFolder readOnlyFolder = new TemporaryFolder();
-
@Test
public void copy() throws Exception {
URL assetsUrl = this.getClass().getResource("/assets");
@@ -67,6 +66,7 @@ public void copyIgnore() throws Exception {
config.setProperty(Keys.ASSET_IGNORE_HIDDEN, "true");
URL assetsUrl = this.getClass().getResource("/ignorables");
File assets = new File(assetsUrl.getFile());
+ hideAssets(assets);
Asset asset = new Asset(assets.getParentFile(), folder.getRoot(), config);
asset.copy(assets);
@@ -78,6 +78,24 @@ public void copyIgnore() throws Exception {
Assert.assertTrue("Errors during asset copying", asset.getErrors().isEmpty());
}
+ /**
+ * Hides the assets on windows that start with a dot (e.g. .test.txt but not test.txt) so File.isHidden() returns true for those files.
+ */
+ private void hideAssets(File assets) throws IOException, InterruptedException {
+ if (isWindows()) {
+ final File[] hiddenFiles = assets.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.startsWith(".");
+ }
+ });
+ for (File file : hiddenFiles) {
+ final Process process = Runtime.getRuntime().exec(new String[] {"attrib" , "+h", file.getAbsolutePath()});
+ process.waitFor();
+ }
+ }
+ }
+
/**
* Primary intention is to extend test cases to increase coverage.
*
@@ -87,7 +105,10 @@ public void copyIgnore() throws Exception {
public void testWriteProtected() throws Exception {
URL assetsUrl = this.getClass().getResource("/assets");
File assets = new File(assetsUrl.getFile());
- Asset asset = new Asset(assets.getParentFile(), readOnlyFolder.getRoot(), config);
+ final File cssFile = new File(folder.newFolder("css"), "bootstrap.min.css");
+ FileUtils.touch(cssFile);
+ cssFile.setReadOnly();
+ Asset asset = new Asset(assets.getParentFile(), folder.getRoot(), config);
asset.copy(assets);
Assert.assertFalse("At least one error during copy expected", asset.getErrors().isEmpty());
@@ -103,7 +124,12 @@ public void testUnlistable() throws Exception {
config.setProperty(Keys.ASSET_FOLDER, "non-existent");
URL assetsUrl = this.getClass().getResource("/");
File assets = new File(assetsUrl.getFile() + File.separatorChar + "non-existent");
- Asset asset = new Asset(assets.getParentFile(), readOnlyFolder.getRoot(), config);
+ Asset asset = new Asset(assets.getParentFile(), folder.getRoot(), config);
asset.copy(assets);
}
+
+ private boolean isWindows() {
+ final String os = System.getProperty("os.name");
+ return os != null && os.toLowerCase(Locale.ENGLISH).contains("win");
+ }
}
From a55f82bd310cf1703a8eacf27d9096fdbaa4ef71 Mon Sep 17 00:00:00 2001
From: Philip Graf
Date: Wed, 9 Sep 2015 13:01:53 +0200
Subject: [PATCH 02/53] Support absolute paths for asset, content and template
folders
---
src/main/java/org/jbake/app/Asset.java | 77 +++++++++----------
src/main/java/org/jbake/app/Crawler.java | 3 +-
src/main/java/org/jbake/app/Oven.java | 3 +-
src/main/java/org/jbake/app/Renderer.java | 2 +-
.../java/org/jbake/model/DocumentTypes.java | 12 ++-
src/test/java/org/jbake/app/CrawlerTest.java | 16 ++--
.../org/jbake/app/GroovyRendererTest.java | 21 +++--
src/test/java/org/jbake/app/OvenTest.java | 72 +++++++++++++++++
src/test/java/org/jbake/app/RendererTest.java | 18 +++--
.../org/jbake/app/ThymeleafRendererTest.java | 21 +++--
10 files changed, 172 insertions(+), 73 deletions(-)
create mode 100644 src/test/java/org/jbake/app/OvenTest.java
diff --git a/src/main/java/org/jbake/app/Asset.java b/src/main/java/org/jbake/app/Asset.java
index 60a978c12..c5667748c 100644
--- a/src/main/java/org/jbake/app/Asset.java
+++ b/src/main/java/org/jbake/app/Asset.java
@@ -23,21 +23,14 @@ public class Asset {
private static final Logger LOGGER = LoggerFactory.getLogger(Asset.class);
- private File source;
- private File destination;
- private CompositeConfiguration config;
+ private final File destination;
private final List errors = new LinkedList();
private final boolean ignoreHidden;
/**
* Creates an instance of Asset.
- *
- * @param source
- * @param destination
*/
public Asset(File source, File destination, CompositeConfiguration config) {
- this.source = source;
- this.config = config;
this.destination = destination;
ignoreHidden = config.getBoolean(ConfigUtil.Keys.ASSET_IGNORE_HIDDEN, false);
}
@@ -48,41 +41,41 @@ public Asset(File source, File destination, CompositeConfiguration config) {
* @param path The starting path
*/
public void copy(File path) {
- File[] assets = path.listFiles(new FileFilter() {
- @Override
- public boolean accept(File file) {
- return !ignoreHidden || !file.isHidden();
- }
- });
- if (assets != null) {
- Arrays.sort(assets);
- for (int i = 0; i < assets.length; i++) {
- if (assets[i].isFile()) {
- StringBuilder sb = new StringBuilder();
- sb.append("Copying [" + assets[i].getPath() + "]...");
- File sourceFile = assets[i];
- File destFile = new File(sourceFile.getPath().replace(source.getPath()+File.separator+config.getString(ConfigUtil.Keys.ASSET_FOLDER), destination.getPath()));
- try {
- FileUtils.copyFile(sourceFile, destFile);
- sb.append("done!");
- LOGGER.info(sb.toString());
- } catch (IOException e) {
- sb.append("failed!");
- LOGGER.error(sb.toString(), e);
- e.printStackTrace();
- errors.add(e.getMessage());
- }
- }
+ copy(path, destination);
+ }
- if (assets[i].isDirectory()) {
- copy(assets[i]);
- }
- }
- }
- }
+ private void copy(File sourceFolder, File targetFolder) {
+ final File[] assets = sourceFolder.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return !ignoreHidden || !file.isHidden();
+ }
+ });
+ if (assets != null) {
+ Arrays.sort(assets);
+ for (File asset : assets) {
+ final File target = new File(targetFolder, asset.getName());
+ if (asset.isFile()) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("Copying [").append(asset.getPath()).append("]... ");
+ try {
+ FileUtils.copyFile(asset, target);
+ sb.append("done!");
+ LOGGER.info(sb.toString());
+ } catch (IOException e) {
+ sb.append("failed!");
+ LOGGER.error(sb.toString(), e);
+ errors.add(e.getMessage());
+ }
+ } else if (asset.isDirectory()) {
+ copy(asset, target);
+ }
+ }
+ }
+ }
- public List getErrors() {
- return new ArrayList(errors);
- }
+ public List getErrors() {
+ return new ArrayList(errors);
+ }
}
diff --git a/src/main/java/org/jbake/app/Crawler.java b/src/main/java/org/jbake/app/Crawler.java
index 49cfa5f2b..5b404d654 100644
--- a/src/main/java/org/jbake/app/Crawler.java
+++ b/src/main/java/org/jbake/app/Crawler.java
@@ -5,6 +5,7 @@
import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery;
import org.apache.commons.configuration.CompositeConfiguration;
+import org.apache.commons.io.FilenameUtils;
import org.jbake.app.ConfigUtil.Keys;
import org.jbake.model.DocumentStatus;
import org.jbake.model.DocumentTypes;
@@ -41,7 +42,7 @@ public class Crawler {
public Crawler(ContentStore db, File source, CompositeConfiguration config) {
this.db = db;
this.config = config;
- this.contentPath = source.getPath() + separator + config.getString(ConfigUtil.Keys.CONTENT_FOLDER);
+ this.contentPath = FilenameUtils.concat(source.getAbsolutePath(), config.getString(ConfigUtil.Keys.CONTENT_FOLDER));
this.parser = new Parser(config, contentPath);
}
diff --git a/src/main/java/org/jbake/app/Oven.java b/src/main/java/org/jbake/app/Oven.java
index de0cae2d9..eb5440009 100644
--- a/src/main/java/org/jbake/app/Oven.java
+++ b/src/main/java/org/jbake/app/Oven.java
@@ -15,6 +15,7 @@
import com.orientechnologies.orient.core.record.impl.ODocument;
import org.apache.commons.configuration.CompositeConfiguration;
+import org.apache.commons.io.FilenameUtils;
import org.jbake.app.ConfigUtil.Keys;
import org.jbake.model.DocumentTypes;
import org.slf4j.Logger;
@@ -113,7 +114,7 @@ public void setupPaths() {
}
private File setupPathFromConfig(String key) {
- return new File(source, config.getString(key));
+ return new File(FilenameUtils.concat(source.getAbsolutePath(), config.getString(key)));
}
private File setupRequiredFolderFromConfig(final String key) {
diff --git a/src/main/java/org/jbake/app/Renderer.java b/src/main/java/org/jbake/app/Renderer.java
index d494338dc..bccc98be4 100644
--- a/src/main/java/org/jbake/app/Renderer.java
+++ b/src/main/java/org/jbake/app/Renderer.java
@@ -93,7 +93,7 @@ public void render(Map content) throws Exception {
} catch (Exception e) {
sb.append("failed!");
LOGGER.error(sb.toString(), e);
- throw new Exception("Failed to render file. Cause: " + e.getMessage());
+ throw new Exception("Failed to render file " + outputFile.getAbsolutePath() + ". Cause: " + e.getMessage(), e);
}
}
diff --git a/src/main/java/org/jbake/model/DocumentTypes.java b/src/main/java/org/jbake/model/DocumentTypes.java
index 178019937..14ea8a5f1 100644
--- a/src/main/java/org/jbake/model/DocumentTypes.java
+++ b/src/main/java/org/jbake/model/DocumentTypes.java
@@ -14,7 +14,16 @@
*/
public class DocumentTypes {
- private static final Set DEFAULT_DOC_TYPES = new LinkedHashSet(Arrays.asList("page", "post", "masterindex", "archive", "feed"));
+ private static final Set DEFAULT_DOC_TYPES = new LinkedHashSet();
+
+ static {
+ resetDocumentTypes();
+ }
+
+ public static void resetDocumentTypes() {
+ DEFAULT_DOC_TYPES.clear();
+ DEFAULT_DOC_TYPES.addAll(Arrays.asList("page", "post", "masterindex", "archive", "feed"));
+ }
public static void addDocumentType(String docType) {
DEFAULT_DOC_TYPES.add(docType);
@@ -23,4 +32,5 @@ public static void addDocumentType(String docType) {
public static String[] getDocumentTypes() {
return DEFAULT_DOC_TYPES.toArray(new String[DEFAULT_DOC_TYPES.size()]);
}
+
}
diff --git a/src/test/java/org/jbake/app/CrawlerTest.java b/src/test/java/org/jbake/app/CrawlerTest.java
index 42ccdd488..ee5b41761 100644
--- a/src/test/java/org/jbake/app/CrawlerTest.java
+++ b/src/test/java/org/jbake/app/CrawlerTest.java
@@ -1,6 +1,8 @@
package org.jbake.app;
+import static org.assertj.core.api.Assertions.assertThat;
+
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
@@ -8,24 +10,28 @@
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 org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
-import static org.assertj.core.api.Assertions.*;
+import com.orientechnologies.orient.core.Orient;
+import com.orientechnologies.orient.core.record.impl.ODocument;
public class CrawlerTest {
private CompositeConfiguration config;
private ContentStore db;
private File sourceFolder;
-
+
+ @BeforeClass
+ public static void startup() {
+ Orient.instance().startup();
+ }
+
@Before
public void setup() throws Exception, IOException, URISyntaxException {
URL sourceUrl = this.getClass().getResource("/");
diff --git a/src/test/java/org/jbake/app/GroovyRendererTest.java b/src/test/java/org/jbake/app/GroovyRendererTest.java
index 53d4cb264..fdc8c5510 100644
--- a/src/test/java/org/jbake/app/GroovyRendererTest.java
+++ b/src/test/java/org/jbake/app/GroovyRendererTest.java
@@ -2,7 +2,12 @@
import static org.assertj.core.api.Assertions.assertThat;
-import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
+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 org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.io.FileUtils;
@@ -11,17 +16,12 @@
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.BeforeClass;
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;
+import com.orientechnologies.orient.core.Orient;
public class GroovyRendererTest {
@@ -34,6 +34,11 @@ public class GroovyRendererTest {
private CompositeConfiguration config;
private ContentStore db;
+ @BeforeClass
+ public static void startup() {
+ Orient.instance().startup();
+ }
+
@Before
public void setup() throws Exception, IOException, URISyntaxException {
URL sourceUrl = this.getClass().getResource("/");
diff --git a/src/test/java/org/jbake/app/OvenTest.java b/src/test/java/org/jbake/app/OvenTest.java
new file mode 100644
index 000000000..1db9185a5
--- /dev/null
+++ b/src/test/java/org/jbake/app/OvenTest.java
@@ -0,0 +1,72 @@
+package org.jbake.app;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.commons.configuration.CompositeConfiguration;
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.ConfigurationException;
+import org.jbake.app.ConfigUtil.Keys;
+import org.jbake.model.DocumentTypes;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import com.orientechnologies.orient.core.Orient;
+
+public class OvenTest {
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+
+ @BeforeClass
+ public static void reset() {
+ DocumentTypes.resetDocumentTypes();
+ }
+
+ @Before
+ public void startup() {
+ Orient.instance().startup();
+ }
+
+ @Test
+ public void bakeWithRelativePaths() throws IOException, ConfigurationException {
+ final File source = new File(OvenTest.class.getResource("/").getFile());
+ final File destination = folder.newFolder("destination");
+ final CompositeConfiguration configuration = ConfigUtil.load(source);
+ configuration.setProperty(Keys.DESTINATION_FOLDER, destination.getAbsolutePath());
+
+ final Oven oven = new Oven(source, destination, configuration, true);
+ oven.setupPaths();
+ oven.bake();
+
+ assertThat("There shouldn't be any errors: " + oven.getErrors(), oven.getErrors().isEmpty());
+ }
+
+ @Test
+ public void bakeWithAbsolutePaths() throws IOException, ConfigurationException {
+ final File source = new File(OvenTest.class.getResource("/").getFile());
+ final File destination = folder.newFolder("destination");
+ final CompositeConfiguration configuration = ConfigUtil.load(source);
+ makeAbsolute(configuration, source, Keys.TEMPLATE_FOLDER);
+ makeAbsolute(configuration, source, Keys.CONTENT_FOLDER);
+ makeAbsolute(configuration, source, Keys.ASSET_FOLDER);
+ configuration.setProperty(Keys.DESTINATION_FOLDER, destination.getAbsolutePath());
+
+ final Oven oven = new Oven(source, destination, configuration, true);
+ oven.setupPaths();
+ oven.bake();
+
+ assertThat("There shouldn't be any errors: " + oven.getErrors(), oven.getErrors().isEmpty());
+ }
+
+ private void makeAbsolute(Configuration configuration, File source, String key) {
+ final File folder = new File(source, configuration.getString(key));
+ configuration.setProperty(key, folder.getAbsolutePath());
+ }
+
+}
diff --git a/src/test/java/org/jbake/app/RendererTest.java b/src/test/java/org/jbake/app/RendererTest.java
index c41546572..fdd1f6815 100644
--- a/src/test/java/org/jbake/app/RendererTest.java
+++ b/src/test/java/org/jbake/app/RendererTest.java
@@ -1,13 +1,8 @@
package org.jbake.app;
-import org.jbake.app.ConfigUtil.Keys;
-import org.jbake.model.DocumentTypes;
-import org.junit.After;
+import static org.assertj.core.api.Assertions.assertThat;
import java.io.File;
-
-import static org.assertj.core.api.Assertions.*;
-
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
@@ -15,12 +10,18 @@
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.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import com.orientechnologies.orient.core.Orient;
+
public class RendererTest {
@Rule
@@ -32,6 +33,11 @@ public class RendererTest {
private CompositeConfiguration config;
private ContentStore db;
+ @BeforeClass
+ public static void startup() {
+ Orient.instance().startup();
+ }
+
@Before
public void setup() throws Exception, IOException, URISyntaxException {
URL sourceUrl = this.getClass().getResource("/");
diff --git a/src/test/java/org/jbake/app/ThymeleafRendererTest.java b/src/test/java/org/jbake/app/ThymeleafRendererTest.java
index fed656e95..4677e1d15 100644
--- a/src/test/java/org/jbake/app/ThymeleafRendererTest.java
+++ b/src/test/java/org/jbake/app/ThymeleafRendererTest.java
@@ -2,7 +2,12 @@
import static org.assertj.core.api.Assertions.assertThat;
-import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
+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 org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.io.FileUtils;
@@ -11,17 +16,12 @@
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.BeforeClass;
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;
+import com.orientechnologies.orient.core.Orient;
public class ThymeleafRendererTest {
@@ -34,6 +34,11 @@ public class ThymeleafRendererTest {
private CompositeConfiguration config;
private ContentStore db;
+ @BeforeClass
+ public static void startup() {
+ Orient.instance().startup();
+ }
+
@Before
public void setup() throws Exception, IOException, URISyntaxException {
URL sourceUrl = this.getClass().getResource("/");
From b2d24e1144865663a4ff164d60899c58d349fd55 Mon Sep 17 00:00:00 2001
From: Michael Gfeller
Date: Fri, 15 Apr 2016 18:23:59 +0200
Subject: [PATCH 03/53] Implements #278, added support for default type
---
src/main/java/org/jbake/app/ConfigUtil.java | 9 +++--
.../java/org/jbake/parser/MarkupEngine.java | 7 ++++
src/test/java/org/jbake/app/ParserTest.java | 33 ++++++++++++++++---
3 files changed, 43 insertions(+), 6 deletions(-)
diff --git a/src/main/java/org/jbake/app/ConfigUtil.java b/src/main/java/org/jbake/app/ConfigUtil.java
index a3f0e0d2b..a45b47feb 100644
--- a/src/main/java/org/jbake/app/ConfigUtil.java
+++ b/src/main/java/org/jbake/app/ConfigUtil.java
@@ -90,7 +90,12 @@ public static interface Keys {
* Default status to use (in order to avoid putting it in all files)
*/
static final String DEFAULT_STATUS = "default.status";
-
+
+ /**
+ * Default type to use (in order to avoid putting it in all files)
+ */
+ static final String DEFAULT_TYPE = "default.type";
+
/**
* Folder where rendered files are output
*/
@@ -195,7 +200,7 @@ public static interface Keys {
* 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);
private final static String LEGACY_CONFIG_FILE = "custom.properties";
diff --git a/src/main/java/org/jbake/parser/MarkupEngine.java b/src/main/java/org/jbake/parser/MarkupEngine.java
index 3666be69d..271872678 100644
--- a/src/main/java/org/jbake/parser/MarkupEngine.java
+++ b/src/main/java/org/jbake/parser/MarkupEngine.java
@@ -99,6 +99,13 @@ public Map parse(Configuration config, File file, String content
}
}
+ if (config.getString(Keys.DEFAULT_TYPE) != null) {
+ if (content.get(Crawler.Attributes.TYPE) == null) {
+ // file hasn't got status so use default
+ content.put(Crawler.Attributes.TYPE, config.getString(Keys.DEFAULT_TYPE));
+ }
+ }
+
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);
diff --git a/src/test/java/org/jbake/app/ParserTest.java b/src/test/java/org/jbake/app/ParserTest.java
index 72ebb4f65..dd38333c9 100644
--- a/src/test/java/org/jbake/app/ParserTest.java
+++ b/src/test/java/org/jbake/app/ParserTest.java
@@ -9,6 +9,7 @@
import java.util.Map;
import org.apache.commons.configuration.CompositeConfiguration;
+import org.apache.commons.configuration.ConfigurationException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
@@ -31,13 +32,14 @@ public class ParserTest {
private File validAsciiDocFileWithoutHeader;
private File invalidAsciiDocFileWithoutHeader;
private File validAsciiDocFileWithHeaderInContent;
-
+ private File validAsciiDocFileWithoutJBakeMetaData;
+
private String validHeader = "title=This is a Title = This is a valid Title\nstatus=draft\ntype=post\ndate=2013-09-02\n~~~~~~";
private String invalidHeader = "title=This is a Title\n~~~~~~";
-
-
- @Before
+
+
+ @Before
public void createSampleFile() throws Exception {
rootPath = new File(this.getClass().getResource(".").getFile());
config = ConfigUtil.load(rootPath);
@@ -112,6 +114,15 @@ public void createSampleFile() throws Exception {
out.println("~~~~~~");
out.println("----");
out.close();
+
+ validAsciiDocFileWithoutJBakeMetaData = folder.newFile("validwojbakemetadata.ad");
+ out = new PrintWriter(validAsciiDocFileWithoutJBakeMetaData);
+ out.println("= Hello: AsciiDoc!");
+ out.println("Test User ");
+ out.println("2013-09-02");
+ out.println("");
+ out.println("JBake now supports AsciiDoc documents without JBake meta data.");
+ out.close();
}
@Test
@@ -192,4 +203,18 @@ public void parseValidAsciiDocFileWithExampleHeaderInContent() {
.contains("tags=tag1, tag2");
// Assert.assertEquals("\n
\n
\n
JBake now supports AsciiDoc.
\n
\n
\n
\n
title=Example Header\ndate=2013-02-01\ntype=post\ntags=tag1, tag2\nstatus=published\n~~~~~~
\n
\n
\n
\n
", map.get("body"));
}
+
+ @Test
+ public void parseValidAsciiDocFileWithoutJBakeMetaDataUsingDefaultTypeAndStatus() throws ConfigurationException {
+ CompositeConfiguration defaultConfig = ConfigUtil.load(rootPath);
+ defaultConfig.addProperty(ConfigUtil.Keys.DEFAULT_STATUS, "published");
+ defaultConfig.addProperty(ConfigUtil.Keys.DEFAULT_TYPE, "page");
+ Parser parser = new Parser(defaultConfig,rootPath.getPath());
+ Map map = parser.processFile(validAsciiDocFileWithoutJBakeMetaData);
+ Assert.assertNotNull(map);
+ Assert.assertEquals("published", map.get("status"));
+ Assert.assertEquals("page", map.get("type"));
+ assertThat(map.get("body").toString())
+ .contains("JBake now supports AsciiDoc documents without JBake meta data.
");
+ }
}
From 6fc53eaa78cd7500c10f9c137303929004834cb8 Mon Sep 17 00:00:00 2001
From: Frank Becker
Date: Fri, 28 Oct 2016 13:57:56 +0200
Subject: [PATCH 04/53] paginate into subfolders
The first index.html is rendered into the root directory.
Following paginated pages are put into subfolders per page like 1/index.html, 2/index.html etc.
closes #308
---
src/main/java/org/jbake/app/Renderer.java | 3 +-
.../java/org/jbake/util/PagingHelper.java | 24 +++++----
...FreemarkerTemplateEngineRenderingTest.java | 4 +-
.../java/org/jbake/util/PagingHelperTest.java | 49 +++++++++++++++++++
4 files changed, 67 insertions(+), 13 deletions(-)
diff --git a/src/main/java/org/jbake/app/Renderer.java b/src/main/java/org/jbake/app/Renderer.java
index 42632459b..810df8672 100644
--- a/src/main/java/org/jbake/app/Renderer.java
+++ b/src/main/java/org/jbake/app/Renderer.java
@@ -246,9 +246,8 @@ public void renderIndexPaging(String indexFile) throws Exception {
model.put("content", buildSimpleModel("masterindex"));
model.put("numberOfPages", pagingHelper.getNumberOfPages());
- db.setLimit(postsPerPage);
-
try {
+ db.setLimit(postsPerPage);
for (int pageStart = 0, page = 1; pageStart < totalPosts; pageStart += postsPerPage, page++) {
String fileName = indexFile;
diff --git a/src/main/java/org/jbake/util/PagingHelper.java b/src/main/java/org/jbake/util/PagingHelper.java
index 47a6c2404..3384b5628 100644
--- a/src/main/java/org/jbake/util/PagingHelper.java
+++ b/src/main/java/org/jbake/util/PagingHelper.java
@@ -1,5 +1,7 @@
package org.jbake.util;
+import java.io.File;
+
public class PagingHelper {
long totalDocuments;
int postsPerPage;
@@ -15,9 +17,7 @@ public int getNumberOfPages() {
public String getNextFileName(int currentPageNumber, String fileName) {
if (currentPageNumber < getNumberOfPages()) {
- int index = fileName.lastIndexOf(".");
- return fileName.substring(0, index) + (currentPageNumber + 1) +
- fileName.substring(index);
+ return (currentPageNumber + 1) + File.separator + fileName;
} else {
return null;
}
@@ -28,9 +28,12 @@ public String getPreviousFileName(int currentPageNumber, String fileName) {
if (isFirstPage(currentPageNumber)) {
return null;
} else {
- int index = fileName.lastIndexOf(".");
- return fileName.substring(0, index) + (currentPageNumber > 2 ? currentPageNumber - 1 : "") +
- fileName.substring(index);
+ if ( currentPageNumber == 2 ) {
+ return fileName;
+ }
+ else {
+ return (currentPageNumber - 1) + File.separator + fileName;
+ }
}
}
@@ -39,9 +42,12 @@ private boolean isFirstPage(int page) {
}
public String getCurrentFileName(int page, String fileName) {
- int index = fileName.lastIndexOf(".");
- return fileName.substring(0, index) + (page > 1 ? page : "") +
- fileName.substring(index);
+ if ( isFirstPage(page) ) {
+ return fileName;
+ }
+ else {
+ return page + File.separator + fileName;
+ }
}
}
diff --git a/src/test/java/org/jbake/app/template/FreemarkerTemplateEngineRenderingTest.java b/src/test/java/org/jbake/app/template/FreemarkerTemplateEngineRenderingTest.java
index 148a9a5ee..3dac37bbb 100644
--- a/src/test/java/org/jbake/app/template/FreemarkerTemplateEngineRenderingTest.java
+++ b/src/test/java/org/jbake/app/template/FreemarkerTemplateEngineRenderingTest.java
@@ -49,13 +49,13 @@ public void renderPaginatedIndex() throws Exception {
outputStrings.put("index", Arrays.asList(
"index.html\">Previous",
- "index3.html\">Next",
+ "3/index.html\">Next",
"2 of 3"
));
renderer.renderIndexPaging("index.html");
- File outputFile = new File(destinationFolder, "index2.html");
+ File outputFile = new File(destinationFolder, 2 + File.separator + "index.html");
String output = FileUtils.readFileToString(outputFile, Charset.defaultCharset());
for (String string : getOutputStrings("index")) {
diff --git a/src/test/java/org/jbake/util/PagingHelperTest.java b/src/test/java/org/jbake/util/PagingHelperTest.java
index 1ee2a8dfe..8d3170b34 100644
--- a/src/test/java/org/jbake/util/PagingHelperTest.java
+++ b/src/test/java/org/jbake/util/PagingHelperTest.java
@@ -3,6 +3,7 @@
import org.junit.Assert;
import org.junit.Test;
+import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
/**
@@ -20,4 +21,52 @@ public void getNumberOfPages() throws Exception {
Assert.assertEquals( expected, helper.getNumberOfPages() );
}
+ @Test
+ public void shouldReturnRootIndexPage() throws Exception {
+ PagingHelper helper = new PagingHelper(5,2);
+
+ String previousFileName = helper.getPreviousFileName(2, "index.html");
+
+ Assert.assertThat("index.html", is( previousFileName) );
+ }
+
+ @Test
+ public void shouldReturnPreviousFileName() throws Exception {
+
+ PagingHelper helper = new PagingHelper(5,2);
+
+ String previousFileName = helper.getPreviousFileName(3, "index.html");
+
+ Assert.assertThat("2/index.html", is( previousFileName) );
+
+ }
+
+ @Test
+ public void shouldReturnNullIfNoPreviousPageAvailable() throws Exception {
+ PagingHelper helper = new PagingHelper(5,2);
+
+ String previousFileName = helper.getPreviousFileName(1, "index.html");
+
+ Assert.assertNull( previousFileName );
+ }
+
+ @Test
+ public void shouldReturnNullIfNextPageNotAvailable() throws Exception {
+ PagingHelper helper = new PagingHelper(5,2);
+
+ String nextFileName = helper.getNextFileName(3, "index.html");
+
+ Assert.assertNull( nextFileName );
+
+ }
+
+ @Test
+ public void shouldReturnNextFileName() throws Exception {
+ PagingHelper helper = new PagingHelper(5,2);
+
+ String nextFileName = helper.getNextFileName(2, "index.html");
+
+ Assert.assertThat("3/index.html", is( nextFileName) );
+
+ }
}
\ No newline at end of file
From ee6e2512108a532ab457bb19fa345ec380af1c2a Mon Sep 17 00:00:00 2001
From: Marcin Grabowski
Date: Sat, 19 Nov 2016 02:00:40 +0100
Subject: [PATCH 05/53] Convert Javadoc to be compatible with strict Java 8
build
---
pom.xml | 3 --
src/main/java/org/jbake/app/Asset.java | 17 ++++-----
src/main/java/org/jbake/app/ConfigUtil.java | 2 +-
src/main/java/org/jbake/app/Crawler.java | 5 ++-
src/main/java/org/jbake/app/DBUtil.java | 2 ++
src/main/java/org/jbake/app/FileUtil.java | 7 ++--
src/main/java/org/jbake/app/Oven.java | 18 ++++++----
src/main/java/org/jbake/app/Parser.java | 17 +++++----
src/main/java/org/jbake/app/Renderer.java | 20 ++++++-----
src/main/java/org/jbake/app/ZipUtil.java | 4 +--
src/main/java/org/jbake/launcher/Init.java | 15 ++++----
.../java/org/jbake/launcher/JettyServer.java | 6 ++--
src/main/java/org/jbake/launcher/Main.java | 14 ++++----
.../java/org/jbake/model/DocumentTypes.java | 11 +++---
.../template/GroovyMarkupTemplateEngine.java | 4 +--
.../org/jbake/template/ModelExtractors.java | 36 +++++++++----------
.../jbake/template/TemplateEngineAdapter.java | 6 ++--
.../org/jbake/template/TemplateEngines.java | 28 ++++++++-------
.../template/ThymeleafTemplateEngine.java | 16 ++++-----
19 files changed, 123 insertions(+), 108 deletions(-)
diff --git a/pom.xml b/pom.xml
index a6c391400..f09f216ce 100644
--- a/pom.xml
+++ b/pom.xml
@@ -121,9 +121,6 @@
maven-javadoc-plugin
-
- -Xdoclint:none
-
attach-javadocs
diff --git a/src/main/java/org/jbake/app/Asset.java b/src/main/java/org/jbake/app/Asset.java
index d90328827..902bce68b 100644
--- a/src/main/java/org/jbake/app/Asset.java
+++ b/src/main/java/org/jbake/app/Asset.java
@@ -1,5 +1,10 @@
package org.jbake.app;
+import org.apache.commons.configuration.CompositeConfiguration;
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
@@ -8,15 +13,10 @@
import java.util.LinkedList;
import java.util.List;
-import org.apache.commons.configuration.CompositeConfiguration;
-import org.apache.commons.io.FileUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
/**
* Deals with assets (static files such as css, js or image files).
*
- * @author Jonathan Bullock
+ * @author Jonathan Bullock jonbullock@gmail.com
*
*/
public class Asset {
@@ -32,8 +32,9 @@ public class Asset {
/**
* Creates an instance of Asset.
*
- * @param source
- * @param destination
+ * @param source Source file for the asset
+ * @param destination Destination (target) directory for asset file
+ * @param config Project configuration
*/
public Asset(File source, File destination, CompositeConfiguration config) {
this.source = source;
diff --git a/src/main/java/org/jbake/app/ConfigUtil.java b/src/main/java/org/jbake/app/ConfigUtil.java
index 089bd7945..26bf5ea32 100644
--- a/src/main/java/org/jbake/app/ConfigUtil.java
+++ b/src/main/java/org/jbake/app/ConfigUtil.java
@@ -11,7 +11,7 @@
/**
* Provides Configuration related functions.
*
- * @author Jonathan Bullock
+ * @author Jonathan Bullock jonbullock@gmail.com
*/
public class ConfigUtil {
diff --git a/src/main/java/org/jbake/app/Crawler.java b/src/main/java/org/jbake/app/Crawler.java
index 1e67606fd..c9403d6e0 100644
--- a/src/main/java/org/jbake/app/Crawler.java
+++ b/src/main/java/org/jbake/app/Crawler.java
@@ -22,7 +22,7 @@
/**
* Crawls a file system looking for content.
*
- * @author Jonathan Bullock
+ * @author Jonathan Bullock jonbullock@gmail.com
*/
public class Crawler {
public interface Attributes {
@@ -61,6 +61,9 @@ interface Status {
/**
* Creates new instance of Crawler.
+ * @param db Database instance for content
+ * @param source Base directory where content directory is located
+ * @param config Project configuration
*/
public Crawler(ContentStore db, File source, CompositeConfiguration config) {
this.db = db;
diff --git a/src/main/java/org/jbake/app/DBUtil.java b/src/main/java/org/jbake/app/DBUtil.java
index 435d50be0..0446bcc96 100644
--- a/src/main/java/org/jbake/app/DBUtil.java
+++ b/src/main/java/org/jbake/app/DBUtil.java
@@ -37,6 +37,8 @@ public static Map documentToModel(ODocument doc) {
/**
* Converts a DB list into a String array
+ * @param entry Entry input to be converted
+ * @return input entry as String[]
*/
@SuppressWarnings("unchecked")
public static String[] toStringArray(Object entry) {
diff --git a/src/main/java/org/jbake/app/FileUtil.java b/src/main/java/org/jbake/app/FileUtil.java
index 91842573e..4c3eae0e7 100644
--- a/src/main/java/org/jbake/app/FileUtil.java
+++ b/src/main/java/org/jbake/app/FileUtil.java
@@ -15,7 +15,7 @@
/**
* Provides File related functions
*
- * @author Jonathan Bullock
+ * @author Jonathan Bullock jonbullock@gmail.com
*/
public class FileUtil {
@@ -43,10 +43,9 @@ public static boolean isExistingFolder(File f) {
* Works out the folder where JBake is running from.
*
* @return File referencing folder JBake is running from
- * @throws Exception
+ * @throws Exception when application is not able to work out where is JBake running from
*/
public static File getRunningLocation() throws Exception {
- // work out where JBake is running from
String codePath = FileUtil.class.getProtectionDomain().getCodeSource().getLocation().getPath();
String decodedPath = URLDecoder.decode(codePath, "UTF-8");
File codeFile = new File(decodedPath);
@@ -80,7 +79,7 @@ public static String fileExt(String name) {
*
* @param sourceFile the original file or directory
* @return an hex string representing the SHA1 hash of the file or directory.
- * @throws Exception
+ * @throws Exception if any IOException of SecurityException occured
*/
public static String sha1(File sourceFile) throws Exception {
byte[] buffer = new byte[1024];
diff --git a/src/main/java/org/jbake/app/Oven.java b/src/main/java/org/jbake/app/Oven.java
index 8ef85afe9..2f6ecedaf 100644
--- a/src/main/java/org/jbake/app/Oven.java
+++ b/src/main/java/org/jbake/app/Oven.java
@@ -1,6 +1,7 @@
package org.jbake.app;
import org.apache.commons.configuration.CompositeConfiguration;
+import org.apache.commons.configuration.ConfigurationException;
import org.jbake.app.ConfigUtil.Keys;
import org.jbake.model.DocumentAttributes;
import org.jbake.model.DocumentTypes;
@@ -23,7 +24,7 @@
/**
* All the baking happens in the Oven!
*
- * @author Jonathan Bullock
+ * @author Jonathan Bullock jonbullock@gmail.com
*/
public class Oven {
@@ -43,16 +44,23 @@ public class Oven {
/**
* Delegate c'tor to prevent API break for the moment.
+ *
+ * @param source Project source directory
+ * @param destination The destination folder
+ * @param isClearCache Should the cache be cleaned
+ * @throws ConfigurationException if configuration is not loaded correctly
*/
- public Oven(final File source, final File destination, final boolean isClearCache) throws Exception {
+ public Oven(final File source, final File destination, final boolean isClearCache) throws ConfigurationException {
this(source, destination, ConfigUtil.load(source), isClearCache);
}
/**
* Creates a new instance of the Oven with references to the source and destination folders.
*
- * @param source The source folder
- * @param destination The destination folder
+ * @param source Project source directory
+ * @param destination The destination folder
+ * @param config Project configuration
+ * @param isClearCache Should the cache be cleaned
*/
public Oven(final File source, final File destination, final CompositeConfiguration config, final boolean isClearCache) {
this.source = source;
@@ -121,8 +129,6 @@ private File setupRequiredFolderFromConfig(final String key) {
/**
* All the good stuff happens in here...
- *
- * @throws JBakeException
*/
public void bake() {
final ContentStore db = DBUtil.createDataStore(config.getString(Keys.DB_STORE), config.getString(Keys.DB_PATH));
diff --git a/src/main/java/org/jbake/app/Parser.java b/src/main/java/org/jbake/app/Parser.java
index 5a18ed64c..fb15146b4 100644
--- a/src/main/java/org/jbake/app/Parser.java
+++ b/src/main/java/org/jbake/app/Parser.java
@@ -1,18 +1,18 @@
package org.jbake.app;
-import org.jbake.parser.Engines;
-import java.io.File;
-import java.util.Map;
-
import org.apache.commons.configuration.Configuration;
+import org.jbake.parser.Engines;
import org.jbake.parser.ParserEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.File;
+import java.util.Map;
+
/**
* Parses a File for content.
*
- * @author Jonathan Bullock
+ * @author Jonathan Bullock jonbullock@gmail.com
*/
public class Parser {
private final static Logger LOGGER = LoggerFactory.getLogger(Parser.class);
@@ -22,6 +22,9 @@ public class Parser {
/**
* Creates a new instance of Parser.
+ *
+ * @param config Project configuration
+ * @param contentPath Content location
*/
public Parser(Configuration config, String contentPath) {
this.config = config;
@@ -31,8 +34,8 @@ public Parser(Configuration config, String contentPath) {
/**
* Process the file by parsing the contents.
*
- * @param file
- * @return The contents of the file
+ * @param file File input for parsing
+ * @return The contents of the file
*/
public Map processFile(File file) {
ParserEngine engine = Engines.get(FileUtil.fileExt(file));
diff --git a/src/main/java/org/jbake/app/Renderer.java b/src/main/java/org/jbake/app/Renderer.java
index 3d03bbc3a..2cbeb4c79 100644
--- a/src/main/java/org/jbake/app/Renderer.java
+++ b/src/main/java/org/jbake/app/Renderer.java
@@ -20,7 +20,7 @@
/**
* Render output to a file.
*
- * @author Jonathan Bullock
+ * @author Jonathan Bullock jonbullock@gmail.com
*/
public class Renderer {
@@ -125,7 +125,7 @@ public Map getModel() {
* @param db The database holding the content
* @param destination The destination folder
* @param templatesPath The templates folder
- * @param config
+ * @param config Project configuration
*/
public Renderer(ContentStore db, File destination, File templatesPath, CompositeConfiguration config) {
this.destination = destination;
@@ -143,8 +143,8 @@ private String findTemplateName(String docType) {
/**
* Render the supplied content to a file.
*
- * @param content The content to renderDocument
- * @throws Exception
+ * @param content The content to renderDocument
+ * @throws Exception if IOException or SecurityException are raised
*/
public void render(Map content) throws Exception {
String docType = (String) content.get(Crawler.Attributes.TYPE);
@@ -219,7 +219,7 @@ private void render(RenderingConfig renderConfig) throws Exception {
* Render an index file using the supplied content.
*
* @param indexFile The name of the output file
- * @throws Exception
+ * @throws Exception if IOException or SecurityException are raised
*/
public void renderIndex(String indexFile) throws Exception {
long totalPosts = db.getDocumentCount("post");
@@ -279,7 +279,8 @@ public void renderIndex(String indexFile) throws Exception {
/**
* Render an XML sitemap file using the supplied content.
- * @throws Exception
+ * @param sitemapFile configuration for site map
+ * @throws Exception if can't create correct default rendering config
*
* @see About Sitemaps
* @see Sitemap protocol
@@ -292,7 +293,7 @@ public void renderSitemap(String sitemapFile) throws Exception {
* Render an XML feed file using the supplied content.
*
* @param feedFile The name of the output file
- * @throws Exception
+ * @throws Exception if default rendering configuration is not loaded correctly
*/
public void renderFeed(String feedFile) throws Exception {
render(new DefaultRenderingConfig(feedFile, "feed"));
@@ -302,7 +303,7 @@ public void renderFeed(String feedFile) throws Exception {
* Render an archive file using the supplied content.
*
* @param archiveFile The name of the output file
- * @throws Exception
+ * @throws Exception if default rendering configuration is not loaded correctly
*/
public void renderArchive(String archiveFile) throws Exception {
render(new DefaultRenderingConfig(archiveFile, "archive"));
@@ -312,7 +313,8 @@ public void renderArchive(String archiveFile) throws Exception {
* Render tag files using the supplied content.
*
* @param tagPath The output path
- * @throws Exception
+ * @return Nuber of rendered tags
+ * @throws Exception if cannot render tags correctly
*/
public int renderTags(String tagPath) throws Exception {
int renderedCount = 0;
diff --git a/src/main/java/org/jbake/app/ZipUtil.java b/src/main/java/org/jbake/app/ZipUtil.java
index 38e87d169..f9ed0f770 100644
--- a/src/main/java/org/jbake/app/ZipUtil.java
+++ b/src/main/java/org/jbake/app/ZipUtil.java
@@ -10,7 +10,7 @@
/**
* Provides Zip file related functions
*
- * @author Jonathan Bullock
+ * @author Jonathan Bullock jonbullock@gmail.com
*
*/
public class ZipUtil {
@@ -20,7 +20,7 @@ public class ZipUtil {
*
* @param is {@link InputStream} InputStream of Zip file
* @param outputFolder folder where Zip file should be extracted to
- * @throws IOException
+ * @throws IOException if IOException occurs
*/
public static void extract(InputStream is, File outputFolder) throws IOException {
ZipInputStream zis = new ZipInputStream(is);
diff --git a/src/main/java/org/jbake/launcher/Init.java b/src/main/java/org/jbake/launcher/Init.java
index e639587bc..de0baf92b 100644
--- a/src/main/java/org/jbake/launcher/Init.java
+++ b/src/main/java/org/jbake/launcher/Init.java
@@ -1,16 +1,16 @@
package org.jbake.launcher;
-import java.io.File;
-import java.io.FileInputStream;
-
import org.apache.commons.configuration.CompositeConfiguration;
import org.jbake.app.ConfigUtil.Keys;
import org.jbake.app.ZipUtil;
+import java.io.File;
+import java.io.FileInputStream;
+
/**
* Initialises sample folder structure with pre-defined template
*
- * @author Jonathan Bullock
+ * @author Jonathan Bullock jonbullock@gmail.com
*
*/
public class Init {
@@ -24,9 +24,10 @@ public Init(CompositeConfiguration config) {
/**
* Performs checks on output folder before extracting template file
*
- * @param outputFolder
- * @param templateLocationFolder
- * @throws Exception
+ * @param outputFolder Target directory for extracting template file
+ * @param templateLocationFolder Source location for template file
+ * @param templateType Type of the template to be used
+ * @throws Exception if required folder structure can't be achieved without content overwriting
*/
public void run(File outputFolder, File templateLocationFolder, String templateType) throws Exception {
if (!outputFolder.canWrite()) {
diff --git a/src/main/java/org/jbake/launcher/JettyServer.java b/src/main/java/org/jbake/launcher/JettyServer.java
index ea4673a76..fe4a5e0aa 100644
--- a/src/main/java/org/jbake/launcher/JettyServer.java
+++ b/src/main/java/org/jbake/launcher/JettyServer.java
@@ -13,7 +13,7 @@
/**
* Provides Jetty server related functions
*
- * @author Jonathan Bullock
+ * @author Jonathan Bullock jonbullock@gmail.com
*
*/
public class JettyServer {
@@ -22,8 +22,8 @@ public class JettyServer {
/**
* Run Jetty web server serving out supplied path on supplied port
*
- * @param path
- * @param port
+ * @param path Base directory for resourced to be served
+ * @param port Required server port
*/
public static void run(String path, String port) {
Server server = new Server();
diff --git a/src/main/java/org/jbake/launcher/Main.java b/src/main/java/org/jbake/launcher/Main.java
index 9855aa3e5..fd6b76e34 100644
--- a/src/main/java/org/jbake/launcher/Main.java
+++ b/src/main/java/org/jbake/launcher/Main.java
@@ -1,10 +1,5 @@
package org.jbake.launcher;
-import java.io.File;
-import java.io.StringWriter;
-import java.text.MessageFormat;
-import java.util.List;
-
import org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.vfs2.FileObject;
@@ -21,10 +16,15 @@
import org.kohsuke.args4j.CmdLineParser;
import org.slf4j.bridge.SLF4JBridgeHandler;
+import java.io.File;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+import java.util.List;
+
/**
* Launcher for JBake.
*
- * @author Jonathan Bullock
+ * @author Jonathan Bullock jonbullock@gmail.com
*
*/
public class Main {
@@ -35,7 +35,7 @@ public class Main {
/**
* Runs the app with the given arguments.
*
- * @param args
+ * @param args Application arguments
*/
public static void main(final String[] args) {
try {
diff --git a/src/main/java/org/jbake/model/DocumentTypes.java b/src/main/java/org/jbake/model/DocumentTypes.java
index d64b2e144..ac4aeeb03 100644
--- a/src/main/java/org/jbake/model/DocumentTypes.java
+++ b/src/main/java/org/jbake/model/DocumentTypes.java
@@ -1,17 +1,16 @@
package org.jbake.model;
+import org.jbake.parser.Engines;
+
import java.util.Arrays;
import java.util.HashSet;
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".
- *
- * Additional document types are added at runtime based on the types found in the configuration.
+ * Utility class used to determine the list of document types. Currently only supports "page", "post", "index",
+ * "archive" and "feed".
+ * Additional document types are added at runtime based on the types found in the configuration.
*
* @author Cédric Champeau
*/
diff --git a/src/main/java/org/jbake/template/GroovyMarkupTemplateEngine.java b/src/main/java/org/jbake/template/GroovyMarkupTemplateEngine.java
index 4adb0826b..f3c359e6e 100644
--- a/src/main/java/org/jbake/template/GroovyMarkupTemplateEngine.java
+++ b/src/main/java/org/jbake/template/GroovyMarkupTemplateEngine.java
@@ -16,9 +16,9 @@
/**
* Renders documents using the GroovyMarkupTemplateEngine.
*
- * @see Groovy MarkupTemplateEngine Documentation
- *
* The file extension to activate this Engine is .tpl
+ *
+ * @see Groovy MarkupTemplateEngine Documentation
*/
public class GroovyMarkupTemplateEngine extends AbstractTemplateEngine {
private TemplateConfiguration templateConfiguration;
diff --git a/src/main/java/org/jbake/template/ModelExtractors.java b/src/main/java/org/jbake/template/ModelExtractors.java
index c1ab3ada5..36ea8e06c 100644
--- a/src/main/java/org/jbake/template/ModelExtractors.java
+++ b/src/main/java/org/jbake/template/ModelExtractors.java
@@ -1,5 +1,12 @@
package org.jbake.template;
+import org.jbake.app.ContentStore;
+import org.jbake.model.DocumentTypeUtils;
+import org.jbake.template.model.PublishedCustomExtractor;
+import org.jbake.template.model.TypedDocumentsExtractor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
@@ -8,26 +15,21 @@
import java.util.Set;
import java.util.TreeMap;
-import org.jbake.app.ContentStore;
-import org.jbake.model.DocumentTypeUtils;
-import org.jbake.template.model.PublishedCustomExtractor;
-import org.jbake.template.model.TypedDocumentsExtractor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
/**
* 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.
- *
+ * (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
@@ -124,9 +126,8 @@ public Type extractAndTransform(ContentStore db, String key, Map map, Tem
}
/**
- * @param key
- * @return
- * @category delegate
+ * @param key Key for lookup
+ * @return True if the key is contained by this object
* @see java.util.Map#containsKey(java.lang.Object)
*/
public boolean containsKey(Object key) {
@@ -134,8 +135,7 @@ public boolean containsKey(Object key) {
}
/**
- * @return
- * @category delegate
+ * @return Set of keys contained in this object
* @see java.util.Map#keySet()
*/
public Set keySet() {
diff --git a/src/main/java/org/jbake/template/TemplateEngineAdapter.java b/src/main/java/org/jbake/template/TemplateEngineAdapter.java
index e0c53929b..03f125b38 100644
--- a/src/main/java/org/jbake/template/TemplateEngineAdapter.java
+++ b/src/main/java/org/jbake/template/TemplateEngineAdapter.java
@@ -18,9 +18,9 @@ public Object adapt(String key, Object extractedValue) {
/**
* Adapt value to expected output
- * @param key
- * @param extractedValue
- * @return
+ * @param key Template key
+ * @param extractedValue Value to be used in template model
+ * @return Value adapted for use in template
*/
Type adapt(String key, Object extractedValue);
diff --git a/src/main/java/org/jbake/template/TemplateEngines.java b/src/main/java/org/jbake/template/TemplateEngines.java
index cd1166518..26be48083 100644
--- a/src/main/java/org/jbake/template/TemplateEngines.java
+++ b/src/main/java/org/jbake/template/TemplateEngines.java
@@ -1,5 +1,11 @@
package org.jbake.template;
+import org.apache.commons.configuration.CompositeConfiguration;
+import org.apache.commons.configuration.Configuration;
+import org.jbake.app.ContentStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
@@ -11,28 +17,24 @@
import java.util.Map;
import java.util.Properties;
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;
/**
* 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
- * (recommanded).
The descriptor file must be found in META-INF directory and named
- * org.jbake.parser.TemplateEngines.properties. The format of the file is easy:
- * org.jbake.parser.FreeMarkerRenderer=ftl
org.jbake.parser.GroovyRenderer=groovy,gsp
where the key
- * is the class of the engine (must extend {@link AbstractTemplateEngine} and have a 4-arg constructor and
- * the value is a comma-separated list of file extensions that this engine is capable of proceeding.
- *
+ * (recommanded).
+ * The descriptor file must be found in META-INF directory and named
+ * org.jbake.parser.TemplateEngines.properties. The format of the file is easy:
+ * org.jbake.parser.FreeMarkerRenderer=ftl
org.jbake.parser.GroovyRenderer=groovy,gsp
+ * where the key is the class of the engine (must extend {@link AbstractTemplateEngine} and have
+ * a 4-arg constructor and the value is a comma-separated list of file extensions that this engine is capable
+ * of proceeding.
* Rendering engines are singletons, so are typically used to initialize the underlying template engines.
- *
+ *
* 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 Cédric Champeau
*/
diff --git a/src/main/java/org/jbake/template/ThymeleafTemplateEngine.java b/src/main/java/org/jbake/template/ThymeleafTemplateEngine.java
index e1b6f71a0..c66cef75f 100644
--- a/src/main/java/org/jbake/template/ThymeleafTemplateEngine.java
+++ b/src/main/java/org/jbake/template/ThymeleafTemplateEngine.java
@@ -1,12 +1,5 @@
package org.jbake.template;
-import java.io.File;
-import java.io.Writer;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.locks.ReentrantLock;
-
import org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.lang.LocaleUtils;
import org.jbake.app.ConfigUtil.Keys;
@@ -16,6 +9,13 @@
import org.thymeleaf.context.Context;
import org.thymeleaf.templateresolver.FileTemplateResolver;
+import java.io.File;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+
/**
* A template engine which renders pages using Thymeleaf.
*
@@ -24,7 +24,7 @@
*
* The default rendering mode is "HTML", but it is possible to use another mode
* for each document type, by adding a key in the configuration, for example:
- *
+ *
*
* template.feed.thymeleaf.mode=XML
*
From 172af743fbd7a65765df4624adca58e2d639fbde Mon Sep 17 00:00:00 2001
From: "John D. Ament"
Date: Sat, 14 Jan 2017 15:32:04 -0500
Subject: [PATCH 06/53] Enhancement for #333, override site.host variable when
running the server.
---
src/main/java/org/jbake/app/ConfigUtil.java | 20 +++++++++++++++++--
src/main/java/org/jbake/launcher/Main.java | 3 +--
.../java/org/jbake/app/ConfigUtilTest.java | 17 ++++++++++++----
.../java/org/jbake/launcher/MainTest.java | 8 +++-----
.../{custom.properties => jbake.properties} | 0
5 files changed, 35 insertions(+), 13 deletions(-)
rename src/test/resources/{custom.properties => jbake.properties} (100%)
diff --git a/src/main/java/org/jbake/app/ConfigUtil.java b/src/main/java/org/jbake/app/ConfigUtil.java
index 089bd7945..9e9d74900 100644
--- a/src/main/java/org/jbake/app/ConfigUtil.java
+++ b/src/main/java/org/jbake/app/ConfigUtil.java
@@ -3,6 +3,7 @@
import org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
+import org.apache.commons.configuration.SystemConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -200,6 +201,11 @@ public interface Keys {
* How many posts per page on index
*/
String POSTS_PER_PAGE = "index.posts_per_page";
+
+ /**
+ * The configured base URI for the hosted content
+ */
+ String SITE_HOST = "site.host";
}
private final static Logger LOGGER = LoggerFactory.getLogger(ConfigUtil.class);
@@ -207,8 +213,12 @@ public interface Keys {
private final static String CONFIG_FILE = "jbake.properties";
private final static String DEFAULT_CONFIG_FILE = "default.properties";
private static boolean LEGACY_CONFIG_FILE_WARNING_SHOWN = false;
-
- public static CompositeConfiguration load(File source) throws ConfigurationException {
+
+ public static CompositeConfiguration load(File source) throws ConfigurationException {
+ return load(source, false);
+ }
+
+ public static CompositeConfiguration load(File source, boolean isRunServer) throws ConfigurationException {
CompositeConfiguration config = new CompositeConfiguration();
config.setListDelimiter(',');
File customConfigFile = new File(source, LEGACY_CONFIG_FILE);
@@ -225,6 +235,12 @@ public static CompositeConfiguration load(File source) throws ConfigurationExcep
config.addConfiguration(new PropertiesConfiguration(customConfigFile));
}
config.addConfiguration(new PropertiesConfiguration(DEFAULT_CONFIG_FILE));
+ config.addConfiguration(new SystemConfiguration());
+ if (isRunServer) {
+ String port = config.getString(Keys.SERVER_PORT);
+ config.setProperty(Keys.SITE_HOST, "http://localhost:"+port);
+ }
return config;
}
+
}
diff --git a/src/main/java/org/jbake/launcher/Main.java b/src/main/java/org/jbake/launcher/Main.java
index 6639f28e4..21a02cae3 100644
--- a/src/main/java/org/jbake/launcher/Main.java
+++ b/src/main/java/org/jbake/launcher/Main.java
@@ -73,11 +73,10 @@ protected void run(String[] args) {
final CompositeConfiguration config;
try {
- config = ConfigUtil.load( res.getSource() );
+ config = ConfigUtil.load( res.getSource(), res.isRunServer() );
} catch( final ConfigurationException e ) {
throw new JBakeException( "Configuration error: " + e.getMessage(), e );
}
-
run(res, config);
}
diff --git a/src/test/java/org/jbake/app/ConfigUtilTest.java b/src/test/java/org/jbake/app/ConfigUtilTest.java
index 63cee9e83..4fcfeca45 100644
--- a/src/test/java/org/jbake/app/ConfigUtilTest.java
+++ b/src/test/java/org/jbake/app/ConfigUtilTest.java
@@ -2,12 +2,12 @@
import java.io.File;
-import junit.framework.Assert;
-
import org.apache.commons.configuration.CompositeConfiguration;
import org.jbake.app.ConfigUtil.Keys;
import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
public class ConfigUtilTest {
@Test
@@ -15,9 +15,18 @@ public void load() throws Exception {
CompositeConfiguration config = ConfigUtil.load(new File(this.getClass().getResource("/").getFile()));
// check default.properties values exist
- Assert.assertEquals("output", config.getString(Keys.DESTINATION_FOLDER));
+ assertEquals("output", config.getString(Keys.DESTINATION_FOLDER));
// check custom.properties values exist
- Assert.assertEquals("testing123", config.getString("test.property"));
+ assertEquals("testing123", config.getString("test.property"));
+
+ assertEquals("http://www.jbake.org", config.getString(Keys.SITE_HOST));
+ }
+
+ @Test
+ public void shouldHaveSiteConfiguredWhenServerRunning() throws Exception {
+ CompositeConfiguration config = ConfigUtil.load(new File(this.getClass().getResource("/").getFile()), true);
+
+ assertEquals("http://localhost:8820", config.getString(Keys.SITE_HOST));
}
}
diff --git a/src/test/java/org/jbake/launcher/MainTest.java b/src/test/java/org/jbake/launcher/MainTest.java
index b2592602e..84915989b 100644
--- a/src/test/java/org/jbake/launcher/MainTest.java
+++ b/src/test/java/org/jbake/launcher/MainTest.java
@@ -101,7 +101,7 @@ public void launchJettyWithCustomDestViaConfig() throws CmdLineException {
@Test
public void launchJettyWithCmdlineOverridingProperties() throws CmdLineException {
String[] args = {"src/jbake", "build/jbake", "-s"};
- Map properties = new HashMap(){{
+ Map properties = new HashMap(){{
put("destination.folder", "target/jbake");
}};
main.run(stubOptions(args), stubConfig(properties));
@@ -119,10 +119,8 @@ private LaunchOptions stubOptions(String[] args) throws CmdLineException {
private CompositeConfiguration stubConfig(Map properties) {
CompositeConfiguration config = new CompositeConfiguration();
config.addProperty("server.port", "8820");
- Iterator it = properties.entrySet().iterator();
- while(it.hasNext()) {
- Map.Entry pair = (Map.Entry)it.next();
- config.addProperty( pair.getKey(), pair.getValue() );
+ for (Map.Entry pair : properties.entrySet()) {
+ config.addProperty(pair.getKey(), pair.getValue());
}
return config;
}
diff --git a/src/test/resources/custom.properties b/src/test/resources/jbake.properties
similarity index 100%
rename from src/test/resources/custom.properties
rename to src/test/resources/jbake.properties
From 5193eedcb65502f616fdecb57eac18f33358c87a Mon Sep 17 00:00:00 2001
From: Frank Becker
Date: Tue, 24 Jan 2017 19:53:58 +0100
Subject: [PATCH 07/53] update orientdb to 2.2.15
* added -XX:MaxDirectMemorySize=1024m to jbake and jbake.bat script, to prevent warning from orientdb
closes #318
---
pom.xml | 9 +-
src/main/java/org/jbake/app/ContentStore.java | 14 +--
.../org/jbake/model/DocumentAttributes.java | 2 +-
src/main/scripts/jbake | 5 +-
src/main/scripts/jbake.bat | 2 +-
.../java/org/jbake/FakeDocumentBuilder.java | 99 +++++++++++++++++++
.../java/org/jbake/app/ContentStoreTest.java | 32 +++---
.../java/org/jbake/app/PaginationTest.java | 29 +++---
8 files changed, 146 insertions(+), 46 deletions(-)
create mode 100644 src/test/java/org/jbake/FakeDocumentBuilder.java
diff --git a/pom.xml b/pom.xml
index 887a76dcb..4aec030dd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,7 +75,7 @@
4.12
1.6.0
8.1.19.v20160209
- 1.7.10
+ 2.2.15
2.4.7
1.7.21
1.1.7
@@ -436,12 +436,7 @@
com.orientechnologies
- orient-commons
- ${orientdb.version}
-
-
- com.orientechnologies
- orientdb-core
+ orientdb-graphdb
${orientdb.version}
diff --git a/src/main/java/org/jbake/app/ContentStore.java b/src/main/java/org/jbake/app/ContentStore.java
index fee087da5..15ea77f83 100644
--- a/src/main/java/org/jbake/app/ContentStore.java
+++ b/src/main/java/org/jbake/app/ContentStore.java
@@ -25,6 +25,8 @@
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;
@@ -61,7 +63,7 @@ public ContentStore(final String type, String name) {
if (!exists) {
db.create();
}
- db = ODatabaseDocumentPool.global().acquire(type + ":" + name, "admin", "admin");
+ db = new OPartitionedDatabasePoolFactory().get(type+":"+name, "admin", "admin").acquire();
ODatabaseRecordThreadLocal.INSTANCE.set(db);
if (!exists) {
updateSchema();
@@ -157,19 +159,19 @@ public DocumentList getPublishedPages() {
}
public DocumentList getPublishedContent(String docType) {
- String query = "select * from " + docType + " where status='published'";
+ String query = "select * from " + docType + " where status='published' order by date desc";
if ((start >= 0) && (limit > -1)) {
query += " SKIP " + start + " LIMIT " + limit;
}
- return query(query + " order by date desc");
+ return query(query);
}
public DocumentList getAllContent(String docType) {
- String query = "select * from " + docType;
+ String query = "select * from " + docType + " order by date desc";
if ((start >= 0) && (limit > -1)) {
query += " SKIP " + start + " LIMIT " + limit;
}
- return query(query + " order by date desc");
+ return query(query);
}
public DocumentList getAllTagsFromPublishedPosts() {
@@ -258,8 +260,6 @@ private void createDocType(final OSchema schema, final String doctype) {
private void createSignatureType(OSchema schema) {
OClass signatures = schema.createClass("Signatures");
- signatures.createProperty(String.valueOf(DocumentAttributes.KEY), OType.STRING).setNotNull(true);
- signatures.createIndex("keyIdx", OClass.INDEX_TYPE.NOTUNIQUE, DocumentAttributes.KEY.toString());
signatures.createProperty(String.valueOf(DocumentAttributes.SHA1), OType.STRING).setNotNull(true);
signatures.createIndex("sha1Idx", OClass.INDEX_TYPE.UNIQUE, DocumentAttributes.SHA1.toString());
}
diff --git a/src/main/java/org/jbake/model/DocumentAttributes.java b/src/main/java/org/jbake/model/DocumentAttributes.java
index 2acda40aa..ae698fb1c 100644
--- a/src/main/java/org/jbake/model/DocumentAttributes.java
+++ b/src/main/java/org/jbake/model/DocumentAttributes.java
@@ -6,7 +6,7 @@ public enum DocumentAttributes {
RENDERED("rendered"),
CACHED("cached"),
STATUS("status"),
- KEY("key");
+ NAME("name");
private String label;
diff --git a/src/main/scripts/jbake b/src/main/scripts/jbake
index c1180fc97..e75fc3920 100755
--- a/src/main/scripts/jbake
+++ b/src/main/scripts/jbake
@@ -7,6 +7,7 @@ fi
EXEC_LOC="`dirname "$P"`"
EXEC_PARENT="`dirname $EXEC_LOC`"
+OPTS="-XX:MaxDirectMemorySize=1024m"
CYGWIN=false;
case "`uname`" in
@@ -15,7 +16,7 @@ esac
if $CYGWIN ;
then
- java -jar $(cygpath -w "${EXEC_PARENT}/jbake-core.jar") $@
+ java $OPTS -jar $(cygpath -w "${EXEC_PARENT}/jbake-core.jar") $@
else
- java -jar "${EXEC_PARENT}/jbake-core.jar" $@
+ java $OPTS -jar "${EXEC_PARENT}/jbake-core.jar" $@
fi
\ No newline at end of file
diff --git a/src/main/scripts/jbake.bat b/src/main/scripts/jbake.bat
index dccbf14a8..5953c91f1 100644
--- a/src/main/scripts/jbake.bat
+++ b/src/main/scripts/jbake.bat
@@ -1,2 +1,2 @@
@echo off
-java -jar "%~dp0\..\jbake-core.jar" %*
+java -XX:MaxDirectMemorySize=1024m -jar "%~dp0\..\jbake-core.jar" %*
diff --git a/src/test/java/org/jbake/FakeDocumentBuilder.java b/src/test/java/org/jbake/FakeDocumentBuilder.java
new file mode 100644
index 000000000..b2bc9d0d5
--- /dev/null
+++ b/src/test/java/org/jbake/FakeDocumentBuilder.java
@@ -0,0 +1,99 @@
+package org.jbake;
+
+import com.orientechnologies.orient.core.record.impl.ODocument;
+import org.jbake.app.Crawler;
+import org.jbake.model.DocumentAttributes;
+
+import javax.xml.bind.DatatypeConverter;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+public class FakeDocumentBuilder {
+
+ Map fileModel = new HashMap();
+ String type;
+ private boolean hasSourceUri = false;
+ private boolean hasSha1 = false;
+ private boolean hasDate = false;
+
+ public FakeDocumentBuilder(String type) {
+ this.type = type;
+ }
+
+ public FakeDocumentBuilder withName(String name) {
+ fileModel.put(DocumentAttributes.NAME.toString(), name);
+ return this;
+ }
+
+ public FakeDocumentBuilder withStatus(String status) {
+ fileModel.put(DocumentAttributes.STATUS.toString(), status);
+ return this;
+ }
+
+ public FakeDocumentBuilder withRandomSha1() throws NoSuchAlgorithmException {
+ fileModel.put(DocumentAttributes.SHA1.toString(), getRandomSha1());
+ hasSha1 = true;
+ return this;
+ }
+
+ private FakeDocumentBuilder withCurrentDate() {
+ fileModel.put(Crawler.Attributes.DATE, new Date() );
+ return this;
+ }
+
+ private FakeDocumentBuilder withRandomSourceUri() throws NoSuchAlgorithmException {
+ String path = "/tmp/" + getRandomSha1() + ".txt";
+ fileModel.put(DocumentAttributes.SOURCE_URI.toString(), path);
+ return this;
+ }
+
+ public FakeDocumentBuilder withCached(boolean cached) {
+ fileModel.put(DocumentAttributes.CACHED.toString(), cached);
+ return this;
+ }
+
+ public void build() {
+
+ try {
+ if ( ! hasSourceUri() ) {
+ this.withRandomSourceUri();
+ }
+ if ( ! hasSha1() ) {
+ this.withRandomSha1();
+ }
+ if ( ! hasDate() ) {
+ this.withCurrentDate();
+ }
+ ODocument document = new ODocument(type).fromMap(fileModel);
+ document.save();
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private String getRandomSha1() throws NoSuchAlgorithmException {
+ MessageDigest sha1Digest = MessageDigest.getInstance("SHA-1");
+ Random random = new Random();
+ int size = random.nextInt(1000) + 1000;
+ byte[] content = new byte[size];
+
+ random.nextBytes(content);
+ return DatatypeConverter.printHexBinary(sha1Digest.digest(content));
+ }
+
+ private boolean hasDate() {
+ return hasDate;
+ }
+
+ private boolean hasSha1() {
+ return hasSha1;
+ }
+
+ private boolean hasSourceUri() {
+ return hasSourceUri;
+ }
+}
diff --git a/src/test/java/org/jbake/app/ContentStoreTest.java b/src/test/java/org/jbake/app/ContentStoreTest.java
index bfce8c8b0..77b02e308 100644
--- a/src/test/java/org/jbake/app/ContentStoreTest.java
+++ b/src/test/java/org/jbake/app/ContentStoreTest.java
@@ -1,13 +1,21 @@
package org.jbake.app;
import com.orientechnologies.orient.core.record.impl.ODocument;
+import org.jbake.FakeDocumentBuilder;
+import org.jbake.model.DocumentAttributes;
+import org.jbake.model.DocumentStatus;
+import org.jbake.model.DocumentTypes;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import javax.xml.bind.DatatypeConverter;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
+import java.util.Random;
import static org.junit.Assert.assertEquals;
@@ -30,26 +38,22 @@ public void tearDown() throws Exception {
@Test
public void shouldGetCountForPublishedDocuments() throws Exception {
- Map fileContents = new HashMap();
-
for (int i = 0; i < 5; i++) {
- fileContents.put("name", "dummyfile" + i);
- fileContents.put("status", "published");
- createFakeDocument("post", fileContents);
+ FakeDocumentBuilder builder = new FakeDocumentBuilder("post");
+ builder.withName("dummyfile" + i)
+ .withStatus("published")
+ .withRandomSha1()
+ .build();
}
- fileContents.put("name", "draftdummy");
- fileContents.put("status", "draft");
- createFakeDocument("post",fileContents);
+ FakeDocumentBuilder builder = new FakeDocumentBuilder("post");
+ builder.withName("draftdummy")
+ .withStatus("draft")
+ .withRandomSha1()
+ .build();
assertEquals( 6, db.getDocumentCount("post"));
assertEquals( 5, db.getPublishedCount("post"));
}
- private void createFakeDocument(String name, Map fileContents) {
- ODocument doc = new ODocument(name);
- doc.fields(fileContents);
- doc.save();
- }
-
}
\ No newline at end of file
diff --git a/src/test/java/org/jbake/app/PaginationTest.java b/src/test/java/org/jbake/app/PaginationTest.java
index b23b60e97..fbb1ebd05 100644
--- a/src/test/java/org/jbake/app/PaginationTest.java
+++ b/src/test/java/org/jbake/app/PaginationTest.java
@@ -25,16 +25,17 @@
import com.orientechnologies.orient.core.record.impl.ODocument;
import org.apache.commons.configuration.CompositeConfiguration;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.jbake.FakeDocumentBuilder;
+import org.junit.*;
import java.io.File;
+import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import static org.assertj.core.api.Assertions.assertThat;
+
/**
* @author jdlee
*/
@@ -68,18 +69,15 @@ public void cleanup() throws InterruptedException {
@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();
+ FakeDocumentBuilder builder = new FakeDocumentBuilder("post");
+ builder.withName("dummyfile" + i)
+ .withCached(true)
+ .withStatus("published")
+ .build();
}
int pageCount = 1;
@@ -90,9 +88,12 @@ public void testPagination() {
db.setStart(start);
DocumentList posts = db.getAllContent("post");
- int expectedNumber = (pageCount==1)?pageCount:((pageCount%2==0)?pageCount+1:pageCount+PER_PAGE);
+ assertThat( posts.size() ).isLessThanOrEqualTo( 2 );
+
+ if( posts.size() > 1 ) {
+ assertThat((Date) posts.get(0).get("date")).isAfter((Date) posts.get(1).get("date"));
+ }
- Assert.assertEquals("pagcount " +pageCount,"dummyfile" + expectedNumber, posts.get(0).get("name"));
pageCount++;
start += PER_PAGE;
}
From 59d1aa90cda18bb3f10de1fb72772ab34bd0f3c2 Mon Sep 17 00:00:00 2001
From: Jonathan Bullock
Date: Sun, 7 May 2017 21:52:52 +0100
Subject: [PATCH 08/53] Reset state of DocumentTypes for OvenTest.
---
src/main/java/org/jbake/model/DocumentTypes.java | 12 +++++++++++-
src/test/java/org/jbake/app/OvenTest.java | 6 +++++-
2 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/jbake/model/DocumentTypes.java b/src/main/java/org/jbake/model/DocumentTypes.java
index f62e1f726..e39a9ea50 100644
--- a/src/main/java/org/jbake/model/DocumentTypes.java
+++ b/src/main/java/org/jbake/model/DocumentTypes.java
@@ -17,9 +17,19 @@
*/
public class DocumentTypes {
- private static final Set DEFAULT_DOC_TYPES = new LinkedHashSet(Arrays.asList("page", "post", "masterindex", "archive", "feed"));
+ private static final Set DEFAULT_DOC_TYPES = new LinkedHashSet();
private static final Set LISTENERS = new HashSet();
+ static {
+ resetDocumentTypes();
+ }
+
+ public static void resetDocumentTypes() {
+ DEFAULT_DOC_TYPES.clear();
+ DEFAULT_DOC_TYPES.addAll(Arrays.asList("page", "post", "masterindex", "archive", "feed"));
+ }
+
+
public static void addDocumentType(String docType) {
DEFAULT_DOC_TYPES.add(docType);
notifyListener(docType);
diff --git a/src/test/java/org/jbake/app/OvenTest.java b/src/test/java/org/jbake/app/OvenTest.java
index 7ab857687..097228660 100644
--- a/src/test/java/org/jbake/app/OvenTest.java
+++ b/src/test/java/org/jbake/app/OvenTest.java
@@ -10,6 +10,7 @@
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.jbake.app.ConfigUtil.Keys;
+import org.jbake.model.DocumentTypes;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -25,7 +26,10 @@ public class OvenTest {
@Before
public void setup() throws Exception {
- URL sourceUrl = this.getClass().getResource("/");
+ // reset values to known state otherwise previous test case runs can affect the success of this test case
+ DocumentTypes.resetDocumentTypes();
+
+ URL sourceUrl = this.getClass().getResource("/");
rootPath = new File(sourceUrl.getFile());
if (!rootPath.exists()) {
throw new Exception("Cannot find base path for test!");
From 10e33bc5a8b6aa7bd22e4202e8e519677e9ceee6 Mon Sep 17 00:00:00 2001
From: Jonathan Bullock
Date: Wed, 10 May 2017 13:50:14 +0100
Subject: [PATCH 09/53] Correctly set flag to say date has been set.
---
src/test/java/org/jbake/FakeDocumentBuilder.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/test/java/org/jbake/FakeDocumentBuilder.java b/src/test/java/org/jbake/FakeDocumentBuilder.java
index 87e873b5f..faf2c90e6 100644
--- a/src/test/java/org/jbake/FakeDocumentBuilder.java
+++ b/src/test/java/org/jbake/FakeDocumentBuilder.java
@@ -42,6 +42,7 @@ public FakeDocumentBuilder withRandomSha1() throws NoSuchAlgorithmException {
public FakeDocumentBuilder withDate(Date date) {
fileModel.put(Crawler.Attributes.DATE, date);
+ hasDate = true;
return this;
}
From fcbdb6e708958fc5256a2f24cf0690ddfe26e9ec Mon Sep 17 00:00:00 2001
From: Frank Becker
Date: Mon, 28 Mar 2016 17:47:12 +0200
Subject: [PATCH 10/53] added gradle application distribution
* clone and zip example project repositories
* add zip files to distribution bundle
* moved test fixture resources to src/test/resources/fixture
to get tests pass with gradle
this.getClass().getResource("/").getFile() resolves to "some/path" whereas
this.getClass().getResource("/fixture").getFile() resolves to "other/path" which makes
it possible to access the fixtures.
---
.gitignore | 2 +
build.gradle | 42 +++++
gradle/application.gradle | 73 ++++++++
gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 52271 bytes
gradle/wrapper/gradle-wrapper.properties | 6 +
gradlew | 164 ++++++++++++++++++
gradlew.bat | 90 ++++++++++
src/{main => dist/lib}/logging/logback.xml | 0
src/test/java/org/jbake/app/AssetTest.java | 14 +-
.../java/org/jbake/app/ConfigUtilTest.java | 24 +--
src/test/java/org/jbake/app/CrawlerTest.java | 21 +--
src/test/java/org/jbake/app/InitTest.java | 118 ++++++-------
.../AbstractTemplateEngineRenderingTest.java | 45 ++++-
...oovyMarkupTemplateEngineRenderingTest.java | 12 +-
.../resources/assets/css/bootstrap.min.css | 9 -
.../assets/css/bootstrap-responsive.min.css | 0
.../fixture/assets/css/bootstrap.min.css | 9 +
.../assets/img/glyphicons-halflings-white.png | Bin
.../assets/img/glyphicons-halflings.png | Bin
.../{ => fixture}/assets/js/bootstrap.min.js | 0
.../{ => fixture}/assets/js/html5shiv.js | 0
.../assets/js/jquery-1.9.1.min.js | 0
.../{ => fixture}/content/about.html | 0
.../{ => fixture}/content/allcontent.html | 0
.../content/blog/2012/first-post.html | 0
.../content/blog/2013/second-post.html | 0
.../content/blog/2016/another-post.html | 0
.../content/blog/2016/draft-post.html | 0
.../content/blog/invalid_header.html | 0
.../{ => fixture}/content/index2.html | 0
.../content/papers/draft-paper.html | 2 +-
.../content/papers/published-paper.html | 2 +-
.../{ => fixture}/content/projects.html | 0
.../custom.properties} | 0
.../freemarkerTemplates/allcontent.ftl | 0
.../freemarkerTemplates/archive.ftl | 0
.../freemarkerTemplates/feed.ftl | 0
.../freemarkerTemplates/footer.ftl | 0
.../freemarkerTemplates/header.ftl | 0
.../freemarkerTemplates/index.ftl | 0
.../freemarkerTemplates/menu.ftl | 0
.../freemarkerTemplates/page.ftl | 0
.../freemarkerTemplates/post.ftl | 0
.../freemarkerTemplates/sitemap.ftl | 0
.../freemarkerTemplates/tags.ftl | 0
.../groovyMarkupTemplates/allcontent.tpl | 2 +
.../groovyMarkupTemplates/archive.tpl | 2 +
.../groovyMarkupTemplates/feed.tpl | 2 +
.../groovyMarkupTemplates/footer.tpl | 2 +
.../groovyMarkupTemplates/header.tpl | 2 +
.../groovyMarkupTemplates/index.tpl | 2 +
.../groovyMarkupTemplates/layout/main.tpl | 2 +
.../groovyMarkupTemplates/menu.tpl | 2 +
.../groovyMarkupTemplates/page.tpl | 2 +
.../groovyMarkupTemplates/paper.tpl | 2 +
.../groovyMarkupTemplates/post.tpl | 2 +
.../groovyMarkupTemplates/sitemap.tpl | 2 +
.../groovyMarkupTemplates/tags.tpl | 2 +
.../groovyTemplates/allcontent.gsp | 0
.../{ => fixture}/groovyTemplates/archive.gsp | 0
.../{ => fixture}/groovyTemplates/feed.gsp | 0
.../{ => fixture}/groovyTemplates/footer.gsp | 0
.../{ => fixture}/groovyTemplates/header.gsp | 0
.../{ => fixture}/groovyTemplates/index.gsp | 0
.../{ => fixture}/groovyTemplates/page.gsp | 0
.../{ => fixture}/groovyTemplates/post.gsp | 0
.../{ => fixture}/groovyTemplates/sitemap.gsp | 0
.../{ => fixture}/groovyTemplates/tags.gsp | 0
.../{ => fixture}/ignorables/.test.txt | 0
.../{ => fixture}/ignorables/test.txt | 0
.../{ => fixture}/jadeTemplates/archive.jade | 0
.../{ => fixture}/jadeTemplates/feed.jade | 0
.../{ => fixture}/jadeTemplates/footer.jade | 0
.../{ => fixture}/jadeTemplates/header.jade | 0
.../{ => fixture}/jadeTemplates/index.jade | 0
.../{ => fixture}/jadeTemplates/layout.jade | 0
.../{ => fixture}/jadeTemplates/page.jade | 0
.../{ => fixture}/jadeTemplates/post.jade | 0
.../{ => fixture}/jadeTemplates/sitemap.jade | 0
.../{ => fixture}/jadeTemplates/tags.jade | 0
src/test/resources/fixture/jbake.properties | 18 ++
.../resources/{ => fixture}/media/favicon.ico | Bin
src/test/resources/{ => fixture}/test.zip | Bin
.../thymeleafTemplates/allcontent.thyme | 0
.../thymeleafTemplates/archive.thyme | 0
.../thymeleafTemplates/feed.thyme | 0
.../thymeleafTemplates/footer.thyme | 0
.../thymeleafTemplates/header.thyme | 0
.../thymeleafTemplates/index.thyme | 0
.../thymeleafTemplates/page.thyme | 0
.../thymeleafTemplates/post.thyme | 0
.../thymeleafTemplates/sitemap.thyme | 0
.../thymeleafTemplates/tags.thyme | 0
93 files changed, 563 insertions(+), 114 deletions(-)
create mode 100644 build.gradle
create mode 100644 gradle/application.gradle
create mode 100644 gradle/wrapper/gradle-wrapper.jar
create mode 100644 gradle/wrapper/gradle-wrapper.properties
create mode 100755 gradlew
create mode 100644 gradlew.bat
rename src/{main => dist/lib}/logging/logback.xml (100%)
delete mode 100644 src/test/resources/assets/css/bootstrap.min.css
rename src/test/resources/{ => fixture}/assets/css/bootstrap-responsive.min.css (100%)
create mode 100644 src/test/resources/fixture/assets/css/bootstrap.min.css
rename src/test/resources/{ => fixture}/assets/img/glyphicons-halflings-white.png (100%)
rename src/test/resources/{ => fixture}/assets/img/glyphicons-halflings.png (100%)
rename src/test/resources/{ => fixture}/assets/js/bootstrap.min.js (100%)
rename src/test/resources/{ => fixture}/assets/js/html5shiv.js (100%)
rename src/test/resources/{ => fixture}/assets/js/jquery-1.9.1.min.js (100%)
rename src/test/resources/{ => fixture}/content/about.html (100%)
rename src/test/resources/{ => fixture}/content/allcontent.html (100%)
rename src/test/resources/{ => fixture}/content/blog/2012/first-post.html (100%)
rename src/test/resources/{ => fixture}/content/blog/2013/second-post.html (100%)
rename src/test/resources/{ => fixture}/content/blog/2016/another-post.html (100%)
rename src/test/resources/{ => fixture}/content/blog/2016/draft-post.html (100%)
rename src/test/resources/{ => fixture}/content/blog/invalid_header.html (100%)
rename src/test/resources/{ => fixture}/content/index2.html (100%)
rename src/test/resources/{ => fixture}/content/papers/draft-paper.html (95%)
rename src/test/resources/{ => fixture}/content/papers/published-paper.html (95%)
rename src/test/resources/{ => fixture}/content/projects.html (100%)
rename src/test/resources/{jbake.properties => fixture/custom.properties} (100%)
rename src/test/resources/{ => fixture}/freemarkerTemplates/allcontent.ftl (100%)
rename src/test/resources/{ => fixture}/freemarkerTemplates/archive.ftl (100%)
rename src/test/resources/{ => fixture}/freemarkerTemplates/feed.ftl (100%)
rename src/test/resources/{ => fixture}/freemarkerTemplates/footer.ftl (100%)
rename src/test/resources/{ => fixture}/freemarkerTemplates/header.ftl (100%)
rename src/test/resources/{ => fixture}/freemarkerTemplates/index.ftl (100%)
rename src/test/resources/{ => fixture}/freemarkerTemplates/menu.ftl (100%)
rename src/test/resources/{ => fixture}/freemarkerTemplates/page.ftl (100%)
rename src/test/resources/{ => fixture}/freemarkerTemplates/post.ftl (100%)
rename src/test/resources/{ => fixture}/freemarkerTemplates/sitemap.ftl (100%)
rename src/test/resources/{ => fixture}/freemarkerTemplates/tags.ftl (100%)
rename src/test/resources/{ => fixture}/groovyMarkupTemplates/allcontent.tpl (83%)
rename src/test/resources/{ => fixture}/groovyMarkupTemplates/archive.tpl (96%)
rename src/test/resources/{ => fixture}/groovyMarkupTemplates/feed.tpl (95%)
rename src/test/resources/{ => fixture}/groovyMarkupTemplates/footer.tpl (87%)
rename src/test/resources/{ => fixture}/groovyMarkupTemplates/header.tpl (97%)
rename src/test/resources/{ => fixture}/groovyMarkupTemplates/index.tpl (93%)
rename src/test/resources/{ => fixture}/groovyMarkupTemplates/layout/main.tpl (90%)
rename src/test/resources/{ => fixture}/groovyMarkupTemplates/menu.tpl (93%)
rename src/test/resources/{ => fixture}/groovyMarkupTemplates/page.tpl (92%)
rename src/test/resources/{ => fixture}/groovyMarkupTemplates/paper.tpl (93%)
rename src/test/resources/{ => fixture}/groovyMarkupTemplates/post.tpl (93%)
rename src/test/resources/{ => fixture}/groovyMarkupTemplates/sitemap.tpl (92%)
rename src/test/resources/{ => fixture}/groovyMarkupTemplates/tags.tpl (97%)
rename src/test/resources/{ => fixture}/groovyTemplates/allcontent.gsp (100%)
rename src/test/resources/{ => fixture}/groovyTemplates/archive.gsp (100%)
rename src/test/resources/{ => fixture}/groovyTemplates/feed.gsp (100%)
rename src/test/resources/{ => fixture}/groovyTemplates/footer.gsp (100%)
rename src/test/resources/{ => fixture}/groovyTemplates/header.gsp (100%)
rename src/test/resources/{ => fixture}/groovyTemplates/index.gsp (100%)
rename src/test/resources/{ => fixture}/groovyTemplates/page.gsp (100%)
rename src/test/resources/{ => fixture}/groovyTemplates/post.gsp (100%)
rename src/test/resources/{ => fixture}/groovyTemplates/sitemap.gsp (100%)
rename src/test/resources/{ => fixture}/groovyTemplates/tags.gsp (100%)
rename src/test/resources/{ => fixture}/ignorables/.test.txt (100%)
rename src/test/resources/{ => fixture}/ignorables/test.txt (100%)
rename src/test/resources/{ => fixture}/jadeTemplates/archive.jade (100%)
rename src/test/resources/{ => fixture}/jadeTemplates/feed.jade (100%)
rename src/test/resources/{ => fixture}/jadeTemplates/footer.jade (100%)
rename src/test/resources/{ => fixture}/jadeTemplates/header.jade (100%)
rename src/test/resources/{ => fixture}/jadeTemplates/index.jade (100%)
rename src/test/resources/{ => fixture}/jadeTemplates/layout.jade (100%)
rename src/test/resources/{ => fixture}/jadeTemplates/page.jade (100%)
rename src/test/resources/{ => fixture}/jadeTemplates/post.jade (100%)
rename src/test/resources/{ => fixture}/jadeTemplates/sitemap.jade (100%)
rename src/test/resources/{ => fixture}/jadeTemplates/tags.jade (100%)
create mode 100644 src/test/resources/fixture/jbake.properties
rename src/test/resources/{ => fixture}/media/favicon.ico (100%)
rename src/test/resources/{ => fixture}/test.zip (100%)
rename src/test/resources/{ => fixture}/thymeleafTemplates/allcontent.thyme (100%)
rename src/test/resources/{ => fixture}/thymeleafTemplates/archive.thyme (100%)
rename src/test/resources/{ => fixture}/thymeleafTemplates/feed.thyme (100%)
rename src/test/resources/{ => fixture}/thymeleafTemplates/footer.thyme (100%)
rename src/test/resources/{ => fixture}/thymeleafTemplates/header.thyme (100%)
rename src/test/resources/{ => fixture}/thymeleafTemplates/index.thyme (100%)
rename src/test/resources/{ => fixture}/thymeleafTemplates/page.thyme (100%)
rename src/test/resources/{ => fixture}/thymeleafTemplates/post.thyme (100%)
rename src/test/resources/{ => fixture}/thymeleafTemplates/sitemap.thyme (100%)
rename src/test/resources/{ => fixture}/thymeleafTemplates/tags.thyme (100%)
diff --git a/.gitignore b/.gitignore
index 069485ec9..dc6be0687 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,5 @@
/.idea
*.iml
*~
+build/
+.gradle
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 000000000..a982e2b2d
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,42 @@
+apply plugin: 'java'
+apply plugin: 'eclipse'
+apply plugin: 'maven'
+apply from: 'gradle/application.gradle'
+
+group = 'org.jbake'
+version = '2.5.0-SNAPSHOT'
+
+description = """jbake"""
+
+sourceCompatibility = 1.6
+targetCompatibility = 1.6
+
+
+repositories {
+ jcenter()
+}
+
+dependencies {
+ compile group: 'commons-io', name: 'commons-io', version:'2.4'
+ compile group: 'commons-configuration', name: 'commons-configuration', version:'1.9'
+ compile group: 'com.googlecode.json-simple', name: 'json-simple', version:'1.1.1'
+ compile group: 'args4j', name: 'args4j', version:'2.0.26'
+ compile group: 'org.freemarker', name: 'freemarker', version:'2.3.20'
+ compile group: 'com.orientechnologies', name: 'orient-commons', version:'1.6.4'
+ compile group: 'com.orientechnologies', name: 'orientdb-core', version:'1.6.4'
+ compile group: 'org.pegdown', name: 'pegdown', version:'1.4.2'
+ compile group: 'org.asciidoctor', name: 'asciidoctorj', version:'1.5.2'
+ compile group: 'org.eclipse.jetty', name: 'jetty-server', version:'8.1.12.v20130726'
+ compile group: 'org.codehaus.groovy', name: 'groovy', version:'2.3.6'
+ compile group: 'org.codehaus.groovy', name: 'groovy-templates', version:'2.3.6'
+ compile group: 'org.thymeleaf', name: 'thymeleaf', version:'2.1.3.RELEASE'
+ compile group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-conditionalcomments', version:'2.1.1.RELEASE'
+ compile group: 'org.slf4j', name: 'slf4j-api', version:'1.7.6'
+ compile group: 'org.slf4j', name: 'jul-to-slf4j', version:'1.7.6'
+ compile group: 'ch.qos.logback', name: 'logback-classic', version:'1.1.1'
+ compile group: 'ch.qos.logback', name: 'logback-core', version:'1.1.1'
+ compile "de.neuland-bfi:jade4j:0.4.2"
+ compile "org.apache.commons:commons-vfs2:2.0"
+ testCompile group: 'junit', name: 'junit', version:'4.11'
+ testCompile group: 'org.assertj', name: 'assertj-core', version:'1.7.0'
+}
diff --git a/gradle/application.gradle b/gradle/application.gradle
new file mode 100644
index 000000000..09307e3a8
--- /dev/null
+++ b/gradle/application.gradle
@@ -0,0 +1,73 @@
+import org.ajoberstar.grgit.Grgit
+
+buildscript {
+ repositories {
+ jcenter()
+ }
+ dependencies {
+ classpath 'org.ajoberstar:grgit:1.5.0'
+ }
+}
+
+apply plugin: 'application'
+
+mainClassName = "org.jbake.launcher.Main"
+applicationName = "jbake"
+
+ext {
+ examplesBase = "$project.buildDir/examples"
+
+ exampleRepositories = [
+ "example_project_freemarker": "git://github.com/jbake-org/jbake-example-project-freemarker.git",
+ "example_project_groovy" : "git://github.com/jbake-org/jbake-example-project-groovy.git",
+ "example_project_thymeleaf" : "git://github.com/jbake-org/jbake-example-project-thymeleaf.git"
+ ]
+}
+
+task cloneProjectExampleRepositories() {
+ group = "distribution"
+ description "Clone jbake example project repositories"
+
+ outputs.dir examplesBase
+
+ doLast {
+ exampleRepositories.each { name, repository ->
+ Grgit.clone(dir: "$examplesBase/$name", uri: repository)
+ }
+ }
+}
+
+//create Zip Task for each repository
+exampleRepositories.each { name, repository ->
+
+ task "zip${name}Repository"(type: Zip, dependsOn: cloneProjectExampleRepositories) {
+ group "distribution"
+ description "Zip $name repository"
+
+ baseName = name
+ archiveName = "${baseName}.zip"
+
+ from "$examplesBase/$baseName"
+ exclude 'README.md'
+ exclude 'LICENSE'
+ exclude '.git'
+ }
+
+}
+
+startScripts {
+ classpath += files("logging")
+}
+
+distributions {
+ main {
+ baseName = "jbake"
+ contents {
+ //Include Outputs from zip tasks
+ exampleRepositories.each { name, repository ->
+ def task = project.tasks.getByName("zip${name}Repository")
+ from(task)
+ }
+ }
+ }
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..30d399d8d2bf522ff5de94bf434a7cc43a9a74b5
GIT binary patch
literal 52271
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