InitCommand.java
package org.docascode.api;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.docascode.api.core.errors.DocAsCodeException;
import org.docascode.api.core.DocAsCodeRepository;
import org.docascode.api.event.Event;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.api.errors.GitAPIException;
import javax.xml.XMLConstants;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class InitCommand extends DocAsCodeCommand<DocAsCode> {
private static final String DOCASCODE_DOCX = "docascode-docx";
private File directory;
public InitCommand setDirectory(File directory){
this.directory = directory;
return this;
}
@Override
public DocAsCode call() throws DocAsCodeException {
Git git;
try {
git = Git.init().setDirectory(directory).call();
} catch (GitAPIException e) {
throw new DocAsCodeException(
String.format("Unable to initialize a DocAsCode repository in '%s'",
directory), e);
}
try {
Status s = git.status().call();
if(s.hasUncommittedChanges()){
log("Working tree contains unstaged changes. Aborting.", Event.Level.WARN);
return new DocAsCode(null);
}
} catch (GitAPIException e) {
throw new DocAsCodeException("Unable to get status of git repository " + directory.getAbsolutePath(), e);
}
DocAsCodeRepository repo;
try {
repo = new DocAsCodeRepository(git.getRepository());
} catch (IOException e) {
throw new DocAsCodeException("Unable to handle DocAsCode repository.",e);
}
String versionString = repo.getVersion();
if (versionString == null) {
initialize(repo);
} else {
DefaultArtifactVersion version = new DefaultArtifactVersion(versionString);
switch (version.getMajorVersion()){
case 3:
upgradeChronoXML(repo);
try {
Files.delete(Paths.get(
String.format("%s/%s",
repo.getWorkTree(),
".docascode/docascode.xml")));
} catch (IOException e) {
throw new DocAsCodeException("Unable to delete .docascode/docascode.xml",e);
}
break;
case 4:
break;
default:
throw new DocAsCodeException(
String.format("Unhandled version ('%s')of DocAsCode repository...",versionString));
}
}
new ConfigCommand(repo)
.setAction(ConfigCommand.Action.SET)
.setLevel(ConfigCommand.Level.PROJECT)
.setSection("docascode")
.setSubsection(null)
.setName("version")
.setValue(DocAsCode.getVersion())
.call();
return new DocAsCode(repo);
}
private void initialize(DocAsCodeRepository repo) throws DocAsCodeException {
copyResource("org/docascode/init/config",repo.git().getProjectConfigFile());
copyResource("org/docascode/init/delivery/chrono.xml",repo.getChronoXML());
copyResource("org/docascode/init/delivery/delivery.xml",repo.getDeliveryXML());
copyResource("org/docascode/init/delivery/delivery.properties",repo.getDeliveryProperties());
copyHook("org/docascode/init/hooks/pre-commit",repo.git().getPreCommit());
copyHook("org/docascode/init/hooks/post-commit",repo.git().getPostCommit());
ConfigCommand config = new ConfigCommand(repo);
config.setAction(ConfigCommand.Action.ADD)
.setSection("include")
.setSubsection(null)
.setName("path")
.setValue("../.docascode/config")
.setLevel(ConfigCommand.Level.LOCAL)
.call();
config.setAction(ConfigCommand.Action.SET)
.setSection("merge")
.setSubsection(DOCASCODE_DOCX)
.setName("driver")
.setValue("docascode merge --ancestor %O --current %A --other %B --placeholder %P --format docx")
.setLevel(ConfigCommand.Level.LOCAL)
.call();
config.setAction(ConfigCommand.Action.SET)
.setSection("merge")
.setSubsection("keep-mine")
.setName("driver")
.setValue("true")
.setLevel(ConfigCommand.Level.LOCAL)
.call();
config.setAction(ConfigCommand.Action.SET)
.setSection("diff")
.setSubsection(DOCASCODE_DOCX)
.setName("textconv")
.setValue("pandoc --to=markdown")
.setLevel(ConfigCommand.Level.LOCAL)
.call();
config.setAction(ConfigCommand.Action.SET)
.setSection("diff")
.setSubsection(DOCASCODE_DOCX)
.setName("prompt")
.setValue("false")
.setLevel(ConfigCommand.Level.LOCAL)
.call();
File gitattributes = new File(repo.getWorkTree(),".gitattributes");
if (!gitattributes.exists()){
try {
Files.createFile(Paths.get(gitattributes.getAbsolutePath()));
} catch (IOException e) {
throw new DocAsCodeException("Unable to create .gitattributes file.",e);
}
}
replace(gitattributes,
"\\*.docx merge=*",
"*.docx merge=docascode-docx diff=docascode-docx");
replace(gitattributes,
"\\.docascode/\\*\\*/\\*.md merge=",
".docascode/**/*.md merge=keep-mine");
mergeGitIgnore(new File(repo.getWorkTree().getAbsolutePath()+"/.gitignore"));
log(
String.format("Successfully initialized DocAsCode Repository in '%s'.",
directory), Event.Level.SUCESS);
}
private void copyResource(String resourcePath, File destFile) throws DocAsCodeException {
InputStream src = getClass().getClassLoader().getResourceAsStream(resourcePath);
try {
copyInputStreamToFile(src,destFile);
} catch (IOException e) {
throw new DocAsCodeException(
String.format("Unable to copy resource '%s' to '%s'.",
resourcePath,
destFile),e);
}
}
private void copyHook(String resourcePath, File destHook) throws DocAsCodeException {
if (destHook.exists()){
InputStream src = getClass().getClassLoader().getResourceAsStream(resourcePath);
if (src != null) {
String content;
try {
content = IOUtils.toString(src, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new DocAsCodeException(
"Unable to read hook content", e);
}
log(String.format(
"Hook '%s' already exists. Please check that it contains the following content:%n%n%s%n",
destHook.getPath(),
content),
Event.Level.WARN);
}
} else {
copyResource(resourcePath,destHook);
if (!destHook.setExecutable(true)) {
throw new DocAsCodeException(
String.format(
"Unable to make '%s' executable.",
getRepository().git().getPreCommit()
)
);
}
}
}
private void copyInputStreamToFile(InputStream source, File destination) throws IOException {
if (!destination.exists()){
FileUtils.copyInputStreamToFile(source,destination);
}
}
private void replace(File file, String pattern, String replace) throws DocAsCodeException {
List<String> result = new ArrayList<>();
Pattern regexp = Pattern.compile(pattern);
boolean missing = true;
Matcher matcher = regexp.matcher("");
try (
BufferedReader reader = Files.newBufferedReader(file.toPath(),
StandardCharsets.UTF_8);
LineNumberReader lineReader = new LineNumberReader(reader)
){
String line;
while ((line = lineReader.readLine()) != null) {
matcher.reset(line); //reset the input
if (matcher.find()) {
line = replace;
missing = false;
}
result.add(line);
}
if (missing){
result.add(replace);
}
try (FileOutputStream fos=new FileOutputStream(file,false)) {
for (String s : result) {
fos.write(s.getBytes());
fos.write("\n".getBytes());
}
}
}
catch (IOException e){
throw new DocAsCodeException("",e);
}
}
private void mergeGitIgnore(File gitignore) throws DocAsCodeException {
List<String> lines = new ArrayList<>();
String line;
if (!gitignore.exists()) {
try {
Files.createFile(Paths.get(gitignore.getAbsolutePath()));
} catch (IOException e) {
throw new DocAsCodeException("Unable to create .gitignore file.", e);
}
}
try (BufferedReader reader = Files.newBufferedReader(gitignore.toPath(),
StandardCharsets.UTF_8);
LineNumberReader lineReader = new LineNumberReader(reader)){
while ((line = lineReader.readLine()) != null) {
if(!lines.contains(line))
{
lines.add(line);
}
}
} catch (IOException e) {
throw new DocAsCodeException("Unable to read .gitignore file.",e);
}
InputStream gitIgnoreResource = getClass().getClassLoader().getResourceAsStream("org/docascode/init/gitignore");
if (gitIgnoreResource == null){
throw new DocAsCodeException("Unable to read .gitignore resource file.");
}
try(FileOutputStream fos=new FileOutputStream(gitignore,false)) {
BufferedReader gitIgnoreResourceReader = new BufferedReader(new InputStreamReader(gitIgnoreResource));
while ((line = gitIgnoreResourceReader.readLine()) != null) {
if(!lines.contains(line))
{
lines.add(line);
}
}
for (String s : lines) {
fos.write(s.getBytes());
fos.write("\n".getBytes());
}
} catch (IOException e) {
throw new DocAsCodeException("Unable to merge local .gitignore with DocAsCode .gitignore");
}
}
private void upgradeChronoXML(DocAsCodeRepository repository) throws DocAsCodeException{
try {
TransformerFactory factory = TransformerFactory.newInstance();
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream("org/docascode/api/core/delivery-1.0-to-2.0.xsl");
StreamSource xslt = new StreamSource(resourceAsStream);
Transformer transformer = factory.newTransformer(xslt);
File oldChrono =
new File(repository.getWorkTree(),"chrono.xml");
StreamSource source = new StreamSource(oldChrono);
StreamResult result = new StreamResult(repository.getChronoXML());
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
transformer.transform(source, result);
Files.delete(Paths.get(oldChrono.getAbsolutePath()));
log("Successfully upgraded chrono.xml", Event.Level.SUCESS);
} catch (TransformerException | IOException e) {
throw new DocAsCodeException("Unable to upgrade chrono.xml.",e);
}
}
}