diff --git a/docs/emc/EMC_config.md b/docs/emc/EMC_config.md index de41212..9f9625e 100644 --- a/docs/emc/EMC_config.md +++ b/docs/emc/EMC_config.md @@ -422,9 +422,9 @@ The following properties and methods are available for this class:
-#### `String` **`directoryName`** +#### `String` **`directoryPath`** -Returns the directory name if available. Otherwise returns `null` +Returns the directory path if available. Otherwise returns `null`
diff --git a/pom.xml b/pom.xml index 233e1ea..2591844 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,16 @@ + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + -parameters + + + diff --git a/src/main/java/org/entityc/compiler/ASTVisitor.java b/src/main/java/org/entityc/compiler/ASTVisitor.java index aded481..09c61f1 100644 --- a/src/main/java/org/entityc/compiler/ASTVisitor.java +++ b/src/main/java/org/entityc/compiler/ASTVisitor.java @@ -2264,6 +2264,7 @@ public Object visitTemplates(EntityLanguageParser.TemplatesContext ctx) { MTRepositoryImport thisImport = new MTRepositoryImport(ctx.templatesBody().templatesImport(), false); thisImport.setRepositoryName(repositoryImport.getRepositoryName()); thisImport.setFilename(template.getFilename()); + thisImport.setDirectoryPath(template.getDirectoryPath()); template.setRepositoryImport(thisImport); } } @@ -2283,7 +2284,7 @@ public MTTemplate visitTemplate(EntityLanguageParser.TemplateContext ctx) { MTTemplate template = new MTTemplate(ctx, currentConfiguration, ctx.id().getText()); if (ctx.STRING() != null) { - template.setDirectoryName(ECStringUtil.ProcessParserString(ctx.STRING().getText())); + template.setDirectoryPath(ECStringUtil.ProcessParserString(ctx.STRING().getText())); } EntityLanguageParser.TemplateBodyContext body = ctx.templateBody(); if (body == null) { diff --git a/src/main/java/org/entityc/compiler/CommandLineParser.java b/src/main/java/org/entityc/compiler/CommandLineParser.java deleted file mode 100644 index b983173..0000000 --- a/src/main/java/org/entityc/compiler/CommandLineParser.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.md file in the project root. - */ - -package org.entityc.compiler; - -import org.apache.commons.io.filefilter.WildcardFileFilter; - -import java.io.File; -import java.io.FileFilter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static java.lang.System.exit; - -class CommandLineParser { - - private final Map defineValues = new HashMap<>(); - public boolean help = false; - public boolean version = false; - public boolean verbose = false; - public List sourceFileNames = new ArrayList<>(); - public boolean advanceSchemaVersion = false; - public boolean deleteSchema = false; - public String configurationName; - public List templateSearchPaths = new ArrayList<>(); - public String templateToRun; - public String templateToFormat; - public String templateToFormatInPath; - public String templateToFormatOutPath; - public String setupUri; - - public String getDefineValue(String name) { - return defineValues.get(name); - } - - public boolean parse(String[] args) { - int index = 0; - while (index < args.length) { - String arg = args[index++]; - - if (!arg.startsWith("-")) { - if (arg.contains("*")) { - processWildcardArg(arg); - } else { - sourceFileNames.add(arg); - } - continue; - } - - if (arg.equals("-h") || arg.equals("-help") || arg.equals("--help")) { - help = true; - return false; - } - - if (arg.equals("-v") || arg.equals("-version") || arg.equals("--version")) { - version = true; - return false; - } - - if (arg.equals("-verbose") || arg.equals("--verbose")) { - verbose = true; - } - - if ((arg.equals("-c") || arg.equals("-config") || arg.equals("--config")) && index < args.length) { - configurationName = args[index++]; - } - - if (arg.equals("-tp") && index < args.length) { - String pathsString = args[index++]; - for (String path : pathsString.split(":")) { - templateSearchPaths.add(path); - } - } - - if (arg.equals("-rt") && index < args.length) { - templateToRun = args[index++]; - } - - if (arg.equals("-tf") && index < args.length) { - templateToFormat = args[index++]; - } - - if (arg.equals("-tfin") && index < args.length) { - templateToFormatInPath = args[index++]; - } - if (arg.equals("-tfout") && index < args.length) { - templateToFormatOutPath = args[index++]; - } - - if (arg.equals("-s") || arg.equals("-setup") || arg.equals("--setup")) { - setupUri = args[index++]; - } - - if (arg.equals("-asv")) { - advanceSchemaVersion = true; - } else if (arg.equals("-sdelete")) { - deleteSchema = true; - } - - String nameValue = null; - if (arg.equals("-D") && (index < args.length)) { - nameValue = args[index++]; - } else if (arg.startsWith("-D")) { - nameValue = arg.substring(2); - } - if (nameValue != null) { - String[] nameAndValue = nameValue.split("="); - if (nameAndValue.length == 2) { - defineValues.put(nameAndValue[0], nameAndValue[1]); - } - } - } - return false; - } - - private void processWildcardArg(String arg) { - String[] args = arg.split("\\/"); - if (args.length == 1) { - processSingleDirWildcardArg(arg, "."); - } else { - int lastSegmentIndex = args.length - 1; - if (!args[lastSegmentIndex].contains("*")) { - System.err.println("ERROR: wildcards only supported in last path segment."); - exit(1); - } - String dirPath = args[0]; - for (int i = 1; i < args.length - 1; i++) { - dirPath += File.separator + args[i]; - } - processSingleDirWildcardArg(args[lastSegmentIndex], dirPath); - } - } - - private void processSingleDirWildcardArg(String arg, String dirpath) { - File dir = new File(dirpath); - FileFilter fileFilter = new WildcardFileFilter(arg); - File[] files = dir.listFiles(fileFilter); - for (File file : files) { - sourceFileNames.add(dirpath + File.separator + file.getName()); - } - } - - public void printUsage() { - System.out.println("ec [options] sourceFile [...]"); - System.out.println(); - System.out.println("Options:"); - System.out.println("-h Print this help."); - System.out.println("-v Print compiler version."); - System.out.println("-verbose Outputs informational messages."); - System.out.println("-c configName The name of the configuration to use."); - System.out.println( - "-asv Advances the current schema version the next time a new schema version is generated."); - System.out.println("-sdelete Deletes all the schema files."); - System.out.println("-tp path:... Specifies colon delimited search path for templates."); - System.out.println("-rt name Runs only the specified template."); - System.out.println("-tf name Formats template file."); - System.out.println("-tfin path Formats template file specifying its file path."); - System.out.println( - "-tfout path Sends the formatted file to the specified file path (default is same as -tfin path)."); - System.out.println("-D name=value Defines a variable to a value - they can be accessed via templates."); - System.out.println("-setup uri Creates a new project using the specified setup URI, where the URI is:"); - System.out.println(" site:organization/reponame:tag/setupName"); - System.out.println(" site - only github is currently supported."); - System.out.println(" organization - The github organization for the setup repo."); - System.out.println(" reponame - The name of the repo."); - System.out.println(" tag - The tag from which to pull the setup files."); - System.out.println( - " setupName - Path and name of setup file to run (e.g. setups/BasicWebAppSetup)."); - System.out.println( - " This option requires that you use the -D option to define the following variable names:"); - System.out.println( - " appIdentifier - A unique name for your app (e.g., basic-app). This will be the name"); - System.out.println( - " of the created project directory. This would likely be its github"); - System.out.println( - " repository name as well if it where to be uploaded there."); - System.out.println(" appName - A name for your app (e.g., BasicApp)."); - System.out.println( - " apiPrefixNamespace - Represents a URL path prefix for all endpoints of the app. This"); - System.out.println( - " variable should use a \".\" as a delimiter (e.g., api.basicapp"); - System.out.println( - " which would result in api/basicapp/ as a url path prefix."); - System.out.println( - " appBasePackage - This is the base Java package to use for all generated code for the app."); - } -} diff --git a/src/main/java/org/entityc/compiler/EntityCompiler.java b/src/main/java/org/entityc/compiler/EntityCompiler.java index ca58269..95394d4 100644 --- a/src/main/java/org/entityc/compiler/EntityCompiler.java +++ b/src/main/java/org/entityc/compiler/EntityCompiler.java @@ -10,39 +10,28 @@ import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.apache.commons.io.IOUtils; -import org.entityc.compiler.model.MTCodeFormat; -import org.entityc.compiler.model.MTModule; +import org.entityc.compiler.cmdline.CommandLine; import org.entityc.compiler.model.MTRoot; import org.entityc.compiler.model.config.MTConfiguration; -import org.entityc.compiler.model.config.MTDirectory; -import org.entityc.compiler.model.config.MTFile; import org.entityc.compiler.model.config.MTProtoc; -import org.entityc.compiler.model.config.MTRepository; import org.entityc.compiler.model.config.MTRepositoryImport; import org.entityc.compiler.model.config.MTSpace; import org.entityc.compiler.model.config.MTSpaceInclude; import org.entityc.compiler.model.config.MTTemplate; import org.entityc.compiler.model.config.MTTransform; -import org.entityc.compiler.model.domain.MTDEntity; -import org.entityc.compiler.model.domain.MTDModule; -import org.entityc.compiler.model.domain.MTDomain; -import org.entityc.compiler.model.entity.MTEntity; import org.entityc.compiler.protobuf.PBLoaderExtractor; import org.entityc.compiler.repository.RepositoryCache; import org.entityc.compiler.repository.RepositoryFile; import org.entityc.compiler.repository.RepositoryImportManager; -import org.entityc.compiler.structure.sql.SSSchemaVersioning; import org.entityc.compiler.transform.MTBaseTransform; import org.entityc.compiler.transform.TransformManager; import org.entityc.compiler.transform.template.FileTemplateTransform; import org.entityc.compiler.transform.template.TemplatePublishing; import org.entityc.compiler.transform.template.TemplateTransform; -import org.entityc.compiler.transform.template.tree.FTTemplate; -import org.entityc.compiler.transform.template.tree.FTTransformSession; import org.entityc.compiler.util.ECANTLRErrorListener; import org.entityc.compiler.util.ECLog; -import org.entityc.compiler.util.ECSessionManager; import org.entityc.compiler.util.ECStringUtil; +import org.entityc.compiler.util.LogHandler; import java.io.File; import java.io.FileInputStream; @@ -50,271 +39,63 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; -import java.util.logging.Handler; -import java.util.logging.Level; +import java.util.Map; +import java.util.Set; import java.util.logging.LogManager; -import java.util.logging.LogRecord; import java.util.logging.Logger; -import java.util.stream.Stream; import static java.lang.System.exit; -class LogHandler extends Handler { - - @Override - public void publish(LogRecord record) { - String fullClassPath = record.getSourceClassName(); - String[] parts = fullClassPath.split("\\."); - String justClassName = parts[parts.length - 1]; - - StringBuilder sb = new StringBuilder(); - sb.append(record.getLevel().getName()); - sb.append(": "); - sb.append(justClassName); - sb.append("."); - sb.append(record.getSourceMethodName()); - sb.append("()| "); - sb.append(record.getMessage()); - if (record.getLevel() == Level.SEVERE) { - System.err.println(sb); - } else { - System.out.println(sb); - } - } - - @Override - public void flush() { - - } - - @Override - public void close() throws SecurityException { - - } -} - public class EntityCompiler { - public static final String COMPILER_VERSION = "0.12.6"; - public static final String LANGUAGE_VERSION = "0.12.3"; - private static CommandLineParser cmdLineParser; + public static final String COMPILER_VERSION = "0.13.0"; + public static final String LANGUAGE_VERSION = "0.12.3"; + private static final Map defineValues = new HashMap<>(); + private static final Set templateSearchPath = new HashSet<>(); + private static CommandLine commandLine; - public static final List GetTemplateSearchPaths() { - return cmdLineParser.templateSearchPaths; - } + public static void main(String[] args) { - public static final String GetDefineValue(String name, String defaultValue) { - String value = cmdLineParser.getDefineValue(name); - if (value == null) { - value = defaultValue; - } - return value; - } + setupLogger(); - public static final boolean ShouldAdvanceSchemaVersion() { - return cmdLineParser.advanceSchemaVersion; + commandLine = new CommandLine(); + commandLine.run(args); } - public static final boolean isVerbose() { - return cmdLineParser != null && cmdLineParser.verbose; + private static void setupLogger() { + LogManager.getLogManager().reset(); + Logger rootLogger = LogManager.getLogManager().getLogger(""); + rootLogger.addHandler(new LogHandler()); } - private static String readLineByLineJava8(String filePath) { - StringBuilder contentBuilder = new StringBuilder(); - - try (Stream stream = Files.lines(Paths.get(filePath), StandardCharsets.UTF_8)) { - stream.forEach(s -> contentBuilder.append(s).append("\n")); - } catch (IOException e) { - e.printStackTrace(); - } - - return contentBuilder.toString(); + public static void RunConfiguration(MTConfiguration configuration) { + MTRoot root = configuration.getRoot(); + EntityCompiler.LoadTransforms(root, configuration); + EntityCompiler.RunTransforms(root, configuration); + EntityCompiler.RunProtoc(root, configuration); } - public static void main(String[] args) { - - setupLogger(); - - cmdLineParser = new CommandLineParser(); - - cmdLineParser.parse(args); - - if (cmdLineParser.help) { - cmdLineParser.printUsage(); - exit(0); - } - - if (cmdLineParser.version) { - System.out.println("ec compiler version " + COMPILER_VERSION + ", language version " + LANGUAGE_VERSION); - exit(0); - } - - boolean setupMode = cmdLineParser.setupUri != null; - - boolean codeFormattingOnly = !setupMode && (cmdLineParser.templateToFormat != null - || cmdLineParser.templateToFormatInPath != null); - if (cmdLineParser.sourceFileNames.size() < 1) { - if (cmdLineParser.deleteSchema) { - SSSchemaVersioning.DeleteEntireSchema(); - exit(0); - } - if (!codeFormattingOnly && !setupMode) { - System.err.println("ERROR: Must specify at least one source file."); - exit(1); - } - } - - // root node for all things read in during this execution session - MTRoot root = new MTRoot(null); - - List sourceFilenames = new ArrayList<>(); - String configurationName = cmdLineParser.configurationName; - - if (setupMode) { - // site : organization / repo-name / path-to-setup-edl - MTRepository repository = new MTRepository(cmdLineParser.setupUri); - repository.setName("SetupRepo"); - if (cmdLineParser.getDefineValue("appIdentifier") == null) { - ECLog.logFatal("Must define variable \"appIdentifier\" on the command line."); - } - EntityCompiler.ensureDirectory(cmdLineParser.getDefineValue("appIdentifier")); - MTRepositoryImport repositoryImport = new MTRepositoryImport(null, false); - MTSpace space = new MTSpace(null, "Setup"); - root.setSpace(space); - space.addRepository(repository); - repositoryImport.setRepositoryName(repository.getName()); - repositoryImport.setFilename(repository.getSetupFilename()); - RepositoryImportManager importManager = new RepositoryImportManager( - RepositoryCache.CacheStructure.TempDirectory); - ECLog.logInfo("Fetching setup: " + repository.getSetupFilename()); - RepositoryFile repositoryFile = importManager.importFromRepository(space, - repositoryImport, "edl", false); - sourceFilenames.add(repositoryFile.getFilepath()); - configurationName = "Setup"; - } else { - sourceFilenames.addAll(cmdLineParser.sourceFileNames); - } - - ECSessionManager.getInstance().start(); - - // get all filenames passed on command line and parse those files - ArrayList repositoryFiles = new ArrayList<>(); - for (String sourceFilename : sourceFilenames) { - repositoryFiles.add(new RepositoryFile(sourceFilename, false)); - } - parseSourceFiles(root, null, repositoryFiles, codeFormattingOnly); - - // CODE FORMATTING - if (cmdLineParser.templateToFormat != null) { - MTSpace space = new MTSpace(null, "formatterSpace"); - root.setSpace(space); - MTConfiguration config = new MTConfiguration(null, root, "formatter"); - root.addConfiguration(config); - - MTFile file = null; - for (String basePath : cmdLineParser.templateSearchPaths) { - File f = new File(basePath + File.separator + cmdLineParser.templateToFormat + ".eml"); - if (f.exists()) { - file = new MTFile(null, f); - } - } - MTTemplate mtTemplate = new MTTemplate(null, config, file); - TransformManager.AddTransform(new FileTemplateTransform(config, mtTemplate, file.getPath())); - FTTemplate ftTemplate = mtTemplate.parse((FTTransformSession) null, true); - File outFile = null; - if (cmdLineParser.templateToFormatOutPath != null) { - outFile = new File(cmdLineParser.templateToFormatOutPath); - } else { - outFile = new File(file.getPath()); - } - MTCodeFormat codeFormat = root.getCodeFormat("Default"); - ftTemplate.formatCodeToFile(outFile, codeFormat); - exit(0); - } - // CODE FORMATTING - if (cmdLineParser.templateToFormatInPath != null) { - MTSpace space = new MTSpace(null, "formatterSpace"); - root.setSpace(space); - MTConfiguration config = new MTConfiguration(null, root, "formatter"); - root.addConfiguration(config); - MTFile file = null; - File f = new File(cmdLineParser.templateToFormatInPath); - if (f.exists()) { - file = new MTFile(null, f); - } else { - ECLog.logFatal("The specified template file to format does not exist: " - + cmdLineParser.templateToFormatInPath); - } - - MTTemplate mtTemplate = new MTTemplate(null, config, file); - TransformManager.AddTransform(new FileTemplateTransform(config, mtTemplate, file.getPath())); - FTTemplate ftTemplate = mtTemplate.parse((FTTransformSession) null, true); - File outFile = null; - if (cmdLineParser.templateToFormatOutPath != null) { - outFile = new File(cmdLineParser.templateToFormatOutPath); - } else { - outFile = new File(file.getPath()); - } - MTCodeFormat codeFormat = root.getCodeFormat("Default"); - ftTemplate.formatCodeToFile(outFile, codeFormat); - exit(0); - } - - // Get configuration name from command line - this is used to only execute a configuration - // by that name from the files read in. - if (configurationName == null) { - if (root.getConfigurationNames().size() == 1) { - configurationName = root.getConfigurationNames().get(0); - } - } - - MTConfiguration configuration = configurationName != null ? - root.getConfiguration(configurationName) : - null; - - if (configuration == null) { - ECLog.logFatal("Need to specify a configuration."); - } - if (setupMode) { - // add the project directory we created above to the start of the output path - MTDirectory outputDirectory = configuration.getOutputByName("SetupTargetDir"); - if (outputDirectory == null) { - ECLog.logFatal("Setup needs an output defined by the name of \"SetupTargetDir\"."); - } - outputDirectory.setPath(cmdLineParser.getDefineValue("appIdentifier") + "/" + outputDirectory.getPath()); - - outputDirectory = configuration.getOutputByName("ProjectTopDir"); - if (outputDirectory != null) { - outputDirectory.setPath( - cmdLineParser.getDefineValue("appIdentifier") + "/" + outputDirectory.getPath()); - } - } - - + public static void LoadTransforms(MTRoot root, MTConfiguration configuration) { + String configurationName = configuration.getName(); // Load any built in transforms (such as the postgres one) TransformManager.LoadBuiltins(root, configurationName); - if (cmdLineParser.deleteSchema) { - SSSchemaVersioning.DeleteEntireSchema(); - } - // templates boolean failedToLoadTransform = false; for (MTTransform transformSpec : configuration.getTransforms()) { if (transformSpec.isTemplate()) { - MTTemplate template = (MTTemplate) transformSpec; - if (cmdLineParser.templateToRun != null && !template.getName().equals(cmdLineParser.templateToRun)) { + MTTemplate template = (MTTemplate) transformSpec; + if (commandLine.templateToRun != null && !template.getName().equals(commandLine.templateToRun)) { continue; } RepositoryFile repositoryFile; String templateFilename; if (template.getRepositoryImport() != null) { - if (cmdLineParser.verbose) { + if (commandLine.verbose) { ECLog.logInfo("Getting template " + template.getName() + " from repository: " + template.getRepositoryImport().getRepositoryName()); } @@ -345,10 +126,9 @@ public static void main(String[] args) { if (failedToLoadTransform) { exit(1); } + } - if (cmdLineParser.verbose) { - ECLog.logInfo("RESOLVING REFERENCES..."); - } + public static void RunTransforms(MTRoot root, MTConfiguration configuration) { root.resolveReferences(false); //model.processAssetAttributes(); root.getSpace().checkValidReferences(); @@ -360,7 +140,7 @@ public static void main(String[] args) { if (transform instanceof FileTemplateTransform) { continue; // skip templates for now } - if (cmdLineParser.verbose) { + if (commandLine.verbose) { System.out.println("Running " + "transform" + " " + transform.getName()); } transform.start(); @@ -370,7 +150,6 @@ public static void main(String[] args) { root.getSpace().checkValidReferences(); ArrayList fileTemplatesToRun = new ArrayList<>(); - // Non-Contextual Templates for (MTTransform transformSpec : configuration.getTransforms()) { if (transformSpec instanceof MTTemplate) { if (((MTTemplate) transformSpec).isContextual()) { @@ -380,12 +159,12 @@ public static void main(String[] args) { if (transform instanceof FileTemplateTransform) { TemplateTransform templateTransform = (TemplateTransform) transform; templateTransform.setConfig(transformSpec.getConfig()); - if (cmdLineParser.verbose) { + if (commandLine.verbose) { System.out.println("Loading template " + templateTransform.getName()); } templateTransform.load(); fileTemplatesToRun.add((FileTemplateTransform) templateTransform); - if (cmdLineParser.verbose) { + if (commandLine.verbose) { System.out.println("Finished Loading template " + templateTransform.getName()); } } @@ -405,60 +184,9 @@ public static void main(String[] args) { for (FileTemplateTransform transform : fileTemplatesToRun) { transform.run(); } + } - // Contextual Templates - for (MTTransform transformSpec : configuration.getTransforms()) { - if (transformSpec instanceof MTTemplate) { - if (!((MTTemplate) transformSpec).isContextual()) { - continue; - } - - MTBaseTransform transform = TransformManager.GetTransformByName(transformSpec.getName()); - if (!(transform instanceof TemplateTransform)) { - continue; - } - - String templateName = transformSpec.getName(); - - for (MTDomain domain : root.getSpace().getDomains()) { - for (MTEntity entity : root.getSpace().getEntities()) { - MTDEntity domainEntity = domain.getDomainEntity(entity, true); - if (domainEntity.getApplyTemplate() != null - && domainEntity.getApplyTemplate().getTemplateName().equals(templateName)) { - if (EntityCompiler.isVerbose()) { - ECLog.logInfo( - "Running entity contextual template " + transform.getName() + " for domain " - + domain.getName() + " on entity " + entity.getName()); - } - ((TemplateTransform) transform).start(domainEntity.getApplyTemplate(), entity); - } else { - MTModule module = entity.getModule(); - MTDModule domainModule = module != null ? - domain.getDomainModule(module, true) : - null; - if (domainModule != null && domainModule.getApplyTemplate() != null - && domainModule.getApplyTemplate().getTemplateName().equals(templateName)) { - if (EntityCompiler.isVerbose()) { - ECLog.logInfo( - "Running module contextual template " + transform.getName() + " for domain " - + domain.getName() + " in module " + module.getName() + " on entity " - + entity.getName()); - } - ((TemplateTransform) transform).start(domainModule.getApplyTemplate(), entity); - } else if (domain.getApplyTemplate() != null - && domain.getApplyTemplate().getTemplateName().equals(templateName)) { - if (EntityCompiler.isVerbose()) { - ECLog.logInfo( - "Running domain contextual template " + transform.getName() + " for domain " - + domain.getName() + " on entity " + entity.getName()); - } - ((TemplateTransform) transform).start(domain.getApplyTemplate(), entity); - } - } - } - } - } - } + public static void RunProtoc(MTRoot root, MTConfiguration configuration) { // PROTOBUF COMPILER for (MTProtoc protoc : configuration.getProtocs()) { @@ -684,10 +412,46 @@ public static void main(String[] args) { } } } - ECSessionManager.getInstance().close(); } - private static void parseSourceFiles(MTRoot root, MTSpace space, List repositoryFiles, boolean ignoreSpaceRequirement) { + public static final boolean isVerbose() { + return commandLine != null && commandLine.verbose; + } + + public static boolean ensureDirectory(File dir) { + if (!dir.exists()) { + boolean created = dir.mkdirs(); + return created && dir.exists(); + } + return true; + } + + public static boolean ensureDirectory(String directoryPath) { + File dir = new File(directoryPath); + return ensureDirectory(dir); + } + + public static final String GetDefineValue(String name, String defaultValue) { + String value = GetDefineValue(name); + if (value == null) { + value = defaultValue; + } + return value; + } + + public static String GetDefineValue(String name) { + return defineValues.get(name); + } + + public static void SetDefineValue(String name, String value) { + defineValues.put(name, value); + } + + public static final boolean ShouldAdvanceSchemaVersion() { + return commandLine.advanceSchemaVersion; + } + + public static void parseSourceFiles(MTRoot root, MTSpace space, List repositoryFiles, boolean ignoreSpaceRequirement) { // parse each source file and for (RepositoryFile repositoryFile : repositoryFiles) { if (space != null) { @@ -697,8 +461,7 @@ private static void parseSourceFiles(MTRoot root, MTSpace space, List commands = new HashMap<>(); + + public boolean help = false; + public boolean version = false; + public boolean verbose = false; + public List sourceFileNames = new ArrayList<>(); + public boolean advanceSchemaVersion = false; + public boolean deleteSchema = false; + public String configurationName; + public String templateToRun; + private static List templateSearchPaths = new ArrayList<>(); + + public CommandLine() { + addCommand(new CLHelp(this)); + addCommand(new CLInit(this)); + addCommand(new CLBuild(this)); + addCommand(new CLSchema(this)); + addCommand(new CLSetup(this)); + addCommand(new CLFormat(this)); + addCommand(new CLStatus(this)); + addCommand(new CLInfo(this)); + } + + private void addCommand(CLCommand command) { + commands.put(command.getName(), command); + } + + public static void AddToTemplateSearchPath(String path) { + templateSearchPaths.add(path); + } + + public static final List GetTemplateSearchPaths() { + return templateSearchPaths; + } + + public void run(String[] args) { + if (args.length == 0) { + printUsage(); + exit(0); + } + + reset(); // in case it is executed multiple times in a single java vm + + String commandName = args[0]; + + if (commandName.equals("-h") || commandName.equals("-help") || commandName.equals("--help")) { + printUsage(); + exit(0); + } + + if (commandName.equals("-v") || commandName.equals("-version") || commandName.equals("--version")) { + System.out.println("ec compiler version " + COMPILER_VERSION + ", language version " + LANGUAGE_VERSION); + exit(0); + } + + if (!commands.containsKey(commandName)) { + System.err.println("Unknown command: " + commandName); + printUsage(); + exit(1); + } + + CLCommand command = commands.get(commandName); + String cmdArgString = args.length == 1 ? + null : + args[1]; + + if (cmdArgString != null && (cmdArgString.equals("-h") || cmdArgString.equals("-help") || cmdArgString.equals( + "--help"))) { + command.printUsage(); + exit(0); + } + + String[] cmdArgsArray = new String[args.length - 1]; + for (int i = 1; i < args.length; i++) { + cmdArgsArray[i - 1] = args[i]; + } + command.run(cmdArgsArray); + } + + public void printUsage() { + System.out.println("usage: ec [--version] [--help] [--verbose]"); + System.out.println(" []"); + System.out.println(); + System.out.println("The following commands are available:"); + int maxLen = 0; + for (String cmdName : commands.keySet()) { + if (cmdName.length() > maxLen) { + maxLen = cmdName.length(); + } + } + for (String cmdName : commands.keySet()) { + CLCommand cmd = commands.get(cmdName); + System.out.println( + " " + cmd.getName() + ECStringUtil.RepeatString(" ", maxLen + 3 - cmd.getName().length()) + + cmd.getSummary()); + } + } + + public final CLCommand getCommandByName(String name) { + return commands.get(name); + } + + private static void reset() { + templateSearchPaths.clear(); + } +} diff --git a/src/main/java/org/entityc/compiler/cmdline/command/CLBuild.java b/src/main/java/org/entityc/compiler/cmdline/command/CLBuild.java new file mode 100644 index 0000000..9914129 --- /dev/null +++ b/src/main/java/org/entityc/compiler/cmdline/command/CLBuild.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +package org.entityc.compiler.cmdline.command; + +import org.entityc.compiler.EntityCompiler; +import org.entityc.compiler.cmdline.CommandLine; +import org.entityc.compiler.model.MTRoot; +import org.entityc.compiler.model.config.MTConfiguration; +import org.entityc.compiler.project.ProjectManager; +import org.entityc.compiler.repository.RepositoryFile; +import org.entityc.compiler.util.ECLog; + +import java.util.ArrayList; +import java.util.List; + +public class CLBuild extends CLCommand { + + public CLBuild(CommandLine commandLine) { + super(commandLine, + "build", + "Builds an Entity Compiler project.", + "Builds the Entity Compiler project using the specified configuration name."); + } + + @Override + public void run(String[] args) { + + // must have at least one argument + if (args.length == 0) { + printUsage(); + return; + } + + String configurationName = args[0]; + List defines = new ArrayList<>(); + boolean quietMode = false; + + // Gather the source file names + ArrayList sourceFilenames = new ArrayList<>(); + for (int i = 1; i < args.length; i++) { + String arg = args[i]; + if (!arg.startsWith("-")) { + if (arg.contains("*")) { + sourceFilenames.addAll(super.processWildcardArg(arg)); + } else { + sourceFilenames.add(arg); + } + } else if (arg.equals("-D")) { + defines.add(args[++i]); + } else if (arg.startsWith("-D")) { + defines.add(arg.substring(2)); + } else if (arg.equals("-q") || arg.equals("--quiet")) { + quietMode = true; + } else if (arg.equals("-tp")) { + if (i == args.length-1) { + ECLog.logFatal("Missing template path."); + } + String paths = args[++i]; + for (String path : paths.split(":")) { + commandLine.AddToTemplateSearchPath(path); + } + + } + } + + // set the global name/value pairs from command line + for (String defineStatement : defines) { + String[] nameValue = defineStatement.split("="); + if (nameValue.length != 2) { + ECLog.logFatal("-D option must be of the form =."); + } + EntityCompiler.SetDefineValue(nameValue[0], nameValue[1]); + } + + // startup our project + ProjectManager.getInstance().start(quietMode); + + // Convert them to repository imports + ArrayList repositoryFiles = new ArrayList<>(); + for (String sourceFilename : sourceFilenames) { + repositoryFiles.add(new RepositoryFile(sourceFilename, false)); + } + + // create the root node and run the parser + MTRoot root = new MTRoot(null); + EntityCompiler.parseSourceFiles(root, null, repositoryFiles, false); + + // Get the configuration that was requested and make sure it is there + MTConfiguration configuration = root.getConfiguration(configurationName); + if (configuration == null) { + ECLog.logFatal( + "Unable to find a configuration named \"" + configurationName + "\" in the specified files."); + } + + // Run the compiler for our configuration + ProjectManager.getInstance().beginConfiguration(configurationName); + EntityCompiler.RunConfiguration(configuration); + ProjectManager.getInstance().endActiveConfiguration(); + + // If the configuration include a protoc block + EntityCompiler.RunProtoc(root, configuration); + + // close out our session + ProjectManager.getInstance().close(); + if (!quietMode) { + ECLog.log(colorize("Success", StdoutColor.GreenForeground)); + } + } + + @Override + public void printUsage() { + super.printUsageWithArguments(" [ ...] [-tp ][-q,--quiet][-D= ...]"); + } +} diff --git a/src/main/java/org/entityc/compiler/cmdline/command/CLCommand.java b/src/main/java/org/entityc/compiler/cmdline/command/CLCommand.java new file mode 100644 index 0000000..c405e83 --- /dev/null +++ b/src/main/java/org/entityc/compiler/cmdline/command/CLCommand.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +package org.entityc.compiler.cmdline.command; + +import org.apache.commons.io.filefilter.WildcardFileFilter; +import org.entityc.compiler.cmdline.CommandLine; +import org.entityc.compiler.util.ECLog; +import org.entityc.compiler.util.ECStringUtil; + +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static java.lang.System.exit; + +public abstract class CLCommand { + + protected final static int DISPLAY_LINE_WIDTH = 80; + protected final CommandLine commandLine; + private final String name; + private final String summary; + private final String description; + + CLCommand(CommandLine commandLine, String name, String summary, String description) { + this.commandLine = commandLine; + this.name = name; + this.summary = summary; + this.description = description; + } + + public static void displayItems(String message, Set listOfItems) { + displayItems(message, listOfItems, StdoutColor.Default); + } + + public static void displayItems(String message, Set listOfItems, StdoutColor itemColor) { + ECLog.log(""); + ECLog.log(ECStringUtil.WrapString(message + ":", "", DISPLAY_LINE_WIDTH)); + Collection sortedNames = listOfItems.stream().sorted().collect(Collectors.toList()); + for (String name : sortedNames) { + ECLog.log(" " + colorize(name, itemColor)); + } + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getSummary() { + return summary; + } + + abstract public void printUsage(); + + abstract public void run(String[] args); + + void printUsageWithArguments(String arguments) { + printCommandHelpHeader(); + final String prefix = "usage: ec " + name; + final String spacing = " "; + final String indent = ECStringUtil.RepeatString(" ", prefix.length() + spacing.length()); + String wrappedUsage = ECStringUtil.WrapString(arguments, indent, DISPLAY_LINE_WIDTH); + ECLog.log(prefix + spacing + wrappedUsage); + ECLog.log(""); + } + + void printCommandHelpHeader() { + ECLog.log(""); + final String prefix = name; + final String spacing = " -- "; + final String indent = ECStringUtil.RepeatString(" ", prefix.length() + spacing.length()); + String wrappedUsage = ECStringUtil.WrapString(description, indent, DISPLAY_LINE_WIDTH); + System.out.print(prefix + spacing); + System.out.println(wrappedUsage); + ECLog.log(""); + } + + protected List processWildcardArg(String arg) { + String[] args = arg.split("\\/"); + if (args.length == 1) { + return processSingleDirWildcardArg(arg, "."); + } else { + int lastSegmentIndex = args.length - 1; + if (!args[lastSegmentIndex].contains("*")) { + System.err.println("ERROR: wildcards only supported in last path segment."); + exit(1); + } + String dirPath = args[0]; + for (int i = 1; i < args.length - 1; i++) { + dirPath += File.separator + args[i]; + } + return processSingleDirWildcardArg(args[lastSegmentIndex], dirPath); + } + } + + protected List processSingleDirWildcardArg(String arg, String dirpath) { + ArrayList sourceFileNames = new ArrayList<>(); + File dir = new File(dirpath); + FileFilter fileFilter = new WildcardFileFilter(arg); + File[] files = dir.listFiles(fileFilter); + for (File file : files) { + sourceFileNames.add(dirpath + File.separator + file.getName()); + } + return sourceFileNames; + } + + protected static String colorize(String text, StdoutColor color) { + return color.getStringValue() + text + StdoutColor.Default.getStringValue(); + } + + public enum StdoutColor { + Default(0), + Brighter(1), + Underlined(4), + Flashing(5), + BlackForeground(30), + RedForeground(31), + GreenForeground(32), + YellowForeground(33), + BlueForeground(34), + PurpleForeground(35), + CyanForeground(36), + WhiteForeground(37), + BlackBackground(40), + RedBackground(41), + GreenBackground(42), + YellowBackground(43), + BlueBackground(44), + PurpleBackground(45), + CyanBackground(46), + WhiteBackground(47), + ; + + final int ansiCode; + final String stringValue; + + StdoutColor(int ansiCode) { + this.ansiCode = ansiCode; + this.stringValue = "\033[1;" + ansiCode + "m"; + } + + public int getAnsiCode() { + return ansiCode; + } + + public String getStringValue() { + return stringValue; + } + } +} diff --git a/src/main/java/org/entityc/compiler/cmdline/command/CLFormat.java b/src/main/java/org/entityc/compiler/cmdline/command/CLFormat.java new file mode 100644 index 0000000..e060f3b --- /dev/null +++ b/src/main/java/org/entityc/compiler/cmdline/command/CLFormat.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +package org.entityc.compiler.cmdline.command; + +import org.entityc.compiler.cmdline.CommandLine; +import org.entityc.compiler.model.MTCodeFormat; +import org.entityc.compiler.model.MTRoot; +import org.entityc.compiler.model.config.MTConfiguration; +import org.entityc.compiler.model.config.MTFile; +import org.entityc.compiler.model.config.MTSpace; +import org.entityc.compiler.model.config.MTTemplate; +import org.entityc.compiler.transform.TransformManager; +import org.entityc.compiler.transform.template.FileTemplateTransform; +import org.entityc.compiler.transform.template.tree.FTTemplate; +import org.entityc.compiler.transform.template.tree.FTTransformSession; +import org.entityc.compiler.util.ECLog; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class CLFormat extends CLCommand { + + public CLFormat(CommandLine commandLine) { + super(commandLine, + "format", + "Formats template source files.", + "Formats template source files given formatting preferences."); + } + + @Override + public void printUsage() { + super.printUsageWithArguments("[-o ] [ ...]"); + } + + @Override + public void run(String[] args) { + + // process arguments + List filenames = new ArrayList<>(); + String outputDirectory = null; + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if (arg.equals("-o")) { + if (i == args.length) { + ECLog.logFatal("Missing output directory."); + } + outputDirectory = args[++i]; + } else { + if (arg.contains("*")) { + filenames.addAll(processWildcardArg(arg)); + } else { + filenames.add(arg); + } + } + } + + // setup root node, space and configuration + MTRoot root = new MTRoot(null); + MTSpace space = new MTSpace(null, "formatterSpace"); + root.setSpace(space); + MTConfiguration config = new MTConfiguration(null, root, "formatter"); + root.addConfiguration(config); + + // process each file specified + MTFile file = null; + for (String filename : filenames) { + File f = new File(filename); + if (f.exists()) { + file = new MTFile(null, f); + } else { + ECLog.logFatal("The specified template file to format does not exist: " + + filename); + } + + // setup, parse and load template into memory + MTTemplate mtTemplate = new MTTemplate(null, config, file); + TransformManager.AddTransform(new FileTemplateTransform(config, mtTemplate, file.getPath())); + FTTemplate ftTemplate = mtTemplate.parse((FTTransformSession) null, true); + File outFile = null; + if (outputDirectory != null) { + outFile = new File(outputDirectory + File.separator + file.getPath()); + } else { + outFile = new File(file.getPath()); + } + + // Use the default code format and perform the formatting + MTCodeFormat codeFormat = root.getCodeFormat("Default"); + ftTemplate.formatCodeToFile(outFile, codeFormat); + } + } +} diff --git a/src/main/java/org/entityc/compiler/cmdline/command/CLHelp.java b/src/main/java/org/entityc/compiler/cmdline/command/CLHelp.java new file mode 100644 index 0000000..236458d --- /dev/null +++ b/src/main/java/org/entityc/compiler/cmdline/command/CLHelp.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +package org.entityc.compiler.cmdline.command; + +import org.entityc.compiler.cmdline.CommandLine; +import org.entityc.compiler.util.ECLog; + +public class CLHelp extends CLCommand { + + public CLHelp(CommandLine commandLine) { + super(commandLine, + "help", + "Provides help on usage for this app.", + "Provides usage help on a specific command or on the entire app."); + } + + @Override + public void run(String[] args) { + if (args.length == 0) { + printUsage(); + return; + } + + String commandName = args[0]; + CLCommand command = commandLine.getCommandByName(commandName); + if (command == null) { + ECLog.logError("Unknown command line command: " + commandName); + commandLine.printUsage(); + System.exit(1); + } + + command.printUsage(); + } + + @Override + public void printUsage() { + super.printUsageWithArguments("[]"); + } +} diff --git a/src/main/java/org/entityc/compiler/cmdline/command/CLInfo.java b/src/main/java/org/entityc/compiler/cmdline/command/CLInfo.java new file mode 100644 index 0000000..9a7665c --- /dev/null +++ b/src/main/java/org/entityc/compiler/cmdline/command/CLInfo.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +package org.entityc.compiler.cmdline.command; + +import org.entityc.compiler.cmdline.CommandLine; +import org.entityc.compiler.project.GeneratedFile; +import org.entityc.compiler.project.ProjectManager; + +import java.io.File; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class CLInfo extends CLCommand { + + public CLInfo(CommandLine commandLine) { + super(commandLine, + "info", + "Displays some info about this Entity Compiler project.", + "Displays various types of info about this Entity Compiler project."); + } + + @Override + public void printUsage() { + super.printUsageWithArguments("[-s,--sources] [-c,--configs] [-t,--templates] [-g,--generated] [-a,--all]"); + } + + @Override + public void run(String[] args) { + + ProjectManager projectManager = ProjectManager.getInstance(); + projectManager.start(false); + + boolean showSources = false; + boolean showConfigs = false; + boolean showTemplates = false; + boolean showGeneratedFiles = false; + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if (arg.equals("-c") || arg.equals("--configs")) { + showConfigs = true; + } else if (arg.equals("-s") || arg.equals("--sources")) { + showSources = true; + } else if (arg.equals("-t") || arg.equals("--templates")) { + showTemplates = true; + } else if (arg.equals("-g") || arg.equals("--generated")) { + showGeneratedFiles = true; + } else if (arg.equals("-a") || arg.equals("--all")) { + showConfigs = true; + showSources = true; + showTemplates = true; + showGeneratedFiles = true; + } + } + + if (!showConfigs && !showTemplates && !showGeneratedFiles) { + showSources = true; + } + + projectManager.loadProjectFiles(); + + if (showSources) { + displayItems("Source Files", projectManager.getSourceFiles()); + } + + if (showGeneratedFiles) { + final List files = projectManager.getGeneratedFiles(); + Set filenames = new HashSet<>(); + for (GeneratedFile file : files) { + StringBuilder sb = new StringBuilder(); + sb.append(ProjectManager.getInstance().getRelativePath(new File(file.getFilepath()))); + Set configurationNames = file.getConfigurationNames(); + if (configurationNames.size() > 1) { + sb.append(" (" + configurationNames.size() + " configurations)"); + } + filenames.add(sb.toString()); + } + displayItems("Generated Files", filenames, StdoutColor.GreenForeground); + } + + if (showConfigs) { + displayItems("Configurations", projectManager.getConfigurationNames(), StdoutColor.CyanForeground); + } + + if (showTemplates) { + displayItems("Templates", projectManager.getTemplateUris(), StdoutColor.BlueForeground); + } + } +} diff --git a/src/main/java/org/entityc/compiler/cmdline/command/CLInit.java b/src/main/java/org/entityc/compiler/cmdline/command/CLInit.java new file mode 100644 index 0000000..4b3560b --- /dev/null +++ b/src/main/java/org/entityc/compiler/cmdline/command/CLInit.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +package org.entityc.compiler.cmdline.command; + +import org.entityc.compiler.cmdline.CommandLine; +import org.entityc.compiler.project.ProjectManager; +import org.entityc.compiler.util.ECLog; + +import java.io.File; +import java.io.IOException; + +public class CLInit extends CLCommand { + + public CLInit(CommandLine commandLine) { + super(commandLine, + "init", + "Initializes a directory as an Entity Compiler project.", + "Initializes the current working directory to be an Entity Compiler project directory. " + + "This will allow the Entity Compiler to keep track of build sessions for this project and keep other " + + "project information specific to the Entity Compiler in one place."); + } + + @Override + public void printUsage() { + super.printUsageWithArguments("[-f,--force]"); + } + + @Override + public void run(String[] args) { + + boolean force = args.length > 0 && (args[0].equals("-f") || args[0].equals("--force")); + String invocationDirectory = null; + try { + invocationDirectory = new File(".").getCanonicalPath(); + String projectBaseDirPath = ProjectManager.getInstance().findECDirectory(invocationDirectory); + if (projectBaseDirPath != null && projectBaseDirPath.equals(invocationDirectory)) { + ECLog.log("Project directory already established here."); + } else if (projectBaseDirPath == null || (force && !projectBaseDirPath.equals(invocationDirectory))) { + if (force) { + ProjectManager.getInstance().setProjectBaseDirPath(invocationDirectory); + } + ProjectManager.getInstance().start(false); + ProjectManager.getInstance().close(); + ECLog.log("This directory (" + invocationDirectory + ") is now an Entity Compiler project directory."); + } else { + ECLog.logError("There is already a project directory above this directory: " + projectBaseDirPath); + ECLog.log("If you still want to initialize this directory (" + invocationDirectory + + ") as a project directory use the -f option."); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/org/entityc/compiler/cmdline/command/CLSchema.java b/src/main/java/org/entityc/compiler/cmdline/command/CLSchema.java new file mode 100644 index 0000000..7198ce3 --- /dev/null +++ b/src/main/java/org/entityc/compiler/cmdline/command/CLSchema.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +package org.entityc.compiler.cmdline.command; + +import org.entityc.compiler.cmdline.CommandLine; +import org.entityc.compiler.project.ProjectManager; +import org.entityc.compiler.structure.sql.SSSchemaVersioning; +import org.entityc.compiler.util.ECLog; + +import java.util.Scanner; +import java.util.Set; + +import static org.entityc.compiler.structure.sql.SSSchemaVersioning.LoadSchemaVersion; + +public class CLSchema extends CLCommand { + + public CLSchema(CommandLine commandLine) { + super(commandLine, "schema", "Used to manage the database schema of the project.", ""); + } + + @Override + public void run(String[] args) { + + ProjectManager projectManager = ProjectManager.getInstance(); + projectManager.start(false); + + Set directories = projectManager.getSchemaDirectories(); + if (directories.size() == 0) { + ECLog.log("No schema directories found. You may need to run a build so that it will be created."); + } + + if (directories.size() > 1) { + ECLog.logWarning("Multiple schemas are currently not supported. Only the first one found will be used."); + } + + SSSchemaVersioning.setBasePath(directories.stream().findFirst().get()); + + if (args.length > 0) { + String subCommand = args[0]; + if (subCommand.equals("advance")) { + if (args.length > 2) { + ECLog.logFatal(getName() + " " + subCommand + " has too many arguments"); + } + if (args.length == 1) { + SSSchemaVersioning.SaveAdvanceRequest(true); + } else { + String subCommandArg = args[1]; + if (subCommandArg.equals("cancel")) { + SSSchemaVersioning.SaveAdvanceRequest(false); + } else { + ECLog.logFatal("The " + getName() + " " + subCommand + " command does not accept option: " + + subCommandArg); + } + } + } else if (subCommand.equals("delete")) { + boolean doTheDelete = false; + if (args.length > 1) { + String deleteOption = args[1]; + if (deleteOption.equals("-f") || deleteOption.equals("--force")) { + doTheDelete = true; + } else { + ECLog.logFatal("Unknown option for delete: " + deleteOption); + } + } + if (!doTheDelete) { + System.out.print( + "Are you sure you want to delete your schema? To confirm enter the word 'delete': "); + Scanner scanner = new Scanner(System.in); + String value = scanner.nextLine(); + if (value.equals("delete")) { + doTheDelete = true; + } + } + if (doTheDelete) { + SSSchemaVersioning.DeleteEntireSchema(); + ECLog.log( + "Entire schema has been " + CLCommand.StdoutColor.RedForeground.getStringValue() + "DELETED" + + StdoutColor.Default.getStringValue()); + } else { + ECLog.log("Delete aborted."); + return; + } + } else { + ECLog.logFatal("Command " + getName() + " does not support sub-command: " + subCommand); + } + } + for (String schemaDirectory : directories) { + SSSchemaVersioning.setBasePath(schemaDirectory); + int readVersion = LoadSchemaVersion(SSSchemaVersioning.SchemaPointer.Read); + ECLog.log(""); + ECLog.log("Schema Directory: " + schemaDirectory); + ECLog.log(" Previous version: " + ((readVersion > 1) ? + readVersion : + "(none)")); + ECLog.log(" Current version: " + LoadSchemaVersion(SSSchemaVersioning.SchemaPointer.Write)); + ECLog.log(" Version advance directive: " + (SSSchemaVersioning.LoadAdvanceRequest() ? + colorize("advance if schema changes", + StdoutColor.GreenForeground) : + colorize("don't advance", StdoutColor.RedForeground))); + } + ECLog.log(""); + } + + @Override + public void printUsage() { + super.printUsageWithArguments("[advance [cancel]] | [delete [-f,--force]]"); + } +} diff --git a/src/main/java/org/entityc/compiler/cmdline/command/CLSetup.java b/src/main/java/org/entityc/compiler/cmdline/command/CLSetup.java new file mode 100644 index 0000000..b5af7a8 --- /dev/null +++ b/src/main/java/org/entityc/compiler/cmdline/command/CLSetup.java @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +package org.entityc.compiler.cmdline.command; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.stream.JsonReader; +import org.entityc.compiler.EntityCompiler; +import org.entityc.compiler.cmdline.CommandLine; +import org.entityc.compiler.model.MTRoot; +import org.entityc.compiler.model.config.MTConfiguration; +import org.entityc.compiler.model.config.MTDirectory; +import org.entityc.compiler.model.config.MTRepository; +import org.entityc.compiler.model.config.MTRepositoryImport; +import org.entityc.compiler.model.config.MTSpace; +import org.entityc.compiler.project.ProjectManager; +import org.entityc.compiler.repository.RepositoryCache; +import org.entityc.compiler.repository.RepositoryFile; +import org.entityc.compiler.repository.RepositoryImportManager; +import org.entityc.compiler.util.ECLog; +import org.entityc.compiler.util.ECStringUtil; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Scanner; + +public class CLSetup extends CLCommand { + + private final HashMap setupMap = new HashMap<>(); + private MTRoot root = null; + private MTSpace space = null; + private MTRepository setupRepository = null; + + public CLSetup(CommandLine commandLine) { + super(commandLine, + "setup", + "Runs a setup file to install a new project to a directory.", + "Allows you to very quickly setup a project based on a pre-defined project template. " + + "You specify the project template using a URI that can fetch all necessary files over the network."); + } + + @Override + public void run(String[] args) { + + if (args.length == 0) { + printUsage(); + return; + } + + String setupNameOrUri = args[0]; + + loadSetups(); + + if (setupNameOrUri.equals("-l") || setupNameOrUri.equals("--list")) { + listAvailableSetups(); + return; + } + String setupUri = setupNameOrUri; + if (setupMap.containsKey(setupNameOrUri)) { + SetupInfo setupInfo = setupMap.get(setupNameOrUri); + setupUri = setupInfo.uri; + } + + // site : organization / repo-name / path-to-setup-edl + ECLog.logInfo("Fetching setup: " + setupUri); + RepositoryFile repositoryFile = getFileFromRepositoryUri(setupUri, "edl", false); + + String directoryName = repositoryFile.getRepository().getSetupFilename(); + if (args.length < 2) { + System.out.print("Enter project directory name [" + directoryName + "]: "); + Scanner scanner = new Scanner(System.in); + String value = scanner.nextLine(); + if (value.trim().length() != 0) { + directoryName = value; + } + } else { + directoryName = args[1]; + } + EntityCompiler.ensureDirectory(directoryName); + + ProjectManager.getInstance().setProjectBaseDirPath(directoryName); + ProjectManager.getInstance().start(false); + + configureSetupRepo(setupUri); + + String configurationName = "Setup"; + + ArrayList sourceFilenames = new ArrayList<>(); + sourceFilenames.add(repositoryFile.getFilepath()); + // more to put here + ArrayList repositoryFiles = new ArrayList<>(); + for (String sourceFilename : sourceFilenames) { + repositoryFiles.add(new RepositoryFile(sourceFilename, false)); + } + EntityCompiler.parseSourceFiles(root, null, repositoryFiles, true); + + MTConfiguration configuration = root.getConfiguration(configurationName); + + if (configuration == null) { + ECLog.logFatal("Unable to find a configuration named \"" + configurationName + "\" in the setup file."); + } + + // Update output directories to include our project directory + MTDirectory setupTargetDirectory = configuration.getOutputByName("SetupTargetDir"); + if (setupTargetDirectory == null) { + ECLog.logFatal("Setup file is missing an output defined by the name of \"SetupTargetDir\"."); + } + setupTargetDirectory.setPath(directoryName + "/" + setupTargetDirectory.getPath()); + + MTDirectory projectTopDirectory = configuration.getOutputByName("ProjectTopDir"); + if (projectTopDirectory != null) { + projectTopDirectory.setPath(directoryName + "/" + projectTopDirectory.getPath()); + } + + // Run the compiler for our configuration + EntityCompiler.RunConfiguration(configuration); + + // close out our session + ProjectManager.getInstance().close(); + } + + @Override + public void printUsage() { + super.printUsageWithArguments("[-l,--list] []"); + } + + private void loadSetups() { + String uri = "github:entityc/entity-compiler:main/setups"; + RepositoryFile setupListFile = getFileFromRepositoryUri(uri, "json", true); + if (setupListFile == null) { + ECLog.logFatal("Unable to find the list of available setups from: " + uri); + } + String filepath = setupListFile.getFilepath(); + JsonObject setupList = null; + Gson gson = new Gson(); + try { + FileInputStream fis = new FileInputStream(filepath); + JsonReader reader = new JsonReader(new InputStreamReader(fis, StandardCharsets.UTF_8)); + setupList = gson.fromJson(reader, JsonObject.class); + reader.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + for (String setupName : setupList.keySet()) { + JsonObject setup = setupList.getAsJsonObject(setupName); + if (!addSetupInfo(setupName, setup)) { + continue; + } + } + } + + private void listAvailableSetups() { + ECLog.log("The following project templates are available: "); + ECLog.log(""); + if (setupMap.size() == 0) { + ECLog.log("no setups available"); + } else { + for (String setupName : setupMap.keySet()) { + SetupInfo setup = setupMap.get(setupName); + ECLog.log(" " + setup.identifier); + String indent = " "; + ECLog.log(indent + setup.title); + ECLog.log(""); + String wrappedDescription = ECStringUtil.WrapString(setup.description, indent, DISPLAY_LINE_WIDTH); + ECLog.log(indent + wrappedDescription); + ECLog.log(""); + ECLog.log(indent + setup.uri); + ECLog.log(""); + } + } + } + + private RepositoryFile getFileFromRepositoryUri(String uri, String extension, boolean quietly) { + MTRepository repository = new MTRepository(uri); + repository.setName("SetupRepo"); + MTRepositoryImport repositoryImport = new MTRepositoryImport(null, false); + repositoryImport.setQuietly(quietly); + MTSpace space = new MTSpace(null, "Setup"); + MTRoot root = new MTRoot(null); + root.setSpace(space); + space.addRepository(repository); + repositoryImport.setRepositoryName(repository.getName()); + repositoryImport.setFilename(repository.getSetupFilename()); + RepositoryImportManager importManager = new RepositoryImportManager( + RepositoryCache.CacheStructure.TempDirectory); + RepositoryFile repositoryFile = importManager.importFromRepository(space, + repositoryImport, extension, false); + return repositoryFile; + } + + private void configureSetupRepo(String uri) { + this.setupRepository = new MTRepository(uri); + this.setupRepository.setName("SetupRepo"); + MTRepositoryImport repositoryImport = new MTRepositoryImport(null, false); + repositoryImport.setQuietly(true); + this.space = new MTSpace(null, "Setup"); + this.root = new MTRoot(null); + root.setSpace(space); + space.addRepository(this.setupRepository); + } + + private boolean addSetupInfo(String identifier, JsonObject jsonObject) { + String title = jsonObject.get("title").getAsString(); + String description = jsonObject.get("description").getAsString(); + String setupUri = jsonObject.get("uri").getAsString(); + if (title == null || description == null || setupUri == null) { + return false; + } + SetupInfo setupInfo = new SetupInfo(identifier, title, description, setupUri); + setupMap.put(setupInfo.identifier, setupInfo); + return true; + } + + private class SetupInfo { + + String identifier; + String title; + String description; + String uri; + + SetupInfo(String identifier, String title, String description, String uri) { + this.identifier = identifier; + this.title = title; + this.description = description; + this.uri = uri; + } + } +} diff --git a/src/main/java/org/entityc/compiler/cmdline/command/CLStatus.java b/src/main/java/org/entityc/compiler/cmdline/command/CLStatus.java new file mode 100644 index 0000000..03b3bf3 --- /dev/null +++ b/src/main/java/org/entityc/compiler/cmdline/command/CLStatus.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +package org.entityc.compiler.cmdline.command; + +import org.entityc.compiler.cmdline.CommandLine; +import org.entityc.compiler.project.ProjectManager; +import org.entityc.compiler.util.ECLog; + +public class CLStatus extends CLCommand { + + public CLStatus(CommandLine commandLine) { + super(commandLine, + "status", + "Displays the status of an Entity Compiler project.", + "Displays the status of this Entity Compiler project (if exists)."); + } + + @Override + public void printUsage() { + super.printUsageWithArguments(""); + } + + @Override + public void run(String[] args) { + + ProjectManager projectManager = ProjectManager.getInstance(); + projectManager.start(false); + ECLog.log("Project directory: " + ProjectManager.getInstance().getProjectBaseDirPath()); + } +} diff --git a/src/main/java/org/entityc/compiler/model/config/MTRepository.java b/src/main/java/org/entityc/compiler/model/config/MTRepository.java index 241a60a..fe01f72 100644 --- a/src/main/java/org/entityc/compiler/model/config/MTRepository.java +++ b/src/main/java/org/entityc/compiler/model/config/MTRepository.java @@ -30,6 +30,7 @@ public class MTRepository extends MTNode { private String repoName; private boolean indexedTemplates = false; private String setupFilename; + private String commitSHA1; public MTRepository(ParserRuleContext ctx, String name) { super(ctx); @@ -48,7 +49,7 @@ public MTRepository(String setupUri) { type = MTRepositoryType.GITHUB; Integer firstPathDelimIndex = setupUri.indexOf("/"); if (firstPathDelimIndex == -1) { - ECLog.logFatal("Invalid setup URI - nothing after organization name"); + ECLog.logFatal("Invalid setup URI - nothing after organization name."); } organization = setupUri.substring(colonIndex + 1, firstPathDelimIndex); Integer tagDelimIndex = setupUri.indexOf(":", colonIndex + 1); @@ -57,20 +58,21 @@ public MTRepository(String setupUri) { } Integer secondPathDelimIndex = setupUri.indexOf("/", firstPathDelimIndex + 1); boolean hasNoPath = secondPathDelimIndex == -1; + if (hasNoPath) { + ECLog.logFatal("Invalid setup URI - it requires a path to a setup file."); + } tag = setupUri.substring(tagDelimIndex + 1, hasNoPath ? setupUri.length() : secondPathDelimIndex); repoName = setupUri.substring(firstPathDelimIndex + 1, tagDelimIndex); - path = ""; - if (!hasNoPath) { - path = setupUri.substring(secondPathDelimIndex + 1); - Integer lastPathDelimIndex = path.lastIndexOf("/"); - if (lastPathDelimIndex == -1) { - setupFilename = "Setup"; - } else { - setupFilename = path.substring(lastPathDelimIndex + 1); - path = path.substring(0, lastPathDelimIndex); - } + path = setupUri.substring(secondPathDelimIndex + 1); + Integer lastPathDelimIndex = path.lastIndexOf("/"); + if (lastPathDelimIndex == -1) { + setupFilename = path; + path = ""; + } else { + setupFilename = path.substring(lastPathDelimIndex + 1); + path = path.substring(0, lastPathDelimIndex); } } @@ -188,9 +190,6 @@ public void indexTemplateFilesInGithubDirectory(GHRepository repo, List 0) { + builder.append("/"); + builder.append(path); + } return builder.toString(); } + + public String getCommitSHA1() { + return commitSHA1; + } + + public void setCommitSHA1(String commitSHA1) { + this.commitSHA1 = commitSHA1; + } } diff --git a/src/main/java/org/entityc/compiler/model/config/MTRepositoryImport.java b/src/main/java/org/entityc/compiler/model/config/MTRepositoryImport.java index 00cb565..1079362 100644 --- a/src/main/java/org/entityc/compiler/model/config/MTRepositoryImport.java +++ b/src/main/java/org/entityc/compiler/model/config/MTRepositoryImport.java @@ -15,12 +15,15 @@ import org.entityc.compiler.model.visitor.MTVisitor; @ModelClass(type = ModelClassType.CONFIGURATION, - description = "Contains configuration for importing files from a repository.") + description = "Contains configuration for importing files from a repository.") public class MTRepositoryImport extends MTNode { private String filename; private String repositoryName; private boolean includeOnly; + private boolean quietly; + + private String directoryPath; public MTRepositoryImport(ParserRuleContext ctx, boolean includeOnly) { super(ctx); @@ -28,11 +31,11 @@ public MTRepositoryImport(ParserRuleContext ctx, boolean includeOnly) { } @ModelMethod(category = ModelMethodCategory.CONFIGURATION, - description = - "Indicates whether this import should only use the file as like a header file or where everything in " - + "the imported source file is essentially declared as extern. This would be used in the case of " - + "entities where you don't intend to implement them in code directly but need to " - + "know how they are defined so code knows how to interface with them in some way.") + description = + "Indicates whether this import should only use the file as like a header file or where everything in " + + "the imported source file is essentially declared as extern. This would be used in the case of " + + "entities where you don't intend to implement them in code directly but need to " + + "know how they are defined so code knows how to interface with them in some way.") public boolean isIncludeOnly() { return includeOnly; } @@ -50,15 +53,23 @@ public void setFilename(String filename) { this.filename = filename; } + public String getDirectoryPath() { + return directoryPath; + } + + public void setDirectoryPath(String directoryPath) { + this.directoryPath = directoryPath; + } + @ModelMethod(category = ModelMethodCategory.CONFIGURATION, - description = "Returns an identifer which is basically the *repository name*`.`*filename*.") + description = "Returns an identifer which is basically the *repository name*`.`*filename*.") public String getIdentifier() { return repositoryName + "." + filename; } @ModelMethod(category = ModelMethodCategory.CONFIGURATION, - description = "Returns the declared name of the repository object that this import operation will use as " - + "its source repository.") + description = "Returns the declared name of the repository object that this import operation will use as " + + "its source repository.") public String getRepositoryName() { return repositoryName; } @@ -67,6 +78,14 @@ public void setRepositoryName(String repositoryName) { this.repositoryName = repositoryName; } + public boolean isQuietly() { + return quietly; + } + + public void setQuietly(boolean quietly) { + this.quietly = quietly; + } + @Override public void accept(MTVisitor visitor) { diff --git a/src/main/java/org/entityc/compiler/model/config/MTTemplate.java b/src/main/java/org/entityc/compiler/model/config/MTTemplate.java index 63433f2..b4e1679 100644 --- a/src/main/java/org/entityc/compiler/model/config/MTTemplate.java +++ b/src/main/java/org/entityc/compiler/model/config/MTTemplate.java @@ -23,7 +23,7 @@ public class MTTemplate extends MTTransform { private boolean contextual; private MTRepositoryImport repositoryImport; - private String directoryName; + private String directoryPath; private MTFile file; public MTTemplate(ParserRuleContext ctx, MTConfiguration configuration, String name) { @@ -40,24 +40,24 @@ public MTFile getFile() { } @ModelMethod(category = ModelMethodCategory.CONFIGURATION, - description = "Returns the filename of the template which is preceded by a directory name if available.") + description = "Returns the filename of the template which is preceded by a directory name if available.") public String getFilename() { String filename = ""; - if (this.getDirectoryName() != null) { - filename = this.getDirectoryName() + "/"; + if (this.getDirectoryPath() != null) { + filename = this.getDirectoryPath() + "/"; } filename += this.getName(); return filename; } @ModelMethod(category = ModelMethodCategory.CONFIGURATION, - description = "Returns the directory name if available. Otherwise returns `null`") - public String getDirectoryName() { - return directoryName; + description = "Returns the directory path if available. Otherwise returns `null`") + public String getDirectoryPath() { + return directoryPath; } - public void setDirectoryName(String directoryName) { - this.directoryName = directoryName; + public void setDirectoryPath(String directoryPath) { + this.directoryPath = directoryPath; } public boolean isNotDeclared() { @@ -65,7 +65,7 @@ public boolean isNotDeclared() { } @ModelMethod(category = ModelMethodCategory.CONFIGURATION, - description = "Returns an object that defines how the template will be imported.") + description = "Returns an object that defines how the template will be imported.") public MTRepositoryImport getRepositoryImport() { return repositoryImport; } @@ -82,16 +82,19 @@ public boolean isContextual() { @Deprecated public void setContextual(boolean contextual) { this.contextual = contextual; + if (contextual) { + ECLog.logWarning("Contextual templates have been deprecated."); + } } @ModelMethod(category = ModelMethodCategory.CONFIGURATION, - description = "Returns the output directory considered to be the primary output for the template.") + description = "Returns the output directory considered to be the primary output for the template.") public MTDirectory getPrimaryOutputDirectory() { - String directoryName = getOutputNameByLocalName("primary"); - if (directoryName == null) { + String outputName = getOutputNameByLocalName("primary"); + if (outputName == null) { return null; } - return this.configuration.getOutputByName(directoryName); + return this.configuration.getOutputByName(outputName); } public FTTemplate parse(FTTransformSession session, boolean suppressImport) { diff --git a/src/main/java/org/entityc/compiler/project/GeneratedFile.java b/src/main/java/org/entityc/compiler/project/GeneratedFile.java new file mode 100644 index 0000000..c1b3895 --- /dev/null +++ b/src/main/java/org/entityc/compiler/project/GeneratedFile.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +package org.entityc.compiler.project; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class GeneratedFile { + + final private String filepath; + final private List origins = new ArrayList<>(); + + public GeneratedFile(String filepath, String configurationName, String templateUri) { + this.filepath = filepath; + this.origins.add(new GenerationOrigin(configurationName, templateUri)); + } + + public GeneratedFile(String filepath) { + this.filepath = filepath; + } + + public String getFilepath() { + return filepath; + } + + public boolean hasTemplateOrigin(String templateUri) { + for (GenerationOrigin origin : origins) { + if (origin.matchesTemplateWithoutTag(templateUri)) { + return true; + } + } + return false; + } + + public boolean hasConfigurationOrigin(String configurationName) { + for (GenerationOrigin origin : origins) { + if (origin.getConfigurationName().equals(configurationName)) { + return true; + } + } + return false; + } + + public boolean hasSingleConfigurationOrigin(String configurationName) { + if (origins.size() > 1) { + return false; + } + for (GenerationOrigin origin : origins) { + if (origin.getConfigurationName().equals(configurationName)) { + return true; + } + } + return false; + } + + public Set getConfigurationNames() { + HashSet configurationNames = new HashSet<>(); + for (GenerationOrigin origin : origins) { + configurationNames.add(origin.getConfigurationName()); + } + return configurationNames; + } + + public Set getTemplateUris() { + HashSet templateUris = new HashSet<>(); + for (GenerationOrigin origin : origins) { + templateUris.add(origin.getTemplateUri()); + } + return templateUris; + } + + public GeneratedFile extractForConfiguration(String configurationName, boolean remove) { + GeneratedFile singleConfigurationGeneratedFile = new GeneratedFile(this.filepath); + for (GenerationOrigin origin : origins) { + if (origin.getConfigurationName().equals(configurationName)) { + singleConfigurationGeneratedFile.origins.add(origin); + } + } + if (remove) { + origins.removeAll(singleConfigurationGeneratedFile.origins); + } + return singleConfigurationGeneratedFile; + } +} diff --git a/src/main/java/org/entityc/compiler/project/GenerationOrigin.java b/src/main/java/org/entityc/compiler/project/GenerationOrigin.java new file mode 100644 index 0000000..6633db6 --- /dev/null +++ b/src/main/java/org/entityc/compiler/project/GenerationOrigin.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +package org.entityc.compiler.project; + +public class GenerationOrigin { + + private String configurationName; + private String templateUri; + + public GenerationOrigin(String configurationName, String templateUri) { + this.configurationName = configurationName; + this.templateUri = templateUri; + } + + public String getConfigurationName() { + return configurationName; + } + + public String getTemplateUri() { + return templateUri; + } + + public boolean matchesTemplateWithoutTag(String otherTemplateUri) { + return stripVersion(templateUri).equals(stripVersion(otherTemplateUri)); + } + + public String stripVersion(String uri) { + int lastColon = uri.lastIndexOf(':'); + return uri.substring(0, lastColon) + uri.substring( + uri.indexOf('/', lastColon) + 1); + } +} diff --git a/src/main/java/org/entityc/compiler/project/ProjectManager.java b/src/main/java/org/entityc/compiler/project/ProjectManager.java new file mode 100644 index 0000000..858945d --- /dev/null +++ b/src/main/java/org/entityc/compiler/project/ProjectManager.java @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.txt file in the project root. + */ + +/* + * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. + * Use of this file is governed by the BSD 3-clause license that + * can be found in the LICENSE.md file in the project root. + */ + +package org.entityc.compiler.project; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import org.apache.commons.io.FileUtils; +import org.entityc.compiler.cmdline.command.CLCommand; +import org.entityc.compiler.util.ECLog; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class ProjectManager { + + private static final String ECDirectoryName = ".ec"; + private static final String GeneratedFilename = "generated.json"; + private static final String SchemaFilename = "schema.json"; + private static final ProjectManager instance = new ProjectManager(); + private final String[] cwdParts; + private List generatedFiles = new ArrayList<>(); + private Set configurationNames; + private Set templateUris; + private File projectDirectory; // the directory with the .ec directory in it + private File ecDirectory; + private List activeConfigurationPreviouslyGeneratedFiles; + private String activeConfigurationName; + private Set schemaDirectories = new HashSet<>(); + + private boolean quietMode = false; + + private ProjectManager() { + String cwdPath = (new File(".")).getAbsolutePath(); + cwdParts = cwdPath.split(File.separator); + } + + public void start(boolean quietMode) { + this.quietMode = quietMode; + if (projectDirectory == null) { + try { + String invocationDirectory = new File(".").getCanonicalPath(); + String projectDirectoryPath = findECDirectory(invocationDirectory); + if (projectDirectoryPath == null) { + projectDirectoryPath = invocationDirectory; + } + projectDirectory = new File(projectDirectoryPath); + } catch (IOException e) { + ECLog.logFatal("Unable to locate current working directory"); + } + } + + ecDirectory = new File(projectDirectory.getAbsolutePath() + File.separator + ECDirectoryName); + ecDirectory.mkdirs(); + loadProjectFiles(); + } + + public void loadProjectFiles() { + validate(); + this.generatedFiles = loadFile(GeneratedFilename, new TypeToken>() { + }.getType(), new ArrayList<>()); + this.schemaDirectories = loadFile(SchemaFilename, new TypeToken>() { + }.getType(), new HashSet<>()); + } + + private T loadFile(String filename, java.lang.reflect.Type type, T defaultValue) { + T object = defaultValue; + final String errorMessage = "Unable to load project file: " + filename; + File projectFile = new File(ecDirectory.getPath() + File.separator + filename); + if (projectFile.exists()) { + Gson gson = new Gson(); + try { + FileInputStream fis = new FileInputStream(projectFile); + JsonReader reader = new JsonReader(new InputStreamReader(fis, StandardCharsets.UTF_8)); + object = gson.fromJson(reader, type); + reader.close(); + } catch (FileNotFoundException e) { + ECLog.logFatal(errorMessage); + } catch (IOException e) { + ECLog.logFatal(errorMessage); + } + } + return object; + } + + public void validate() { + if (!ecDirectory.exists()) { + ECLog.logFatal("No project directory found."); + } + } + + public void beginConfiguration(String configurationName) { + this.activeConfigurationName = configurationName; + this.activeConfigurationPreviouslyGeneratedFiles = extractGeneratedFilesForConfiguration(configurationName, + true, true); + } + + public List extractGeneratedFilesForConfiguration(String configurationName, boolean onlyTiedToThisConfiguration, boolean remove) { + List singleConfigurationGeneratedFiles = new ArrayList<>(); + for (GeneratedFile generatedFile : generatedFiles) { + boolean onlyThisConfig = generatedFile.hasSingleConfigurationOrigin(configurationName); + if (generatedFile.hasConfigurationOrigin(configurationName)) { + GeneratedFile singleConfigurationGeneratedFile = generatedFile.extractForConfiguration( + configurationName, remove); + if (onlyTiedToThisConfiguration && onlyThisConfig) { + singleConfigurationGeneratedFiles.add(singleConfigurationGeneratedFile); + } + } + } + return singleConfigurationGeneratedFiles; + } + + public void endActiveConfiguration() { + List activeConfigurationGeneratedFiles = extractGeneratedFilesForConfiguration( + activeConfigurationName, true, false); + Set prevConfigurationOnlyGeneratedFiles = new HashSet<>(); + for (GeneratedFile previouslyGeneratedFile : this.activeConfigurationPreviouslyGeneratedFiles) { + boolean found = false; + // TODO: make sure we only remove generated files that belong only to this configuration + for (GeneratedFile generatedFile : activeConfigurationGeneratedFiles) { + if (previouslyGeneratedFile.getFilepath().equals(generatedFile.getFilepath())) { + found = true; + } + } + if (!found) { + prevConfigurationOnlyGeneratedFiles.add(previouslyGeneratedFile); + } + } + if (!quietMode) { + ECLog.log("Number of files generated: " + activeConfigurationGeneratedFiles.size()); + } + if (prevConfigurationOnlyGeneratedFiles.size() == 0) { + //ECLog.log("No files need to be removed."); + } else { + Set removedFilenames = new HashSet<>(); + for (GeneratedFile fileToRemove : prevConfigurationOnlyGeneratedFiles) { + File file = new File(fileToRemove.getFilepath()); + if (file.exists()) { + file.delete(); + removedFilenames.add(getRelativePath(file)); + } + } + CLCommand.displayItems( + "The following files were previously generated but are now no longer part of " + + "the project and so were deleted.", + removedFilenames, CLCommand.StdoutColor.RedForeground); + } + } + + public String getRelativePath(File sourceFile) { + String absolutePath = sourceFile.getAbsolutePath(); + String[] sourceParts = absolutePath.split(File.separator); + + int lastMatchingIndex = -1; + for (int i = 0; i < cwdParts.length; i++) { + if (i + 1 == sourceParts.length) { + break; + } + if (!cwdParts[i].equals(sourceParts[i])) { + break; + } + lastMatchingIndex = i; + } + int numBackDirs = cwdParts.length - 1 - (lastMatchingIndex + 1); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < numBackDirs; i++) { + sb.append(".."); + sb.append(File.separator); + } + for (int i = lastMatchingIndex + 1; i < sourceParts.length; i++) { + sb.append(sourceParts[i]); + if (i != (sourceParts.length) - 1) { + sb.append(File.separator); + } + } + String relativePath = sb.toString(); + return relativePath; + } + + public String findECDirectory(String fromHerePath) { + if (fromHerePath.equals(File.separator)) { + return null; // can't go up any more + } + File ecDirectory = new File(fromHerePath + File.separator + ECDirectoryName); + if (ecDirectory.exists()) { + return fromHerePath; + } + File parentDirectory = new File(fromHerePath + File.separator + ".."); + try { + return findECDirectory(parentDirectory.getCanonicalPath()); + } catch (IOException e) { + return null; + } + } + + public void addGeneratedFile(String path, String configurationName, String templateUri) { + generatedFiles.add(new GeneratedFile(path, configurationName, templateUri)); + configurationNames = null; + templateUris = null; + } + + public void close() { + saveFile(GeneratedFilename, generatedFiles); + saveFile(SchemaFilename, schemaDirectories); + } + + private void saveFile(String filename, T object) { + Gson gson = new Gson(); + String filepath = ecDirectory.getPath() + File.separator + filename; + try { + FileOutputStream fos = new FileOutputStream(filepath); + JsonWriter writer = new JsonWriter(new OutputStreamWriter(fos, StandardCharsets.UTF_8)); + gson.toJson(object, ArrayList.class, writer); + writer.flush(); + fos.close(); + } catch (FileNotFoundException e) { + ECLog.logWarning("Unable to save project files."); + } catch (IOException e) { + ECLog.logWarning("Unable to save project files."); + } + } + + public Set getConfigurationNames() { + if (configurationNames == null) { + configurationNames = new HashSet<>(); + + for (GeneratedFile file : generatedFiles) { + Set names = file.getConfigurationNames(); + configurationNames.addAll(names); + } + } + return configurationNames; + } + + public Set getTemplateUris() { + if (templateUris == null) { + templateUris = new HashSet<>(); + + for (GeneratedFile file : generatedFiles) { + Set uris = file.getTemplateUris(); + templateUris.addAll(uris); + } + } + return templateUris; + } + + public List getGeneratedFilesByTemplateUri(String templateUri) { + ArrayList files = new ArrayList<>(); + for (GeneratedFile file : generatedFiles) { + if (file.hasTemplateOrigin(templateUri)) { + files.add(file); + } + } + return files; + } + + public List getGeneratedFilesByConfigurationName(String configurationName) { + ArrayList files = new ArrayList<>(); + for (GeneratedFile file : generatedFiles) { + if (file.hasConfigurationOrigin(configurationName)) { + files.add(file); + } + } + return files; + } + + public final List getGeneratedFiles() { + return generatedFiles; + } + + public Set getSourceFiles() { + Set sourceFilenames = new HashSet<>(); + Collection sourceFiles = FileUtils.listFiles( + new File(ProjectManager.getInstance().getProjectBaseDirPath()), + new String[]{"eml", "edl"}, true); + for (File sourceFile : sourceFiles) { + String relativePath = getRelativePath(sourceFile); + sourceFilenames.add(relativePath); + } + return sourceFilenames; + } + + public String getProjectBaseDirPath() { + return projectDirectory.getAbsolutePath(); + } + + public static ProjectManager getInstance() { + return instance; + } + + public void setProjectBaseDirPath(String projectDirectoryPath) { + this.projectDirectory = new File(projectDirectoryPath); + } + + public boolean isQuietMode() { + return quietMode; + } + + public void setQuietMode(boolean quietMode) { + this.quietMode = quietMode; + } + + public void registerSchemaDirectory(String path) { + if (schemaDirectories == null) { + schemaDirectories = new HashSet<>(); + } + schemaDirectories.add(path); + } + + public Set getSchemaDirectories() { + return schemaDirectories; + } +} diff --git a/src/main/java/org/entityc/compiler/repository/GitHubRepositoryImporter.java b/src/main/java/org/entityc/compiler/repository/GitHubRepositoryImporter.java index da3adbd..f580957 100644 --- a/src/main/java/org/entityc/compiler/repository/GitHubRepositoryImporter.java +++ b/src/main/java/org/entityc/compiler/repository/GitHubRepositoryImporter.java @@ -6,12 +6,14 @@ package org.entityc.compiler.repository; +import org.apache.commons.io.IOUtils; import org.entityc.compiler.EntityCompiler; import org.entityc.compiler.model.config.MTRepository; import org.entityc.compiler.model.config.MTRepositoryImport; import org.entityc.compiler.util.ECLog; -import org.apache.commons.io.IOUtils; +import org.kohsuke.github.GHBranch; import org.kohsuke.github.GHContent; +import org.kohsuke.github.GHRef; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; @@ -38,11 +40,11 @@ public RepositoryFile importFromRepository(MTRepository repository, MTRepository "Unable to connect to GitHub: " + repository.getOrganization() + "/" + repository.getRepoName() + "\n" + e.getMessage()); } - String pathPart = alternatePath != null ? - (alternatePath + "/") : - repository.getPath() != null && !repository.getPath().isEmpty() ? - repository.getPath() + "/" : - ""; + String pathPart = alternatePath != null ? + (alternatePath + "/") : + repository.getPath() != null && !repository.getPath().isEmpty() ? + repository.getPath() + "/" : + ""; String gitRepoPath = pathPart + repositoryImport.getFilename() + "." + extension; GHRepository repo = null; try { @@ -52,13 +54,16 @@ public RepositoryFile importFromRepository(MTRepository repository, MTRepository + repository.getRepoName()); } try { - ECLog.logInfo("Downloading file: " + gitRepoPath + " " + repository.getTag()); + if (!repositoryImport.isQuietly()) { + ECLog.logInfo("Downloading file: " + gitRepoPath + " " + repository.getTag()); + } GHContent fileContent = repo.getFileContent(gitRepoPath, repository.getTag()); if (fileContent == null) { ECLog.logFatal(repositoryImport, "Could not find file in repository: " + cachedRepositoryFile.getFilepath() + " with tag " + repository.getTag()); } + String outputFilepath = cachedRepositoryFile.getFilepath(); // in case the filename has sub directories, make sure they are created if (outputFilepath.contains(File.separator)) { @@ -88,6 +93,50 @@ public RepositoryFile importFromRepository(MTRepository repository, MTRepository return cachedRepositoryFile; } + @Override + public void updateRepositoryCommitSHA1(MTRepository repository) { + + if (repository.getCommitSHA1() != null) { + return; // we are good + } + + try { + if (github == null) { + github = GitHub.connect(); + } + } catch (IOException e) { + ECLog.logFatal( + "Unable to connect to GitHub: " + repository.getOrganization() + "/" + repository.getRepoName() + + "\n" + e.getMessage()); + } + + GHRepository repo = null; + try { + repo = github.getRepository(repository.getOrganization() + "/" + repository.getRepoName()); + } catch (IOException e) { + ECLog.logFatal("Unable to connect to repository: " + repository.getOrganization() + "/" + + repository.getRepoName()); + } + + String sha1 = null; + try { + GHBranch branch = repo.getBranch(repository.getTag()); + if (branch != null) { + sha1 = branch.getSHA1(); + } + } catch (IOException e) { + // could not find the commit, maybe its not a branch name + } + try { + GHRef ref = repo.getRef("tags/" + repository.getTag()); + sha1 = ref.getObject().getSha(); + } catch (IOException e) { + // must not be a tag + } + + repository.setCommitSHA1(sha1); + } + @Override public void close() { github = null; diff --git a/src/main/java/org/entityc/compiler/repository/LocalRepositoryImporter.java b/src/main/java/org/entityc/compiler/repository/LocalRepositoryImporter.java index 7c8c7f9..fdb8667 100644 --- a/src/main/java/org/entityc/compiler/repository/LocalRepositoryImporter.java +++ b/src/main/java/org/entityc/compiler/repository/LocalRepositoryImporter.java @@ -11,6 +11,7 @@ import org.entityc.compiler.model.config.MTRepository; import org.entityc.compiler.model.config.MTRepositoryImport; import org.entityc.compiler.util.ECLog; +import org.entityc.compiler.util.ECStringUtil; import java.io.File; import java.io.FileInputStream; @@ -23,14 +24,11 @@ public class LocalRepositoryImporter implements RepositoryImporter { public RepositoryFile importFromRepository(MTRepository repository, MTRepositoryImport repositoryImport, RepositoryFile cachedRepositoryFile, String extension, String alternatePath) { String outputFilepath = cachedRepositoryFile.getFilepath(); // in case the filename has sub directories, make sure they are created - if (outputFilepath.contains(File.separator)) { - - int index = outputFilepath.lastIndexOf(File.separatorChar); - if (index > 0) { - String dirPath = outputFilepath.substring(0, index); - if (!EntityCompiler.ensureDirectory(dirPath)) { - ECLog.logFatal("Not able to create directory for template file: " + outputFilepath); - } + + String directoryPath = ECStringUtil.DirectoryPath(outputFilepath); + if (directoryPath != null) { + if (!EntityCompiler.ensureDirectory(directoryPath)) { + ECLog.logFatal("Not able to create directory for template file: " + outputFilepath); } } @@ -54,6 +52,11 @@ public RepositoryFile importFromRepository(MTRepository repository, MTRepository return cachedRepositoryFile; } + @Override + public void updateRepositoryCommitSHA1(MTRepository repository) { + // nothing to do for this type of repository + } + @Override public void close() { } diff --git a/src/main/java/org/entityc/compiler/repository/RepositoryCache.java b/src/main/java/org/entityc/compiler/repository/RepositoryCache.java index 0e9b585..7caf1f4 100644 --- a/src/main/java/org/entityc/compiler/repository/RepositoryCache.java +++ b/src/main/java/org/entityc/compiler/repository/RepositoryCache.java @@ -6,10 +6,13 @@ package org.entityc.compiler.repository; +import org.apache.commons.io.FileUtils; import org.entityc.compiler.model.config.MTRepository; import org.entityc.compiler.model.config.MTRepositoryType; +import org.entityc.compiler.util.ECLog; import java.io.File; +import java.io.IOException; import java.util.UUID; public class RepositoryCache { @@ -32,10 +35,6 @@ public RepositoryCache(CacheStructure structure) { baseCacheDirectory.mkdirs(); } - public File getBaseCacheDirectory() { - return baseCacheDirectory; - } - public RepositoryFile getRepositoryFile(MTRepository repository, String filename, boolean asInclude) { String outputFilepath = baseCacheDirectory.getAbsolutePath() + File.separator; @@ -44,7 +43,9 @@ public RepositoryFile getRepositoryFile(MTRepository repository, String filename outputFilepath += filename; break; case UserCache: { - String pathPart = repository.getPath() != null && !repository.getPath().isEmpty() ? repository.getPath() + File.separator : ""; + String pathPart = repository.getPath() != null && !repository.getPath().isEmpty() ? + repository.getPath() + File.separator : + ""; String completeFilename = pathPart + filename; if (repository.getType() == MTRepositoryType.LOCAL) { outputFilepath = completeFilename; @@ -52,7 +53,7 @@ public RepositoryFile getRepositoryFile(MTRepository repository, String filename outputFilepath += getRepositoryCachePath(repository) + File.separator + completeFilename; } } - break; + break; } RepositoryFile repositoryFile = new RepositoryFile(repository, outputFilepath, asInclude); @@ -60,7 +61,56 @@ public RepositoryFile getRepositoryFile(MTRepository repository, String filename } private String getRepositoryCachePath(MTRepository repository) { - return repository.getOrganization() + File.separator + repository.getRepoName() + File.separator + repository.getTag(); + return repository.getOrganization() + File.separator + repository.getRepoName() + File.separator + + repository.getTag(); + } + + public void validateRepositoryInCache(MTRepository repository) { + if (structure != CacheStructure.UserCache || repository.getType() == MTRepositoryType.LOCAL || !repository.isValid()) { + return; // not this one + } + String commitSha1Filename = getBaseCacheDirectory().getAbsolutePath() + File.separator + getRepositoryCachePath( + repository) + File.separator + ".commitSha1"; + File file = new File(commitSha1Filename); + String cacheSHA1 = null; + if (file.exists()) { + try { + cacheSHA1 = FileUtils.readFileToString(file); + } catch (IOException e) { + // oh well, assume it is not there + } + } + String repositoryCommitSHA1 = repository.getCommitSHA1(); + if (repositoryCommitSHA1 == null || cacheSHA1 == null || !repository.getCommitSHA1().equals(cacheSHA1)) { + invalidateCacheDirectory(repository); + if (repositoryCommitSHA1 != null) { + try { + FileUtils.write(file, repositoryCommitSHA1); + } catch (IOException e) { + ECLog.logWarning("Unable to record repository sha1 in cache directory: " + commitSha1Filename); + } + } + } + } + + public void invalidateCacheDirectory(MTRepository repository) { + if (structure != CacheStructure.UserCache) { + return; + } + String fullPath = getBaseCacheDirectory().getAbsolutePath() + File.separator + getRepositoryCachePath( + repository); + File file = new File(fullPath); + if (file.exists()) { + try { + FileUtils.cleanDirectory(file); + } catch (IOException e) { + ECLog.logWarning("Unable to remove cache directory: " + fullPath); + } + } + } + + public File getBaseCacheDirectory() { + return baseCacheDirectory; } public enum CacheStructure { diff --git a/src/main/java/org/entityc/compiler/repository/RepositoryImportManager.java b/src/main/java/org/entityc/compiler/repository/RepositoryImportManager.java index 14a5e3c..24f1baa 100644 --- a/src/main/java/org/entityc/compiler/repository/RepositoryImportManager.java +++ b/src/main/java/org/entityc/compiler/repository/RepositoryImportManager.java @@ -47,6 +47,11 @@ public RepositoryFile getRepositoryFileByName(String identifier) { return repositoryFilesByIdentifier.get(identifier); } + public void updateRepositoryCommitSHA1(MTRepository repository) { + RepositoryImporter repositoryImporter = importersByType.get(repository.getType()); + repositoryImporter.updateRepositoryCommitSHA1(repository); + } + public RepositoryFile importFromRepository(MTSpace space, MTRepositoryImport repositoryImport, String extension, boolean asInclude) { @@ -67,15 +72,20 @@ public RepositoryFile importFromRepository(MTSpace space, MTRepositoryImport rep && type == MTRepositoryType.LOCAL; MTRepository sourceRepository = repository; - String alternatePath = null; + String alternatePath = null; if (shouldUseSpaceRepository) { sourceRepository = space.getRepositoryThatImportedThisSpace(); - alternatePath = repository.getPath(); + alternatePath = repository.getPath(); } RepositoryImporter repositoryImporter = importersByType.get(sourceRepository.getType()); - RepositoryFile cacheRepositoryFile = repositoryCache.getRepositoryFile(sourceRepository, filename, asInclude); - if (sourceRepository.getType() != MTRepositoryType.LOCAL && cacheRepositoryFile.exists()) { // check if it exists in the cache already + repositoryImporter.updateRepositoryCommitSHA1( + sourceRepository); // if it has one, fetch it so the cache will know if it needs to invalidate itself + repositoryCache.validateRepositoryInCache(sourceRepository); + RepositoryFile cacheRepositoryFile = repositoryCache.getRepositoryFile(sourceRepository, filename, + asInclude); + if (sourceRepository.getType() != MTRepositoryType.LOCAL + && cacheRepositoryFile.exists()) { // check if it exists in the cache already if (EntityCompiler.isVerbose()) { ECLog.logInfo("Found file already in cache: " + cacheRepositoryFile.getFilepath()); } @@ -84,7 +94,7 @@ public RepositoryFile importFromRepository(MTSpace space, MTRepositoryImport rep } RepositoryFile file = repositoryImporter.importFromRepository(sourceRepository, repositoryImport, - cacheRepositoryFile, extension, alternatePath); + cacheRepositoryFile, extension, alternatePath); if (file == null || !file.exists()) { String nameOrPath = repository.getType() == MTRepositoryType.LOCAL ? repository.getPath() : diff --git a/src/main/java/org/entityc/compiler/repository/RepositoryImporter.java b/src/main/java/org/entityc/compiler/repository/RepositoryImporter.java index cb0d387..4e09580 100644 --- a/src/main/java/org/entityc/compiler/repository/RepositoryImporter.java +++ b/src/main/java/org/entityc/compiler/repository/RepositoryImporter.java @@ -12,6 +12,7 @@ public interface RepositoryImporter { RepositoryFile importFromRepository(MTRepository repository, MTRepositoryImport repositoryImport, RepositoryFile cachedRepositoryFile, String extension, String alternatePath); + void updateRepositoryCommitSHA1(MTRepository repository); void close(); } diff --git a/src/main/java/org/entityc/compiler/structure/sql/SSSchemaVersioning.java b/src/main/java/org/entityc/compiler/structure/sql/SSSchemaVersioning.java index 98a0009..0024ed9 100644 --- a/src/main/java/org/entityc/compiler/structure/sql/SSSchemaVersioning.java +++ b/src/main/java/org/entityc/compiler/structure/sql/SSSchemaVersioning.java @@ -117,8 +117,10 @@ public boolean accept(File pathname) { return pathname.getName().startsWith("v") && pathname.getName().endsWith("_schema.json"); } }); - for (File f : schemaFiles) { - f.delete(); + if (schemaFiles != null) { + for (File f : schemaFiles) { + f.delete(); + } } File f = new File(schemaDirectoryPath + File.separator + "readVersion.json"); f.delete(); @@ -134,8 +136,10 @@ public boolean accept(File pathname) { return pathname.getName().startsWith("V") && pathname.getName().endsWith("__model.sql"); } }); - for (File fd : modelFiles) { - fd.delete(); + if (modelFiles != null) { + for (File fd : modelFiles) { + fd.delete(); + } } } } diff --git a/src/main/java/org/entityc/compiler/transform/MTVPostgresTransform.java b/src/main/java/org/entityc/compiler/transform/MTVPostgresTransform.java index 06460b6..e082bc7 100644 --- a/src/main/java/org/entityc/compiler/transform/MTVPostgresTransform.java +++ b/src/main/java/org/entityc/compiler/transform/MTVPostgresTransform.java @@ -26,6 +26,7 @@ import org.entityc.compiler.model.entity.MTRelationship; import org.entityc.compiler.model.entity.MTType; import org.entityc.compiler.model.entity.MTUniqueness; +import org.entityc.compiler.project.ProjectManager; import org.entityc.compiler.structure.sql.SSColumn; import org.entityc.compiler.structure.sql.SSComment; import org.entityc.compiler.structure.sql.SSForeignKey; @@ -65,12 +66,13 @@ public void start() { super.start(); - MTTransform config = getConfiguration().getTransformByName(getName()); - String outputName = config.getOutputNameByLocalName("primary"); + MTTransform transform = getConfiguration().getTransformByName(getName()); + String outputName = transform.getOutputNameByLocalName("primary"); MTDirectory output = getConfiguration().getOutputByName(outputName); - String schemaOutputName = config.getOutputNameByLocalName("schema"); + String schemaOutputName = transform.getOutputNameByLocalName("schema"); MTDirectory schemaOutput = getConfiguration().getOutputByName(schemaOutputName); SSSchemaVersioning.setBasePath(schemaOutput.getPath()); + ProjectManager.getInstance().registerSchemaDirectory(schemaOutput.getPath()); boolean requestToAdvance = SSSchemaVersioning.LoadAdvanceRequest(); advanceSchemaVersion = EntityCompiler.ShouldAdvanceSchemaVersion(); diff --git a/src/main/java/org/entityc/compiler/transform/TransformManager.java b/src/main/java/org/entityc/compiler/transform/TransformManager.java index 3657989..938643b 100644 --- a/src/main/java/org/entityc/compiler/transform/TransformManager.java +++ b/src/main/java/org/entityc/compiler/transform/TransformManager.java @@ -7,6 +7,7 @@ package org.entityc.compiler.transform; import org.entityc.compiler.EntityCompiler; +import org.entityc.compiler.cmdline.CommandLine; import org.entityc.compiler.model.MTRoot; import java.io.File; @@ -50,7 +51,7 @@ private static File SearchForTemplateFile(File directory, String templateFilenam } public static File FindTemplateFile(String templateFilename) { - for (String searchPath : EntityCompiler.GetTemplateSearchPaths()) { + for (String searchPath : CommandLine.GetTemplateSearchPaths()) { File dir = new File(searchPath); File foundFile = SearchForTemplateFile(dir, templateFilename); if (foundFile != null) { diff --git a/src/main/java/org/entityc/compiler/transform/template/FileTemplateTransform.java b/src/main/java/org/entityc/compiler/transform/template/FileTemplateTransform.java index 8b09acf..4566a59 100644 --- a/src/main/java/org/entityc/compiler/transform/template/FileTemplateTransform.java +++ b/src/main/java/org/entityc/compiler/transform/template/FileTemplateTransform.java @@ -57,7 +57,7 @@ public void load() { public void load(boolean suppressImport) { - //ECLog.logInfo("Parsing template: " + getName()); + //ECLog.logInfo("Parsing template: " + getName() + " that has directory path: " + mtTemplate.getDirectoryName()); if (!findTemplateFile()) { ECLog.logFatal("ERROR: Unable to find a template file with the name: " + templateFilename); @@ -70,7 +70,7 @@ public void load(boolean suppressImport) { } catch (IOException e) { ECLog.logFatal("Unable to open template source file: " + file.getAbsolutePath() + " (" + mtTemplate.getName() + ": " + templateFilename + ")"); } - loadFromStream(mtTemplate.getName(), file.getName(), input, suppressImport); + loadFromStream(mtTemplate.getName(), mtTemplate.getDirectoryPath(), file.getName(), input, suppressImport); } public void formatCode(MTCodeFormat codeFormat) { diff --git a/src/main/java/org/entityc/compiler/transform/template/StringTemplateTransform.java b/src/main/java/org/entityc/compiler/transform/template/StringTemplateTransform.java index bfdeefd..c6daa3d 100644 --- a/src/main/java/org/entityc/compiler/transform/template/StringTemplateTransform.java +++ b/src/main/java/org/entityc/compiler/transform/template/StringTemplateTransform.java @@ -57,7 +57,7 @@ public void load() { public void load(boolean suppressImport) { CharStream input = CharStreams.fromString(inputStringValue); - loadFromStream("", "String", input, suppressImport); + loadFromStream("", null, "String", input, suppressImport); } public String getOutputStringValue() { diff --git a/src/main/java/org/entityc/compiler/transform/template/TemplateASTVisitor.java b/src/main/java/org/entityc/compiler/transform/template/TemplateASTVisitor.java index 34e0b10..86da69d 100644 --- a/src/main/java/org/entityc/compiler/transform/template/TemplateASTVisitor.java +++ b/src/main/java/org/entityc/compiler/transform/template/TemplateASTVisitor.java @@ -402,10 +402,7 @@ public Object visitImportTag(TemplateGrammer.ImportTagContext ctx) { if (this.suppressImport) { return null; } - String templateName = templatePath; - if (templateName.contains("/")) { - templateName = templateName.substring(templateName.lastIndexOf('/') + 1); - } + String templateName = ECStringUtil.FilenameFromPath(templatePath); if (false && EntityCompiler.isVerbose()) { ECLog.logInfo(ctx, "Found Import of template: " + templatePath + " from repository " + fromRepositoryName); if (fromRepositoryName == null) { @@ -433,6 +430,7 @@ public Object visitImportTag(TemplateGrammer.ImportTagContext ctx) { } FTTemplate importedTemplate = configuration.parseTemplate(null, new MTFile(null, templateFile), fromRepositoryName); + importedTemplate.setDirectoryPath(ECStringUtil.DirectoryPath(templatePath)); importedTemplate.setImported(true); currentContainer(ctx).addChild(importedTemplate); currentContainer(ctx).resolveFunctionCalls(importedTemplate); diff --git a/src/main/java/org/entityc/compiler/transform/template/TemplateTransform.java b/src/main/java/org/entityc/compiler/transform/template/TemplateTransform.java index 180ace1..5beba68 100644 --- a/src/main/java/org/entityc/compiler/transform/template/TemplateTransform.java +++ b/src/main/java/org/entityc/compiler/transform/template/TemplateTransform.java @@ -120,7 +120,7 @@ private void applyConfig(FTTransformSession session, JsonObject config) { } } - protected void loadFromStream(String templateName, String filename, CharStream input, boolean suppressImport) { + protected void loadFromStream(String templateName, String directoryName, String filename, CharStream input, boolean suppressImport) { ECANTLRErrorListener errorListener = new ECANTLRErrorListener(filename); TemplateLexer lexer = new TemplateLexer(input); CommonTokenStream tokens = new CommonTokenStream(lexer); @@ -141,6 +141,7 @@ protected void loadFromStream(String templateName, String filename, CharStream i TemplateASTVisitor visitor = new TemplateASTVisitor(templateName, getConfiguration(), root.getSpace().getRepositoryByName(sourceRepositoryName), suppressImport); template = (FTTemplate) visitor.visit(templateContext); template.setName(getName()); + template.setDirectoryPath(directoryName); template.processIndents(true); } } diff --git a/src/main/java/org/entityc/compiler/transform/template/tree/FTInstall.java b/src/main/java/org/entityc/compiler/transform/template/tree/FTInstall.java index 77f65ef..0976334 100644 --- a/src/main/java/org/entityc/compiler/transform/template/tree/FTInstall.java +++ b/src/main/java/org/entityc/compiler/transform/template/tree/FTInstall.java @@ -27,6 +27,7 @@ import org.entityc.compiler.transform.template.tree.expression.FTConstant; import org.entityc.compiler.transform.template.tree.expression.FTExpression; import org.entityc.compiler.util.ECLog; +import org.entityc.compiler.util.ECStringUtil; import java.io.File; import java.io.IOException; @@ -35,18 +36,18 @@ import static org.entityc.compiler.transform.template.formatter.ConfigurableElement.RequiredSpace; @TemplateInstruction(category = TemplateInstructionCategory.FILE_IO, - name = "install", - usage = "`install `[`copy`]` `*sourceExpression*` `*destExpression*", - summary = "Provides an easy way to install files into your local project.", - description = "When setting up a project using files from a library, this instruction makes it easy " - + "to install files from that library into the project directory structure. The files being installed " - + "have their native file extension (not the one we use for templates) so they are easily " - + "viewable and edited (using the code highlighter appropriate for them). You can also include " - + "template code in them and the template code will execute during the install process. " - + "Of course, the presence of the template code may interfere with the code highlighter and " - + "as well, the template code will not be appropriately highlighted but when the amount of " - + "template code is relatively small it can be very advantageous to use this install method over " - + "just making it a template and running the template.") + name = "install", + usage = "`install `[`copy`]` `*sourceExpression*` `*destExpression*", + summary = "Provides an easy way to install files into your local project.", + description = "When setting up a project using files from a library, this instruction makes it easy " + + "to install files from that library into the project directory structure. The files being installed " + + "have their native file extension (not the one we use for templates) so they are easily " + + "viewable and edited (using the code highlighter appropriate for them). You can also include " + + "template code in them and the template code will execute during the install process. " + + "Of course, the presence of the template code may interfere with the code highlighter and " + + "as well, the template code will not be appropriately highlighted but when the amount of " + + "template code is relatively small it can be very advantageous to use this install method over " + + "just making it a template and running the template.") public class FTInstall extends FTNode { private final FTExpression sourceExpression; @@ -56,16 +57,16 @@ public class FTInstall extends FTNode { public FTInstall(ParserRuleContext ctx, @TemplateInstructionArgument(optional = true, keyword = true, - description = "If specified, the file will be copied verbatim without trying to execute " - + "any template code.") - Boolean copy, + description = "If specified, the file will be copied verbatim without trying to execute " + + "any template code.") + Boolean copy, @TemplateInstructionArgument( - description = "Defines the path and filename with extension of the source file. The path " - + "is relative to the directory in which the template was configured.") - FTExpression sourceExpression, + description = "Defines the path and filename with extension of the source file. The path " + + "is relative to the directory in which the template was configured.") + FTExpression sourceExpression, @TemplateInstructionArgument( - description = "Defines the destination path and directory name to copy the file into. " - + "The path is relative to the directory in which the template was configured.") + description = "Defines the destination path and directory name to copy the file into. " + + "The path is relative to the directory in which the template was configured.") FTExpression destExpression) { super(ctx); this.copyOnly = copy; @@ -97,10 +98,7 @@ public void transform(FTTransformSession session) { String destFilepath = (String) destFilepathObject; String sourceFilePath = ((FTConstant) sourceExpression).getStringValue(); - String filename = sourceFilePath; - if (filename.contains("/")) { - filename = filename.substring(filename.lastIndexOf('/') + 1); - } + String directoryPath = ECStringUtil.DirectoryPath(sourceFilePath); if (sourceRepositoryName == null) { ECLog.logFatal(this, "Cannot install files without an associated Repository."); @@ -124,15 +122,14 @@ public void transform(FTTransformSession session) { sourceFilePath = sourceFilePath.substring(0, lastExtensionIndex); if (lastPathSeparatorIndex != -1) { sourceFilename = sourceFilePath.substring(lastPathSeparatorIndex + 1); - } - else { + } else { sourceFilename = sourceFilePath; } } } RepositoryImportManager importManager = new RepositoryImportManager( - RepositoryCache.CacheStructure.UserCache); + RepositoryCache.CacheStructure.UserCache); MTRepositoryImport repositoryImport = new MTRepositoryImport(null, false); repositoryImport.setRepositoryName(sourceRepositoryName); repositoryImport.setFilename(sourceFilePath); @@ -153,20 +150,12 @@ public void transform(FTTransformSession session) { ECLog.logFatal(this, e.getMessage()); } - FTTemplate template = new FTTemplate(null); - template.setName(sourceFilename + "." + sourceFileExtension); - if (domain != null) { - template.setDefaultDomainName(domain.getName()); - } - if (language != null) { - template.setLanguage(language.getName()); - } - MTDirectory directoryOfTemplateFile = new MTDirectory(null, "something"); directoryOfTemplateFile.setPath(destFilepath); MTFile templateFile = new MTFile(directoryOfTemplateFile, fileToInstall); - MTTemplate mtTemplate = new MTTemplate(null, session.getConfiguration(), templateFile); + MTTemplate mtTemplate = new MTTemplate(null, session.getConfiguration(), templateFile); + mtTemplate.setDirectoryPath(directoryPath); MTRepositoryImport mtRepositoryImport = new MTRepositoryImport(null, false); mtRepositoryImport.setRepositoryName(repository.getName()); mtTemplate.setRepositoryImport(mtRepositoryImport); @@ -190,6 +179,7 @@ public void transform(FTTransformSession session) { } return; } + ftTemplate.setDirectoryPath(directoryPath); ftTemplate.insertTopContainer(ftFile); ftTemplate.transform(session); // run it with this session } diff --git a/src/main/java/org/entityc/compiler/transform/template/tree/FTTemplate.java b/src/main/java/org/entityc/compiler/transform/template/tree/FTTemplate.java index 9736f67..2822bc4 100644 --- a/src/main/java/org/entityc/compiler/transform/template/tree/FTTemplate.java +++ b/src/main/java/org/entityc/compiler/transform/template/tree/FTTemplate.java @@ -23,6 +23,7 @@ import org.entityc.compiler.transform.template.TemplateDocExtractor; import org.entityc.compiler.transform.template.TemplatePublishing; import org.entityc.compiler.transform.template.formatter.TemplateFormatController; +import org.entityc.compiler.util.ECStringUtil; import org.entityc.compiler.util.ECVersion; import java.io.File; @@ -43,10 +44,12 @@ description = "Represents an actual template containing code to execute.") public class FTTemplate extends FTContainerNode implements MTNamed { + private static DocumentationManager documentationManager = new DocumentationManager(); private final List calledExternalFunctions = new ArrayList<>(); private final Map publishers = new HashMap<>(); private final List authors = new ArrayList<>(); private String name; + private String directoryPath; private ECVersion version; private String language; private String defaultDomainName; @@ -55,9 +58,8 @@ public class FTTemplate extends FTContainerNode implements MTNamed { private Map functionsByName = new HashMap<>(); private List referencedTags = new ArrayList<>(); private Set importedTemplates = new HashSet<>(); - private boolean hasOnlyFunctions = false; - private MTRepository repository; - private static DocumentationManager documentationManager = new DocumentationManager(); + private boolean hasOnlyFunctions = false; + private MTRepository repository; public FTTemplate(ParserRuleContext ctx) { super(ctx, null); @@ -189,17 +191,6 @@ public void transform(FTTransformSession session) { } } - @Override - @ModelMethod(category = ModelMethodCategory.TEMPLATE, - description = "Returns the name of this template.") - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - public ECVersion getVersion() { return version; } @@ -352,18 +343,37 @@ public static int GetTemplateLexerSymbol() { return 0; } - public void setRepository(MTRepository repository) { - this.repository = repository; - } - public MTRepository getRepository() { return repository; } + public void setRepository(MTRepository repository) { + this.repository = repository; + } + public String getUri() { if (repository != null) { - return repository.getUri() + "/" + getName(); + return repository.getUri() + File.separator + ECStringUtil.PathWithSeparator(directoryPath) + getName(); } return getName(); } + + @Override + @ModelMethod(category = ModelMethodCategory.TEMPLATE, + description = "Returns the name of this template.") + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDirectoryPath() { + return directoryPath; + } + + public void setDirectoryPath(String directoryPath) { + this.directoryPath = directoryPath; + } } diff --git a/src/main/java/org/entityc/compiler/transform/template/tree/FTTransformSession.java b/src/main/java/org/entityc/compiler/transform/template/tree/FTTransformSession.java index 7438113..d0e8e03 100644 --- a/src/main/java/org/entityc/compiler/transform/template/tree/FTTransformSession.java +++ b/src/main/java/org/entityc/compiler/transform/template/tree/FTTransformSession.java @@ -18,11 +18,10 @@ import org.entityc.compiler.model.entity.MTEntity; import org.entityc.compiler.model.entity.MTRelationship; import org.entityc.compiler.model.language.MTLanguage; +import org.entityc.compiler.project.ProjectManager; import org.entityc.compiler.protobuf.PBASTVisitor; import org.entityc.compiler.protobuf.PBLoaderExtractor; import org.entityc.compiler.util.ECLog; -import org.entityc.compiler.util.ECSessionFiles; -import org.entityc.compiler.util.ECSessionManager; import java.util.ArrayList; import java.util.Arrays; @@ -42,7 +41,6 @@ public class FTTransformSession { private final Stack scopeStack = new Stack<>(); private final Stack bodyBlockStack = new Stack<>(); private final Map receiveMap = new HashMap<>(); - private final ECSessionFiles sessionFiles; private final Map authorScopeMap = new HashMap<>(); private FTTemplate template; private MTTransform templateTransform; @@ -56,15 +54,15 @@ public class FTTransformSession { private boolean debugMode; public FTTransformSession(MTRoot root, MTConfiguration configuration, FTTemplate template) { - this.space = root.getSpace(); + this.space = root.getSpace(); this.configuration = configuration; - this.template = template; + this.template = template; if (EntityCompiler.isVerbose()) { ECLog.logInfo("============= CREATED SCOPE FOR template: " + template.getName() + " =================="); } pushScope(new Scope()); if (configuration != null) { - this.configuration = configuration; + this.configuration = configuration; this.templateTransform = configuration.getTransformByName(template.getName()); this.templateTransform.setVersion(this.template.getVersion()); String outputName = this.templateTransform.getOutputNameByLocalName("primary"); @@ -81,7 +79,13 @@ public FTTransformSession(MTRoot root, MTConfiguration configuration, FTTemplate } addReadonlyNamedValue("true", Boolean.TRUE); addReadonlyNamedValue("false", Boolean.FALSE); - sessionFiles = ECSessionManager.getInstance().getSessionFiles(template.getName()); + } + + private void pushScope(Scope scope) { + if (false && EntityCompiler.isVerbose()) { + ECLog.logInfo("++++++ PUSHED SCOPE"); + } + scopeStack.push(scope); } public void addReadonlyNamedValue(String name, Object value) { @@ -122,12 +126,17 @@ public void setDebugMode(boolean debugMode) { not going to execute its generating template code because it has the ifnotexist set. */ public void registerFileBlock(FTFile fileBlock) { - sessionFiles.addFilePath(fileBlock.getFullFilePath()); + ProjectManager.getInstance().addGeneratedFile(fileBlock.getFullFilePath(), configuration.getName(), + this.template.getUri() + ); } + public void pushFileBlock(FTFile fileBlock) { fileBlock.getBody().clear(); bodyBlockStack.push(fileBlock); - sessionFiles.addFilePath(fileBlock.getFullFilePath()); + ProjectManager.getInstance().addGeneratedFile(fileBlock.getFullFilePath(), configuration.getName(), + this.template.getUri() + ); } public void popFileBlock() { @@ -250,13 +259,15 @@ public void popPreserveBlock(FTTransformSession session) { String commentStart = "//"; String commentEnd = null; if (language == null) { - ECLog.logError(session.getTemplate(), "Cannot find language \"" + session.getTemplate().getLanguage() + "\" so defaulting line comment to //"); + ECLog.logError(session.getTemplate(), "Cannot find language \"" + session.getTemplate().getLanguage() + + "\" so defaulting line comment to //"); } else if (language.getLineComment() == null) { if (language.getBlockCommentStart() != null && language.getBlockCommentEnd() != null) { commentStart = language.getBlockCommentStart(); - commentEnd = language.getBlockCommentEnd(); + commentEnd = language.getBlockCommentEnd(); } else { - ECLog.logError("Cannot find declaration of line or block comment for language \"" + session.getTemplate().getLanguage() + "\" so defaulting line comment to //"); + ECLog.logError("Cannot find declaration of line or block comment for language \"" + + session.getTemplate().getLanguage() + "\" so defaulting line comment to //"); } } else { commentStart = language.getLineComment(); @@ -277,7 +288,9 @@ public boolean test(FTBodyBlock ftBodyBlock) { Optional extractedTextOptional = fileBlock.extract(startMarker, endMarker); if (!extractedTextOptional.isPresent() && preserve.getDeprecatedNames().size() > 0) { for (String deprecatedName : preserve.getDeprecatedNames()) { - extractedTextOptional = fileBlock.extract(preserve.getStartMarker(commentStart, commentEnd, deprecatedName), preserve.getEndMarker(commentStart, commentEnd, deprecatedName)); + extractedTextOptional = fileBlock.extract( + preserve.getStartMarker(commentStart, commentEnd, deprecatedName), + preserve.getEndMarker(commentStart, commentEnd, deprecatedName)); if (extractedTextOptional.isPresent()) { break; } @@ -378,9 +391,11 @@ public Object getValue(String variableName) { // if (debugMode) { // ECLog.logInfo("DEBUG>> Getting value for \"" + variableName + "\" => " + scopeStack.peek().namedValues.get(variableName)); // } - Scope currentScope = scopeStack.peek(); + Scope currentScope = scopeStack.peek(); boolean isInternalVariable = variableName.startsWith("__"); - for (Scope scope = currentScope; scope != null; scope = isInternalVariable ? scope.parentScopeForInternalVariables : scope.parentScope) { + for (Scope scope = currentScope; scope != null; scope = isInternalVariable ? + scope.parentScopeForInternalVariables : + scope.parentScope) { Object value = scope.namedValues.get(variableName); if (value != null) { // if (scope.name != null) { @@ -451,7 +466,9 @@ public String getOutputStringValue() { public void pushFunctionScope(FTFunction function) { Scope newScope = new Scope(); - newScope.parentScopeForInternalVariables = scopeStack.size() > 0 ? scopeStack.peek() : null; + newScope.parentScopeForInternalVariables = scopeStack.size() > 0 ? + scopeStack.peek() : + null; //ECLog.logInfo("------------- Pushing Function Scope: " + function.getName()); pushScope(newScope); } @@ -460,34 +477,30 @@ public void popFunctionScope() { popScope(); } + private void popScope() { + if (false && EntityCompiler.isVerbose()) { + ECLog.logInfo("------ POPPED SCOPE"); + } + scopeStack.pop(); + } + public void pushAuthorScope(FTAuthor author) { String publisherId = author.getTopAuthor().getUniqueId(); Scope scopeToUse = null; if (!authorScopeMap.containsKey(publisherId)) { - scopeToUse = new Scope(); + scopeToUse = new Scope(); scopeToUse.name = publisherId; authorScopeMap.put(publisherId, scopeToUse); } else { scopeToUse = authorScopeMap.get(publisherId); } - scopeToUse.parentScope = scopeStack.size() > 0 ? scopeStack.peek() : null; + scopeToUse.parentScope = scopeStack.size() > 0 ? + scopeStack.peek() : + null; scopeToUse.parentScopeForInternalVariables = scopeToUse.parentScope; pushScope(scopeToUse); } - private void pushScope(Scope scope) { - if (false && EntityCompiler.isVerbose()) { - ECLog.logInfo("++++++ PUSHED SCOPE"); - } - scopeStack.push(scope); - } - - private void popScope() { - if (false && EntityCompiler.isVerbose()) { - ECLog.logInfo("------ POPPED SCOPE"); - } - scopeStack.pop(); - } public void popAuthorScope() { popScope(); // its removed from the stack but not from the map } diff --git a/src/main/java/org/entityc/compiler/util/ECANTLRErrorListener.java b/src/main/java/org/entityc/compiler/util/ECANTLRErrorListener.java index 3e8499d..379bdcb 100644 --- a/src/main/java/org/entityc/compiler/util/ECANTLRErrorListener.java +++ b/src/main/java/org/entityc/compiler/util/ECANTLRErrorListener.java @@ -20,7 +20,7 @@ public ECANTLRErrorListener(String filename) { @Override public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) { - System.err.println(filename + ":" + line + "," + charPositionInLine + " " + msg); + ECLog.logFatal(filename + ":" + line + "," + charPositionInLine + " " + msg); // super.syntaxError(recognizer, offendingSymbol, line, charPositionInLine, msg, e); } } diff --git a/src/main/java/org/entityc/compiler/util/ECLog.java b/src/main/java/org/entityc/compiler/util/ECLog.java index a46f2cd..837a799 100644 --- a/src/main/java/org/entityc/compiler/util/ECLog.java +++ b/src/main/java/org/entityc/compiler/util/ECLog.java @@ -6,9 +6,10 @@ package org.entityc.compiler.util; +import org.antlr.v4.runtime.ParserRuleContext; +import org.entityc.compiler.cmdline.command.CLCommand; import org.entityc.compiler.model.MTNode; import org.entityc.compiler.transform.template.tree.FTNode; -import org.antlr.v4.runtime.ParserRuleContext; import java.io.File; @@ -20,7 +21,7 @@ public class ECLog { private static final String INFO = "INFO"; private static final String WARNING = "WARNING"; private static final String ERROR = "ERROR"; - private static final String FATAL = "ERROR"; + private static final String FATAL = "FATAL"; static boolean exitOnFatal = true; @@ -29,16 +30,16 @@ public static void SetExitOnFatal(boolean shouldExit) { } public static void logInfo(ParserRuleContext ctx, String message) { - log("INFO", ctx, message, false); + log(INFO, ctx, message, false); } private static void log(String level, ParserRuleContext ctx, String message, boolean fatal) { if (ctx == null) { - System.out.println(level + ": " + message); + log(level, message, fatal); } else { String filename = filenameFromFullPath(ctx.getStart().getTokenSource().getSourceName()); System.out.println(level + ": " + filename + "(" + ctx.getStart().getLine() + "," + ( - ctx.getStart().getCharPositionInLine() + 1) + ") " + message); + ctx.getStart().getCharPositionInLine() + 1) + ") " + message + endColorization()); } if (fatal) { if (exitOnFatal) { @@ -49,6 +50,17 @@ private static void log(String level, ParserRuleContext ctx, String message, boo } } + private static void log(String level, String message, boolean fatal) { + if (level.isEmpty()) { + System.out.println(message); + } else { + System.out.println(colorizedLevel(level) + ": " + message + endColorization()); + } + if (fatal) { + exit(1); + } + } + private static String filenameFromFullPath(String fullPath) { if (fullPath == null) { return "??"; @@ -60,6 +72,19 @@ private static String filenameFromFullPath(String fullPath) { return fullPath.substring(lastSlash + 1); } + private static String endColorization() { + return CLCommand.StdoutColor.Default.getStringValue(); + } + + private static String colorizedLevel(String level) { + if (level.equals(WARNING)) { + level = CLCommand.StdoutColor.YellowForeground.getStringValue() + level; + } else if (level.equals(ERROR) || level.equals(FATAL)) { + level = CLCommand.StdoutColor.RedForeground.getStringValue() + level; + } + return level; + } + public static void logWarning(ParserRuleContext ctx, String message) { log(WARNING, ctx, message, false); } @@ -84,18 +109,7 @@ private static void log(String level, FTNode node, String message, boolean fatal String filename = filenameFromFullPath(node.getSourceName()); System.out.println( level + ": " + filename + "(" + node.getStartLineNumber() + "," + node.getStartCharPosition() + ") " - + message); - if (fatal) { - exit(1); - } - } - - private static void log(String level, String message, boolean fatal) { - if (level.isEmpty()) { - System.out.println(message); - } else { - System.out.println(level + ": " + message); - } + + message + endColorization()); if (fatal) { exit(1); } @@ -124,8 +138,9 @@ private static void log(String level, MTNode node, String message, boolean fatal } String filename = filenameFromFullPath(node.getSourceName()); System.out.println( - level + ": " + filename + "(" + node.getStartLineNumber() + "," + node.getStartCharPosition() + ") " - + message); + colorizedLevel(level) + ": " + filename + "(" + node.getStartLineNumber() + "," + + node.getStartCharPosition() + ") " + + message + endColorization()); if (fatal) { exit(1); } diff --git a/src/main/java/org/entityc/compiler/util/ECSessionFiles.java b/src/main/java/org/entityc/compiler/util/ECSessionFiles.java deleted file mode 100644 index 3080640..0000000 --- a/src/main/java/org/entityc/compiler/util/ECSessionFiles.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.md file in the project root. - */ - -package org.entityc.compiler.util; - -import com.google.gson.Gson; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; -import org.entityc.compiler.EntityCompiler; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashSet; -import java.util.Set; - -public class ECSessionFiles { - - /** - * Name of the session that will be used in the naming of the session file. - */ - private final String sessionName; - /** - * The location to store a session file containing the list of files generated in the session. - */ - private final String sessionSaveDirFilepath; - Set filePaths = new HashSet<>(); - - public ECSessionFiles(String sessionName, String sessionSaveDirFilepath) { - this.sessionName = sessionName; - this.sessionSaveDirFilepath = sessionSaveDirFilepath; - } - - public void addFilePath(String filePath) { - filePaths.add(filePath); - } - - public void removeOldFiles() { - Set filesNotInLatestSessionFiles = load(); - if (filesNotInLatestSessionFiles == null) { - return; - } - filesNotInLatestSessionFiles.removeAll(this.filePaths); - - for (String pathOfFileToRemove : filesNotInLatestSessionFiles) { - //ECLog.logInfo("Deleting previously generated file no longer being generated: " + pathOfFileToRemove); - java.io.File file = new File(pathOfFileToRemove); - if (EntityCompiler.isVerbose() && !file.delete()) { - ECLog.logWarning("Unable to delete old generated file: " + pathOfFileToRemove); - } - } - } - - private Set load() { - Set returnSet = new HashSet<>(); - Gson gson = new Gson(); - String filepath = sessionFilename(); - try { - FileInputStream fis = new FileInputStream(filepath); - JsonReader reader = new JsonReader(new InputStreamReader(fis, StandardCharsets.UTF_8)); - returnSet = gson.fromJson(reader, Set.class); - reader.close(); - } catch (FileNotFoundException e) { - // will happen the first time since the file doesn't exist - } catch (IOException e) { - } - return returnSet; - } - - private String sessionFilename() { - return sessionSaveDirFilepath + File.separator + sessionName + ".json"; - } - - public void save() { - ArrayList sortedFilePaths = new ArrayList<>(this.filePaths); - sortedFilePaths.sort(new Comparator() { - @Override - public int compare(String o1, String o2) { - return o1.compareTo(o2); - } - }); - Gson gson = new Gson(); - String filepath = sessionFilename(); - try { - EntityCompiler.ensureDirectory(sessionSaveDirFilepath); - FileOutputStream fos = new FileOutputStream(filepath); - JsonWriter writer = new JsonWriter(new OutputStreamWriter(fos, StandardCharsets.UTF_8)); - writer.setIndent(" "); - gson.toJson(sortedFilePaths, ArrayList.class, writer); - writer.flush(); - fos.close(); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/src/main/java/org/entityc/compiler/util/ECSessionManager.java b/src/main/java/org/entityc/compiler/util/ECSessionManager.java deleted file mode 100644 index 43b4e76..0000000 --- a/src/main/java/org/entityc/compiler/util/ECSessionManager.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2019-2022 The EntityC Project. All rights reserved. - * Use of this file is governed by the BSD 3-clause license that - * can be found in the LICENSE.md file in the project root. - */ - -package org.entityc.compiler.util; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -public class ECSessionManager { - - private static final String ECDirectoryName = ".ec"; - private static final String SessionsDirectoryName = "sessions"; - private static final ECSessionManager instance = new ECSessionManager(); - private final Map sessionsByName = new HashMap<>(); - private String projectBaseDirPath; // the directory with the .ec directory in it - private File ecSessionsDirectory; - private boolean createIfDoesntExist = false; - - private ECSessionManager() { - } - - public static ECSessionManager getInstance() { - return instance; - } - - public boolean isCreateIfDoesntExist() { - return createIfDoesntExist; - } - - public void setCreateIfDoesntExist(boolean createIfDoesntExist) { - this.createIfDoesntExist = createIfDoesntExist; - } - - public String getProjectBaseDirPath() { - return projectBaseDirPath; - } - - public void setProjectBaseDirPath(String projectBaseDirPath) { - this.projectBaseDirPath = projectBaseDirPath; - } - - public void start() { - if (projectBaseDirPath == null) { - try { - String invocationDirectory = new File(".").getCanonicalPath(); - projectBaseDirPath = findECDirectory(invocationDirectory); - if (projectBaseDirPath == null) { - projectBaseDirPath = invocationDirectory; - } - } catch (IOException e) { - ECLog.logFatal("Unable to locate current working directory"); - } - } - - if (ecSessionsDirectory == null) { - ecSessionsDirectory = new File( - projectBaseDirPath + File.separator + ECDirectoryName + File.separator + SessionsDirectoryName); - } - ecSessionsDirectory.mkdirs(); - } - - private String findECDirectory(String fromHerePath) { - if (fromHerePath.equals(File.separator)) { - return null; // can't go up any more - } - File ecDirectory = new File(fromHerePath + File.separator + ECDirectoryName); - if (ecDirectory.exists()) { - return fromHerePath; - } - File parentDirectory = new File(fromHerePath + File.separator + ".."); - try { - return findECDirectory(parentDirectory.getCanonicalPath()); - } catch (IOException e) { - return null; - } - } - - public ECSessionFiles getSessionFiles(String sessionName) { - //we need to make sure all paths added to this session are relative to the found single project base path - - if (!sessionsByName.containsKey(sessionName)) { - sessionsByName.put(sessionName, new ECSessionFiles(sessionName, ecSessionsDirectory.getAbsolutePath())); - } - return sessionsByName.get(sessionName); - } - - public void close() { - for (ECSessionFiles sessionFiles : sessionsByName.values()) { - sessionFiles.removeOldFiles(); - sessionFiles.save(); - } - } -} diff --git a/src/main/java/org/entityc/compiler/util/ECStringUtil.java b/src/main/java/org/entityc/compiler/util/ECStringUtil.java index a5d1436..2c65f82 100644 --- a/src/main/java/org/entityc/compiler/util/ECStringUtil.java +++ b/src/main/java/org/entityc/compiler/util/ECStringUtil.java @@ -8,6 +8,7 @@ import org.atteo.evo.inflector.English; +import java.io.File; import java.util.ArrayList; import java.util.List; @@ -339,4 +340,35 @@ public static boolean IsUncapitalized(String text) { } return Character.isAlphabetic(text.charAt(0)) && !Character.isUpperCase(text.charAt(0)); } + + public static String RepeatString(String str, int times) { + StringBuilder sb = new StringBuilder(); + for (int i=0;i cmdLine = new ArrayList<>(); + cmdLine.add("build"); + cmdLine.add("Tutorial"); + cmdLine.add("--quiet"); + + cmdLine.add("-tp"); + cmdLine.add(TestResourceDir); for (String filename : files) { cmdLine.add(TestResourceDir + File.separator + filename); } - cmdLine.add("-c"); - cmdLine.add("Tutorial"); - cmdLine.add("-tp"); - cmdLine.add(TestResourceDir); - cmdLine.add("-D"); - cmdLine.add("TEMP_DIR=" + strTmp); + cmdLine.add("-DTEMP_DIR=" + strTmp); EntityCompiler.main(cmdLine.toArray(new String[0])); diff --git a/test/resources/compiler/main/CompilerDocs/CompilerDocsExpected.txt b/test/resources/compiler/main/CompilerDocs/CompilerDocsExpected.txt index 5084417..e878dc4 100644 --- a/test/resources/compiler/main/CompilerDocs/CompilerDocsExpected.txt +++ b/test/resources/compiler/main/CompilerDocs/CompilerDocsExpected.txt @@ -469,8 +469,8 @@ These methods relate to a part of application configuration. | Method/Property | |---| -| `String` **`directoryName`** | -| Returns the directory name if available. Otherwise returns `null` | +| `String` **`directoryPath`** | +| Returns the directory path if available. Otherwise returns `null` | |
| | `String` **`filename`** | | Returns the filename of the template which is preceded by a directory name if available. | @@ -943,6 +943,9 @@ These methods relate to relationships. | `boolean` **`hasParentRelationship`** | | Indicates whether it has a least one relationship declared as parent. | |
| +| `boolean` [**`hasParentRelationshipToEntity(MTEntity parentEntity)`**](#class_MTEntity_hasParentRelationshipToEntity) | +| Indicates whether it has a least one relationship declared as parent to the specified entity. | +|
| | `boolean` **`hasPrimaryParentRelationship`** | | Indicates whether this entity has a primary parent relationship. A primary parent relationship is one which is declared `parent` and **not** declared `optional`. | |
| @@ -1025,6 +1028,16 @@ Given an entity object, this method will try to find and return a relationship o + +#### Method `boolean hasParentRelationshipToEntity(MTEntity parentEntity)` + + +Indicates whether it has a least one relationship declared as parent to the specified entity. + +| Parameter | Description | +|-----|-----| +|`MTEntity parentEntity` | *no description* | + @@ -1632,6 +1645,9 @@ These methods relate to data types. | `MTEnum` **`enumType`** | | If this type is an enum type then it returns the enum type object. Otherwise it returns `null`. | |
| +| `boolean` **`isBooleanType`** | +| Indicates whether this type is one of the integer data types. | +|
| | `boolean` **`isByteArrayType`** | | Indicates whether this type is both an array type and also `byte` data type. | |
| @@ -1666,6 +1682,7 @@ These methods relate to data types. + #### Method `boolean isNativeDataType(DataType dataType)` @@ -3010,7 +3027,7 @@ These methods relate to an outlet. | Indicates whether this author has child authors that connect to an outlet. | |
| | `boolean` **`hasChildOutletsWithIntermediateParents`** | -| Indicates whether this author has children authors that connect to an outlet and there are intermediate parent authors (that is authors that don't connect to an outlet). | +| Indicates whether this author has children authors that connect to an outlet and there are intermediate parent authors (that is, authors that don't connect to an outlet). | |
| | `boolean` **`isOutletAuthor`** | | Indicates whether this author connects to an outlet. | diff --git a/test/resources/compiler/main/TemplateCodeFormatting/TemplateCodeFormattingExpected.eml b/test/resources/compiler/main/TemplateCodeFormatting/TemplateCodeFormattingExpected.eml index 08ba037..6906ca8 100644 --- a/test/resources/compiler/main/TemplateCodeFormatting/TemplateCodeFormattingExpected.eml +++ b/test/resources/compiler/main/TemplateCodeFormatting/TemplateCodeFormattingExpected.eml @@ -13,22 +13,22 @@ Enum: ${enum.name} ${qualifier}Entity: ${entity.name} $[foreach attribute in entity.attributes] $[switch attribute.type] - $[case string] - $[let typeLongName = "String of characters"] + $[case uuid] + $[let typeLongName = "Unique Identifier - 128 bits"] $[case int32] $[let typeLongName = "32-bit Integer"] $[case int64] $[let typeLongName = "64-bit Integer"] - $[case double] - $[let typeLongName = "64-bit Floating Point"] $[case float] $[let typeLongName = "32-bit Floating Point"] - $[case uuid] - $[let typeLongName = "Unique Identifier - 128 bits"] - $[case typedef] - $[let typeLongName = "Typedef named " + attribute.type.name] + $[case double] + $[let typeLongName = "64-bit Floating Point"] + $[case string] + $[let typeLongName = "String of characters"] $[case enum] $[let typeLongName = "Enum named " + attribute.type.name] + $[case typedef] + $[let typeLongName = "Typedef named " + attribute.type.name] $[case entity] $[let typeLongName = "Entity named " + attribute.type.name] $[default]