package com.redhat.ceylon.tools.src;
import java.io.File;
import java.io.IOException;
import java.util.List;
import com.redhat.ceylon.cmr.api.ArtifactContext;
import com.redhat.ceylon.cmr.api.ModuleQuery;
import com.redhat.ceylon.cmr.ceylon.RepoUsingTool;
import com.redhat.ceylon.cmr.impl.IOUtils;
import com.redhat.ceylon.common.FileUtil;
import com.redhat.ceylon.common.config.DefaultToolOptions;
import com.redhat.ceylon.common.tool.Argument;
import com.redhat.ceylon.common.tool.Description;
import com.redhat.ceylon.common.tool.OptionArgument;
import com.redhat.ceylon.common.tool.RemainingSections;
import com.redhat.ceylon.common.tool.Summary;
import com.redhat.ceylon.common.tools.CeylonTool;
import com.redhat.ceylon.common.tools.ModuleSpec;
import com.redhat.ceylon.model.cmr.ArtifactResult;
@Summary("Fetches source archives from a repository and extracts their contents into a source directory")
@Description("Fetches any sources, resources, documentation and scripts " +
"that can be found for given `module` from the " +
"first configured repository to contain the module and extracts " +
"the them into their respective output directories. Multiple modules " +
"can be given.\n" +
"\n" +
"This tool is especially useful for working with example projects.")
@RemainingSections("## Examples\n" +
"\n" +
"A typical workflow might be:\n" +
"\n" +
" mkdir my-project\n" +
" cd my-project\n" +
" ceylon src org.example.foo\n" +
" ceylon compile org.example.foo\n" +
" ceylon run org.example.foo\n" +
"")
public class CeylonSrcTool extends RepoUsingTool {
private File src = DefaultToolOptions.getCompilerSourceDirs().get(0);
private File resource = DefaultToolOptions.getCompilerResourceDirs().get(0);
private File doc = DefaultToolOptions.getCompilerDocDirs().get(0);
private File script = DefaultToolOptions.getCompilerScriptDirs().get(0);
private String resourceRoot = DefaultToolOptions.getCompilerResourceRootName();
private List<ModuleSpec> modules;
public CeylonSrcTool() {
super(CeylonSrcMessages.RESOURCE_BUNDLE);
}
@OptionArgument(shortName='s', longName="src", argumentName="dir")
@Description("The output source directory (default: `./source`)")
public void setSrc(File directory) {
this.src = directory;
}
@OptionArgument(longName="source", argumentName="dir")
@Description("An alias for `--src`" +
" (default: `./source`)")
public void setSource(File source) {
setSrc(source);
}
@OptionArgument(shortName='r', longName="resource", argumentName="dir")
@Description("The output resource directory (default: `./resource`)")
public void setResource(File resource) {
this.resource = resource;
}
@OptionArgument(longName="doc", argumentName="dirs")
@Description("The output doc directory (default: `./doc`)")
public void setDocFolders(File doc) {
this.doc = doc;
}
@OptionArgument(shortName='x', longName="script", argumentName="dir")
@Description("The output script directory (default: `./script`)")
public void setScriptFolders(File script) {
this.script = script;
}
@OptionArgument(shortName='R', argumentName="folder-name")
@Description("Sets the special resource folder name whose files will " +
"end up in the root of the resulting module CAR file (default: ROOT).")
public void setResourceRoot(String resourceRoot) {
this.resourceRoot = resourceRoot;
}
@Argument(argumentName="module", multiplicity="+")
public void setModules(List<String> modules) {
setModuleSpecs(ModuleSpec.parseEachList(modules));
}
public void setModuleSpecs(List<ModuleSpec> modules) {
this.modules = modules;
}
@Override
protected boolean needsSystemRepo() {
return false;
}
@Override
public void initialize(CeylonTool mainTool) {
}
@Override
public void run() throws Exception {
// First check if all the arguments point to source archives
for (ModuleSpec module : modules) {
if (module != ModuleSpec.DEFAULT_MODULE && !module.isVersioned()) {
if (checkModuleVersionsOrShowSuggestions(getRepositoryManager(), module.getName(), null, ModuleQuery.Type.SRC, null, null) == null) {
return;
}
}
}
// If all are correct we unpack them
for (ModuleSpec module : modules) {
String version = module.getVersion();
if (module != ModuleSpec.DEFAULT_MODULE && !module.isVersioned()) {
version = checkModuleVersionsOrShowSuggestions(getRepositoryManager(), module.getName(), null, ModuleQuery.Type.SRC, null, null);
}
msg("retrieving.module", module).newline();
ArtifactContext allArtifacts = new ArtifactContext(module.getName(), version, ArtifactContext.SRC, ArtifactContext.RESOURCES, ArtifactContext.DOCS, ArtifactContext.SCRIPTS_ZIPPED);
List<ArtifactResult> results = getRepositoryManager().getArtifactResults(allArtifacts);
if (results == null) {
String err = getModuleNotFoundErrorMessage(getRepositoryManager(), module.getName(), module.getVersion());
errorAppend(err);
errorNewline();
continue;
}
String modFolder = module.getName().replace('.', File.separatorChar);
boolean hasSources = false;
for (ArtifactResult result : results) {
String suffix = ArtifactContext.getSuffixFromFilename(result.artifact().getName());
if (ArtifactContext.SRC.equals(suffix)) {
append(" ").msg("extracting.sources").newline();
extractArchive(result, applyCwd(src), "source");
hasSources = true;
} else if (ArtifactContext.SCRIPTS_ZIPPED.equals(suffix)) {
append(" ").msg("extracting.scripts").newline();
extractArchive(result, new File(applyCwd(script), modFolder), "script");
} else if (ArtifactContext.RESOURCES.equals(suffix)) {
append(" ").msg("extracting.resources").newline();
copyResources(result, applyCwd(resource));
} else if (ArtifactContext.DOCS.equals(suffix)) {
append(" ").msg("extracting.docs").newline();
copyFiles(result, "doc", new File(applyCwd(doc), modFolder), "doc");
}
}
if (!hasSources) {
msg("no.sources.found", module).newline();
}
}
}
private void extractArchive(ArtifactResult result, File dir, String name) throws IOException {
try{
IOUtils.extractArchive(result.artifact(), dir);
}catch(IOUtils.UnzipException x){
switch(x.failure){
case CannotCreateDestination:
throw new RuntimeException(CeylonSrcMessages.msg("unable.create.output.dir", name, x.dir));
case CopyError:
throw new RuntimeException(CeylonSrcMessages.msg("unable.extract.entry", x.entryName, result.artifact().getAbsolutePath()), x.getCause());
case DestinationNotDirectory:
throw new RuntimeException(CeylonSrcMessages.msg("not.dir.output.dir", name, x.dir));
default:
throw x;
}
}
}
private void copyFiles(ArtifactResult result, String fromSubDir, File destDir, String name) {
File fromDir = result.artifact();
if (fromSubDir != null) {
fromDir = new File(fromDir, fromSubDir);
}
if (!fromDir.isDirectory()) {
throw new RuntimeException(CeylonSrcMessages.msg("not.dir.input.dir", name, destDir));
}
if (!destDir.exists() && !destDir.mkdirs()) {
throw new RuntimeException(CeylonSrcMessages.msg("unable.create.output.dir", name, destDir));
}
if (!destDir.isDirectory()) {
throw new RuntimeException(CeylonSrcMessages.msg("not.dir.output.dir", name, destDir));
}
try {
FileUtil.copyAll(fromDir, destDir);
} catch (IOException ex) {
throw new RuntimeException(CeylonSrcMessages.msg("unable.copy", name, fromDir), ex);
}
}
private void copyResources(ArtifactResult result, File destDir) {
String[] parts = result.name().split("\\.");
// First we copy the main resource files
copyFiles(result, parts[0], new File(destDir, parts[0]), "resource");
// And now any root resources if they exist
String modFolder = result.name().replace('.', File.separatorChar);
File destRoot = new File(new File(destDir, modFolder), resourceRoot);
for (File f : result.artifact().listFiles()) {
if (!f.getName().equals(parts[0])) {
// We have found a root resource
try {
FileUtil.copyAll(f, destRoot);
} catch (IOException ex) {
throw new RuntimeException(CeylonSrcMessages.msg("unable.copy", "resource", f), ex);
}
}
}
}
}