HootMain.java
package Hoot.Compiler;
import java.io.*;
import java.util.*;
import org.apache.commons.cli.*;
import org.apache.commons.cli.Option.*;
import org.apache.commons.lang3.ArrayUtils;
import Hoot.Runtime.Faces.*;
import Hoot.Runtime.Maps.Package;
import Hoot.Runtime.Values.Variable;
import static Hoot.Compiler.Scopes.File.*;
import static Hoot.Runtime.Maps.Package.*;
import static Hoot.Runtime.Names.Primitive.*;
import static Hoot.Runtime.Functions.Utils.*;
import static Hoot.Runtime.Functions.Exceptional.*;
import static Hoot.Runtime.Maps.Library.SourceFileType;
import static Hoot.Runtime.Maps.Library.loadBasePaths;
/**
* Compiles Hoot code to Java classes and types.
* Takes command line arguments and thereby instructs the compiler.
*
* @author nik <nikboyd@sonic.net>
* @see "Copyright 2010,2021 Nikolas S Boyd."
* @see "Permission is granted to copy this work provided this copyright statement is retained in all copies."
*/
public class HootMain implements Logging {
public static final String Source = "source";
public static final String TestSource = "test-source";
public static final String SourceTest = "src/test/hoot";
public static final String SourcePath = "src/main/hoot";
public static final String Target = "target";
public static final String TargetTest = "target/generated-test-sources/hoot-maven-plugin";
public static final String TargetPath = "target/generated-sources/hoot-maven-plugin";
static final String VersionReport = "found JDK %s, generated code will be compatible with JDK %s";
static { // check and report java version
String message = String.format(VersionReport, Variable.javaVersion(), Variable.javaCompatibility());
printLine(); printLine(message);
}
public static void main(String... args) { mainly().parseCommand(args).runCommand(); }
public static HootMain mainly() { return new HootMain(); }
final CommandLineParser parser = new DefaultParser();
CommandLine parseArgs(String[] args) { return nullOrTryLoudly(() -> parser.parse(availableOptions(), args)); }
CommandLine command;
HootMain parseCommand(String[] args) { command = parseArgs(args); return this; }
HootMain runCommand() { if (helpWanted()) printHelp(); else compilePackages(); return this; }
boolean helpWanted() { return falseOr((c) -> c.hasOption(Help), command); }
boolean testWanted() { return falseOr((c) -> c.hasOption(TestOnly), command); }
boolean hasMainSource() { return falseOr((c) -> c.hasOption(Source), command); }
boolean hasTestSource() { return falseOr((c) -> c.hasOption(TestSource), command); }
boolean needsDefaultSource() { return !hasMainSource() && !hasTestSource(); }
static String normalPath(String p) { return Package.normalPath(p); }
String defaultSourcePath() { return defaultSourceFolder().getPath(); }
File defaultSourceFolder() {
return new File(removeTail(normalPath(targetTail()), targetFolder.getPath()), normalPath(sourceTail())); }
String removeTail(String tail, String path) {
return (path.endsWith(tail)) ? path.substring(0, path.length() - tail.length()) : path; }
String targetTail() { return hasTestSource() ? TargetTest : TargetPath; }
String sourceTail() { return hasTestSource() ? SourceTest : SourcePath; }
String selectedSource() { // figure intended source from supplied args
return hasTestSource() ? testSourcePath() :
hasMainSource() ? sourcePath() : defaultSourcePath(); }
String targetPath() { return emptyOr((c) -> c.getOptionValue(Folder, targetTail()), command); }
String sourcePath() { return emptyOr((c) -> c.getOptionValue(Source, sourceTail()), command); }
String sourceType() { return emptyOr((c) -> c.getOptionValue(Language, SourceFileType), command); }
String testTargetPath() { return emptyOr((c) -> c.getOptionValue(Folder, TargetTest), command); }
String testSourcePath() { return emptyOr((c) -> c.getOptionValue(TestSource, SourceTest), command); }
static final String WorkPath = "working";
public static final String BasePath = "user.dir";
String basePath() { return emptyOr((c) -> c.getOptionValue(Base, systemValue(BasePath)), command); }
final List<String> packageList = emptyList(String.class);
List<String> packages() { return this.packageList; }
boolean noPackages() { return packages().isEmpty(); }
boolean somePackages() { return !noPackages(); }
List<String> packageList() { return wrap(command.getOptionValues(Packages)); }
void cachePackages() {
packages().addAll(selectList(packageList(), p -> !p.isEmpty()));
if (noPackages()) {
packages().addAll(collectPackagesUnder(sourceFolder));
}
}
List<String> collectPackagesUnder(File hootFolder) {
return collectList(list -> {
File[] subFolders = hootFolder.listFiles(FolderFilter);
wrap(subFolders).forEach(f -> {
File packageFolder = new File(hootFolder, f.getName());
if (packageFolder.isDirectory()) {
String packageName = Package.nameFrom(sourceFolder, packageFolder);
list.addAll(collectPackagesUnder(packageFolder));
list.add(packageName);
}
});
});
}
static final String[] Skipped = { "resources", "java" };
static final List<String> SkippedFolders = wrap(Skipped);
static final FileFilter FolderFilter = (f) -> f.isDirectory() && !SkippedFolders.contains(f.getName());
final List<String> libsList = emptyList(String.class);
List<String> libs() { return this.libsList; }
void cacheLibs() { libs().addAll(libsList()); }
List<String> libsList() { return wrap(command.getOptionValues(Libs)); }
public static final String Compiler = "hoot-compiler";
void printHelp() { helpPrinter().printHelp(Compiler, availableOptions()); }
HelpFormatter helpPrinter() {
HelpFormatter f = new HelpFormatter();
f.setWidth(120);
return f;
}
Options availableOptions() { return collectWith(new Options(), listOptions(), (opts,opt) -> opts.addOption(opt)); }
List<Option> listOptions() { return wrap(
baseOption(), sourceOption(), targetOption(), packageOption(),
testSourceOption(), testOption(), helpOption(), languageOption()
); }
String basePath;
File baseFolder;
File sourceFolder;
File targetFolder;
void prepareFolders() {
// validate the paths
basePath = basePath();
baseFolder = locate(WorkPath, basePath);
if (baseFolder == null) return;
targetFolder = locate(Target, targetPath(), targetTail());
sourceFolder = locate(Source, selectedSource(), sourceTail());
}
static final String Adding = "adding";
static final String RelativePrefix = "../";
static final String ClassesPath = "target/classes";
final List<File> basicPaths = emptyList(File.class);
void addBasicPath(String relativePath) { basicPaths.add(locate(Adding, relativePath, ClassesPath)); }
void addQualifiedPath(String relativePath, String qualifier) {
if (basePath.endsWith(qualifier)) basicPaths.add(locate(Adding, RelativePrefix + relativePath, ClassesPath)); }
void addPath(String relativePath, String... qualifiers) {
if (hasAny(qualifiers)) addQualifiedPath(relativePath, qualifiers[0]); else addBasicPath(relativePath); }
void loadPaths() {
File[] paths = { sourceFolder, targetFolder, };
basicPaths.addAll(wrap(paths));
loadBasePaths(wrap(paths));
UnitFactory = StandardUnitFactory;
Hoot.Compiler.Scopes.File.fileType(sourceType());
}
static final String Comparison = "comparing: '%s' and '%s'";
File locate(String name, String... paths) { return reportFolder(name, locateCode(name, paths)); }
File locateCode(String folderName, String... folderPaths) {
File folder = new File(normalPath(folderPaths[0]));
if (WorkPath.equals(folderName) || folder.isAbsolute()) return folder;
if (folderPaths.length == 1) return new File(baseFolder, normalPath(folderPaths[0]));
return locateRelative(folderName, normalPath(folderPaths[0]), normalPath(folderPaths[1])) ;
}
// paths have already been normalized
File locateRelative(String folderName, String relativePath, String soughtPath) {
File folder = new File(relativePath);
if (relativePath.endsWith(soughtPath)) return folder;
File possible = new File(folder, soughtPath);
if (Target.equals(folderName)) return possible;
return possible.exists() ? possible : folder;
}
static final String Pad = " ";
File reportFolder(String folderName, File folder) {
if (folderName.length() < Missing.length())
folderName = Pad + folderName; // adjust name
if (folder.exists()) { // found folder
reportLoudly(folderName, folder); return folder; }
else if (Target.equals(folderName.trim())) {
folder.mkdirs(); // create a missing target
reportLoudly(Created, folder); return folder; }
else { // report missing folder
reportLoudly(Missing, folder); return null; }
}
static final String Created = " CREATED";
static final String Missing = " WITHOUT";
static final String FolderFound = "%s folder = %s";
void reportLoudly(String folderName, File folder) {
runLoudly(() -> report(format(FolderFound, folderName, folder.getCanonicalPath()))); }
void compilePackages() { prepareCompile(); compileOrTest(); }
void prepareCompile() { prepareFolders(); cacheLibs(); cachePackages(); loadPaths(); }
void compileOrTest() { if (testWanted()) reportPackages(); else compileAllPackages(); }
void compileAllPackages() { packages().forEach(p -> compilePackage(Package.named(p))); }
static final String PackageReport = "packages: %s";
void reportPackages() { report(format(PackageReport, joinWith(Blank, packages()))); testPackages(); }
void testPackages() { packages().forEach((p) -> { reportPackage(Package.named(p)); }); }
static final String PackageFound = "source folder located: %s";
static final String PackageMissing = "no " + PackageFound;
void reportPackage(Package p) { File folder = p.sourceFolder();
String reportFormat = folder.exists() ? PackageFound : PackageMissing;
runLoudly(() -> report(format(reportFormat, folder.getCanonicalPath()))); }
static final String HootFileType = ".hoot";
static final FileFilter HootFilter = (f) -> isHoot(f);
static boolean isHoot(File f) { return f.isFile() && f.getPath().endsWith(HootFileType); }
static final String Translation = "translating %s";
void compilePackage(Package p) {
if (!p.sourceFaces().isEmpty()) {
report(Empty);
report(format(Translation, p.name()));
runLoudly(() -> { // any throwable is seriously jacked! --nik
Map<String, UnitFile> fileMap = p.parseSources();
fileMap.values().forEach(f -> f.compile());
});
}
}
static final String Dash = "-";
static final String Optional = Dash + Dash;
public static final String Help = "help";
Option helpOption() { return buildOption(Help, "optional: displays this help").build(); }
public static final String TestOnly = "only-test";
Option testOption() { return buildOption(TestOnly, "optional: only tests compile arguments").build(); }
public static final String Test = "test"; // note mojo alias for only-test
public static final String[] Optionals = { Help, Test };
public static final Map<String, String> OptionalBools = emptyMap();
static {
OptionalBools.put(Help, "false");
OptionalBools.put(Test, "false");
}
static final String Base = "base";
Option baseOption() { return baseOptBuilder().optionalArg(true).hasArg().build(); }
Builder baseOptBuilder() { return buildOption(Base, Base+Path, "optional: base folder path, assumes 'user.dir' value"); }
Option sourceOption() { return sourceOptBuilder().optionalArg(true).hasArg().build(); }
Builder sourceOptBuilder() { return buildOption(Source, Source+Path, "optional: Hoot sources folder path"); }
Option testSourceOption() { return testSourceBuilder().optionalArg(true).hasArg().build(); }
Builder testSourceBuilder() { return buildOption(TestSource, Source+Path, "optional: Hoot test sources folder path"); }
static final String Path = "Path";
public static final String Folder = "folder";
Option targetOption() { return targetOptBuilder().optionalArg(false).hasArg().build(); }
Builder targetOptBuilder() { return buildOption(Folder, Target+Path, "required: Java target folder path"); }
static final char BLANK = ' ';
static final String Libs = "libs";
Option libsOption() { return libsOptBuilder().optionalArg(true).valueSeparator(BLANK).hasArgs().build(); }
Builder libsOptBuilder() { return buildOption(Libs, Libs+Path, "library JARs"); }
static final String LangName = "languageName";
static final String Language = "language";
Option languageOption() { return languageBuilder().optionalArg(true).hasArg().build(); }
Builder languageBuilder() { return buildOption(Language, LangName, "optional: language name, .st or .hoot = default"); }
static final String PackNames = "packageNames";
public static final String Packages = "packages";
Option packageOption() { return packageOptBuilder().optionalArg(true).valueSeparator(BLANK).hasArgs().build(); }
Builder packageOptBuilder() { return buildOption(Packages, PackNames, "optional: packages to compile, none = all"); }
public static String shortened(String optionName) { return Dash + shortOption(optionName); }
static String shortOption(String optionName) { return optionName.substring(0, 1); }
Builder buildOption(String optionName, String text) { return buildOption(optionName, Empty, text); }
static final String Optioned = "Option";
Builder buildOption(String optionName, String argName, String text) {
return Option.builder(shortOption(optionName))
.longOpt(optionName).required(false).desc(text)
.argName(argName.isEmpty() ? optionName+Optioned : argName); }
public static String[] buildCommand(String path, String... options) {
String[] args = { shortened(Folder), path, }; return ArrayUtils.addAll(args, options); }
} // HootMain