pax_global_header00006660000000000000000000000064125213124160014507gustar00rootroot0000000000000052 comment=eb254d106a49bf64ee9c5a8054aefebc6daea0be angular-maven-plugin-angular-maven-plugin-0.3.4/000077500000000000000000000000001252131241600215735ustar00rootroot00000000000000angular-maven-plugin-angular-maven-plugin-0.3.4/.gitignore000066400000000000000000000000661252131241600235650ustar00rootroot00000000000000/bin /target .project .classpath .directory .settings angular-maven-plugin-angular-maven-plugin-0.3.4/LICENSE000066400000000000000000000020701252131241600225770ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2013 Keith Branton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. angular-maven-plugin-angular-maven-plugin-0.3.4/README.md000066400000000000000000000014471252131241600230600ustar00rootroot00000000000000angular-maven-plugin ==================== A plugin designed to help developers who are deploying angularjs applications, but use maven as a build tool. So far there are two goals: [html2js](doc/html2js.md) ------- Mimics grunt-html2js in combining html templates into a single javascript file for use with Angular.js. It does NOT use grunt or node. [join](doc/join.md) ---- a more complex goal designed to simplify assembly of a large modular angularjs application where modules are lazy loaded. The goal only deals with the reorganization of the code, not the lazy loading itself. Usage ----- This plugin is hosted in Maven Central... com.keithbranton.mojo angular-maven-plugin 0.3.4 angular-maven-plugin-angular-maven-plugin-0.3.4/doc/000077500000000000000000000000001252131241600223405ustar00rootroot00000000000000angular-maven-plugin-angular-maven-plugin-0.3.4/doc/html2js.md000066400000000000000000000072151252131241600242520ustar00rootroot00000000000000html2js ======= A maven plugin goal that mimics grunt-html2js in combining html templates into a single javascript file for use with Angular.js. It does NOT use grunt or node. The html2js goal allows the specification of a source directory, include and exclude file patterns, and a target. It will collect all the html from the source directory that satisfies the include/exclude requirements and convert them into javascript statements and write them to target. If you use require.js (as I do) then you can set the addRequireWrapper flag that will cause the code to be wrapped in the appropriate declare. To use the plugin add the following to the pom of the project containing the templates... com.keithbranton.mojo angular-maven-plugin 0.3.4 generate-resources html2js ${basedir}/src/main/template/ angular **/*.html not-this.html ${basedir}/src/main/generated/js/templates.js true /templateCachePrefix ... The above shows examples of the configuration parameters available. The values shown for sourceDir, include and target are the default values provided by the plugin. By default there is no exclude or prefix and addRequireWrapper is false. Using html2js with Eclipse (kepler) ----------------------------------- As I work I like the template.js file to be updated whenever I add/change/delete a template. The plugin is designed to be incremental build aware. To use it add the following section to your pom... org.eclipse.m2e lifecycle-mapping 1.0.0 com.keithbranton.mojo angular-maven-plugin [0.1-SNAPSHOT,) html2js true ... With that in place you should be able to make a change to a template and see the result in your target file as soon as you save the change. The plugin adds some info to the maven log, which can be seen in the eclipse console if you choose the maven console. This can be helpful in diagnosing misconfiguration-type problems. Changes ------- Feel free to fork if you'd rather take the plugin in a different direction. If you'd like to send me pull requests for improvements that you think could benefit the community I'd be happy to consider them. Thanks ------ Thanks to jkorri for adding the optional configuration option, which adds the supplied prefix to the start of each template name in the template cache. Thanks to cybercomkvint for reporting and supplying a pull request to correct a windows incompatibility. Thanks to yexela for submitting a pull request to allow the name of the angular dependency to be configured. angular-maven-plugin-angular-maven-plugin-0.3.4/doc/join.md000066400000000000000000000117011252131241600236210ustar00rootroot00000000000000join ==== This goal is designed to build modular angularjs applications that are intended to be lazy loaded. Angularjs doesn't really support lazy loading of angular modules at this point. I have a customized router which manages this for me and I use angularjs pull request #4694 to allow modules to be added to the injector after bootstrap. There are probably a lot of assumptions made about project layout so I'm going to describe how I organize my angularjs code and what the results are. src/main/js/ |__app.js |__main.js |__first | |__firstModule.js | |__template1.html | |__firstDirectives.js |__second | |__secondModule.js | |__template2.js | |__template3.js |__utility |__commonDirectives.js |__commonServices.js |__template4.js |__template5.js When I use this goal... com.keithbranton.mojo angular-maven-plugin 0.3.4 generate-sources join src/main/js/ ${target.dir}/js/ *.html,utility/*.html ...it produces: target/js/ |__app.js |__main.js |__firstModule.js |__secondModule.js **firstModule.js** declares a dependency on "/js/first/firstDirectives.js" to requirejs in a declare function. The declare is parsed (pretty naively, using a regex). The resulting firstModule.js will consist of firstDirectives.js, wrapped in an immediate function to prevent any contamination, then a template cache insertion statement like the html2js goal generates, then will return the result of an immediate function wrapping firstModule.js. **secondModule.js** will include the templates 2 and 3 and secondModule the same way as firstModule.js does When these modules are combined a new define call is generated combining all the external requirejs dependencies of all the combined file in what is hopefully a sensible way. **app.js** will contain commonDirectives and commonServices because it refers to them. It will also include templates 4 and 5 because they are in the utility folder and the templates pattern we provided in the goal configuration includes them. Assuming **main.js** contains content such as require.config({ paths : { 'FirstModule' : '/js/first/firstModule', 'SecondModule' : '/js/second/secondModule' } }); this will be changed to require.config({ paths : { 'FirstModule' : '/js/firstModule', 'SecondModule' : '/js/secondModule' } }); Configuration Options --------------------- Option | Description --- | --- source | the folder containing the source code and templates - defaults to /src/main/js main | the name of the main file - defaults to main.js app | the name of the app file - defaults to app.js modules | a comma separated list of glob patterns that identify the starting point for a module, defaults to **/*Module.js templates | a comma separated list of glob patterns that identify html templates - defaults to *.html joinable | a comma separated list of glob patterns that identify dependencies that should be joined, defaults to /js/**/*.js/ target | where to put the resulting files prefix | a prefix to add to all the template cache keys Using join with Eclipse (kepler) ----------------------------------- As I work I like to generate these files incrementally whenever any of their source files are changed. The goal is designed to be incremental build aware. To use it add the following section to your pom... org.eclipse.m2e lifecycle-mapping 1.0.0 com.keithbranton.mojo angular-maven-plugin [0.1-SNAPSHOT,) join true ... With that in place you should be able to make a change to a file and see the result in the corresponding target file as soon as you save the change. The goal adds some info to the maven log, which can be seen in the eclipse console if you choose the maven console. This can be helpful in diagnosing misconfiguration-type problems. Changes ------- Feel free to fork if you'd rather take the goal in a different direction. If you'd like to send me pull requests for improvements that you think could benefit the community I'd be happy to consider them. angular-maven-plugin-angular-maven-plugin-0.3.4/pom.xml000066400000000000000000000065511252131241600231170ustar00rootroot00000000000000 4.0.0 Angular Maven Plugin org.sonatype.oss oss-parent 7 com.keithbranton.mojo angular-maven-plugin 0.3.4 maven-plugin https://github.com/keithbranton/angular-maven-plugin MIT License http://www.opensource.org/licenses/mit-license.php scm:git:git@github.com:keithbranton/angular-maven-plugin.git scm:git:git@github.com:keithbranton/angular-maven-plugin.git git@github.com:keithbranton/angular-maven-plugin.git UTF-8 org.apache.maven.plugins maven-compiler-plugin 3.0 1.7 1.7 false true false javac org.codehaus.plexus plexus-compiler-eclipse 2.1 org.apache.maven.plugins maven-plugin-plugin 3.3 true mojo-descriptor descriptor commons-lang commons-lang 2.6 commons-io commons-io 2.4 com.google.guava guava 17.0 org.apache.maven maven-plugin-api 3.2.3 org.apache.maven.plugin-tools maven-plugin-annotations 3.3 org.apache.maven maven-project 2.2.1 org.sonatype.plexus plexus-build-api 0.0.7 angular-maven-plugin-angular-maven-plugin-0.3.4/src/000077500000000000000000000000001252131241600223625ustar00rootroot00000000000000angular-maven-plugin-angular-maven-plugin-0.3.4/src/main/000077500000000000000000000000001252131241600233065ustar00rootroot00000000000000angular-maven-plugin-angular-maven-plugin-0.3.4/src/main/java/000077500000000000000000000000001252131241600242275ustar00rootroot00000000000000angular-maven-plugin-angular-maven-plugin-0.3.4/src/main/java/com/000077500000000000000000000000001252131241600250055ustar00rootroot00000000000000angular-maven-plugin-angular-maven-plugin-0.3.4/src/main/java/com/keithbranton/000077500000000000000000000000001252131241600274755ustar00rootroot00000000000000angular-maven-plugin-angular-maven-plugin-0.3.4/src/main/java/com/keithbranton/mojo/000077500000000000000000000000001252131241600304415ustar00rootroot00000000000000angular-maven-plugin-angular-maven-plugin-0.3.4/src/main/java/com/keithbranton/mojo/GlobMatcher.java000066400000000000000000000027261252131241600335020ustar00rootroot00000000000000package com.keithbranton.mojo; import java.io.File; import org.codehaus.plexus.util.AbstractScanner; public class GlobMatcher { private static class GlobScanner extends AbstractScanner { private GlobScanner(final String[] globs) { setIncludes(globs); } @Override public void scan() { throw new UnsupportedOperationException(); } @Override public String[] getIncludedFiles() { throw new UnsupportedOperationException(); } @Override public String[] getIncludedDirectories() { throw new UnsupportedOperationException(); } @Override public File getBasedir() { throw new UnsupportedOperationException(); } public boolean matches(final String name) { // System.out.println("Glob Testing " + name); return isIncluded(name); } } private final String relativedir; private final String absolutedir; private final GlobScanner globScanner; public GlobMatcher(final File absolutedir, final File relativedir, final String[] globs) { this.absolutedir = absolutedir.getAbsolutePath(); this.relativedir = relativedir.getAbsolutePath() + "/"; // System.out.println("absolutedir: " + this.absolutedir + ", relativedir: " + this.relativedir); globScanner = new GlobScanner(globs); } public boolean matches(final File file) { return globScanner.matches(file.getAbsolutePath().replace(absolutedir, "/")); } public File makeFile(final String name) { return new File((name.startsWith("/") ? absolutedir : relativedir) + name); } }angular-maven-plugin-angular-maven-plugin-0.3.4/src/main/java/com/keithbranton/mojo/Html2jsMojo.java000066400000000000000000000227731252131241600334670ustar00rootroot00000000000000package com.keithbranton.mojo; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.DirectoryScanner; import org.codehaus.plexus.util.Scanner; import org.sonatype.plexus.build.incremental.BuildContext; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.Lists; /** * Maven/Java approximation of grunt-html2js functionality * * @author Keith Branton */ @Mojo(name = "html2js", defaultPhase = LifecyclePhase.GENERATE_RESOURCES) public class Html2jsMojo extends AbstractMojo { // plexus injected fields first @Parameter(defaultValue = "${project}", readonly = true) protected MavenProject project; /** * The name of the overall module to use for the templates */ @Parameter(defaultValue = "templates-main", required = true) private String moduleName; /** * Specifies the source of the template files. */ @Parameter(defaultValue = "${basedir}/src/main/templates/", required = true) private File sourceDir; /** * Specifies the angular location in requireJS configuration. */ @Parameter(defaultValue = "angular") private String angularDependency; /** * Comma separated list of patterns to identify files to be treated as templates */ @Parameter(defaultValue = "**/*.html") private String include; /** * Prefix to put before the cache key */ @Parameter private String prefix; /** * Comma separated list of patterns to identify files to be ignored */ @Parameter private String exclude; /** * Location for the generated templates js file */ @Parameter(defaultValue = "${basedir}/src/main/generated/js/templates.js", required = true) private File target; /** * A flag to indicate if a require.js compatible wrapper should be written around the output */ @Parameter(defaultValue = "false", required = true) private boolean addRequireWrapper; /** * A flag to control multiModule mode - i.e. the generation of a module per template - for compatibility with the grunt task */ @Parameter(defaultValue = "false", required = true) private boolean multiModule; /** * Statements to insert at top of generated file - e.g. jshint directives */ @Parameter private List preambles; @Component(role = org.sonatype.plexus.build.incremental.BuildContext.class) private BuildContext buildContext; // Local fields below this point private String[] includes; private String[] excludes; /** @see org.apache.maven.plugin.Mojo#execute() */ @Override public void execute() throws MojoExecutionException { long start = System.currentTimeMillis(); try { includes = include == null ? null : include.split(","); excludes = exclude == null ? null : exclude.split(","); prefix = prefix == null ? "" : prefix; getLog().debug("-------------------------------------------------"); getLog().debug("---Html2js Mojo ---------------------------------"); getLog().debug("---moduleName: " + moduleName); getLog().debug("---sourceDir: " + sourceDir.getAbsolutePath()); getLog().debug("---angularDependency: " + angularDependency); getLog().debug("---includes: " + (includes == null ? "null" : Arrays.asList(includes))); getLog().debug("---excludes: " + (excludes == null ? "null" : Arrays.asList(excludes))); getLog().debug("---target: " + target.getAbsolutePath()); getLog().debug("---addRequireWrapper: " + addRequireWrapper); getLog().debug("---prefix: \"" + prefix + "\""); getLog().debug("---multiModule: " + multiModule); getLog().debug("---preambles: " + preambles); getLog().debug("-------------------------------------------------"); if (!isBuildNeeded()) { getLog().info("Html2js:: Nothing to do"); return; } if (!target.getParentFile().exists()) { target.getParentFile().mkdirs(); } try { doIt(); } catch (final Exception e) { throw new MojoExecutionException("", e); } } finally { getLog().info("Html2js:: took " + (System.currentTimeMillis() - start) + "ms"); } } /** * We can skip if no files were deleted, modified or added since the last build AND the target file is still there * * @return true if a build is needed, otherwise false */ private boolean isBuildNeeded() { if (!buildContext.isIncremental()) { // always needed if we're not doing an incremental build getLog().info("Html2js:: full build"); return true; } // ensure the target exists if (!target.exists()) { getLog().info("Html2js:: detected target file missing"); return true; } // check for any deleted files List deleted = findFiles(buildContext.newDeleteScanner(sourceDir)); for (File deletedFile : deleted) { getLog().info("Html2js:: detected deleted template: " + shorten(deletedFile)); } // next check for any new/changed files List changed = findFiles(buildContext.newScanner(sourceDir)); for (File changedFile : changed) { getLog().info("Html2js:: detected new/changed template: " + shorten(changedFile)); } if (changed.size() > 0 || deleted.size() > 0) { return true; } // determine the last modified template long lastModified = 0; File lastModifiedFile = null; for (File templateFile : findFiles()) { if (templateFile.lastModified() > lastModified) { lastModifiedFile = templateFile; lastModified = templateFile.lastModified(); } } // check if the target is as recent as the last modified template if (lastModifiedFile != null && !buildContext.isUptodate(target, lastModifiedFile)) { getLog().info("Html2js:: target file was changed or is older than " + shorten(lastModifiedFile)); return true; } return false; } private String shorten(final File file) { return shorten(file.getAbsolutePath()); } private String shorten(final String absolute) { return absolute.replace(sourceDir.getAbsolutePath(), ""); } private void doIt() throws Exception { if (sourceDir == null || !sourceDir.exists()) { throw new MojoExecutionException("Html2js:: Could not find the source folder: " + sourceDir.getAbsolutePath()); } // first make a list of all templates List files = findFiles(); Collections.sort(files); List lines = new ArrayList<>(); // add the preambles if (preambles != null) { lines.addAll(preambles); } if (addRequireWrapper) { lines.add("define(['" + angularDependency + "'], function (angular){"); lines.add(""); } for (final File file : files) { getLog().debug("Html2js:: found: " + file.getName()); } if (multiModule) { lines.add("angular.module('" + moduleName + "'" + ", ['" + Joiner.on("', '").join(Lists.transform(files, new Function() { @Override public String apply(final File file) { return prefix + file.getAbsolutePath().replace(sourceDir.getAbsolutePath(), "").replace("\\", "/"); } })) + "']" + ");"); lines.add(""); } else { lines.add("angular.module('" + moduleName + "', []).run(['$templateCache', function($templateCache) {"); } for (final File file : files) { String shortName = prefix + file.getAbsolutePath().replace(sourceDir.getAbsolutePath(), "").replace("\\", "/"); if (multiModule) { lines.add("angular.module('" + shortName + "', []).run(['$templateCache', function($templateCache) {"); } List fileLines = null; try { fileLines = FileUtils.readLines(file); } catch (IOException ex) { throw new MojoExecutionException("Html2js:: Unable to read template file: " + file.getAbsolutePath(), ex); } if (fileLines.isEmpty()) { lines.add("\t$templateCache.put('" + shortName + "', \"\");"); } else { lines.add("\t$templateCache.put('" + shortName + "',"); for (String line : fileLines) { lines.add("\t\"" + line.replace("\\", "\\\\").replace("\"", "\\\"") + "\\n\" +"); } lines.set(lines.size() - 1, StringUtils.chomp(lines.get(lines.size() - 1), "\\n\" +") + "\");"); } if (multiModule) { lines.add("}]);"); lines.add(""); } } if (!multiModule) { lines.add("}]);"); } if (addRequireWrapper) { lines.add(""); lines.add("return null;"); lines.add("});"); } // finally emit the output file try { getLog().info("Html2js:: Writing output file: " + target.getAbsolutePath()); FileUtils.writeLines(target, lines); } catch (final IOException ex) { throw new MojoExecutionException("Html2js:: Unable to write output file: " + target.getAbsolutePath(), ex); } buildContext.refresh(target); } private List findFiles() { final DirectoryScanner scanner = new DirectoryScanner(); scanner.setBasedir(sourceDir); return findFiles(scanner); } private List findFiles(final Scanner scanner) { final List results = new ArrayList(); if (includes != null && includes.length > 0) { scanner.setIncludes(includes); } if (excludes != null && excludes.length > 0) { scanner.setExcludes(excludes); } scanner.addDefaultExcludes(); scanner.scan(); for (final String name : scanner.getIncludedFiles()) { results.add(new File(scanner.getBasedir(), name)); } return results; } } angular-maven-plugin-angular-maven-plugin-0.3.4/src/main/java/com/keithbranton/mojo/JoinMojo.java000066400000000000000000000351071252131241600330360ustar00rootroot00000000000000package com.keithbranton.mojo; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.DirectoryScanner; import org.codehaus.plexus.util.Scanner; import org.sonatype.plexus.build.incremental.BuildContext; import com.google.common.base.Charsets; import com.google.common.base.Joiner; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.common.io.Files; /** * Bundle an angularjs application for modular production deployment * * Combine all files for each module in a project into a single module file * * @author Keith Branton */ @Mojo(name = "join"// , defaultPhase = LifecyclePhase.GENERATE_SOURCES ) public class JoinMojo extends AbstractMojo { // plexus injected fields first @Parameter(defaultValue = "${project}", readonly = true) protected MavenProject project; /** * Specifies the location of the files to join. */ @Parameter(defaultValue = "${basedir}/src/main/js/", required = true) private File source; /** * Filename of the main.js file */ @Parameter(defaultValue = "main.js") private String main; /** * Filename of the app.js file */ @Parameter(defaultValue = "app.js") private String app; /** * Pattern for modules */ @Parameter(defaultValue = "**/*Module.js", required = true) private String modules; /** * Comma separated list of patterns of the templates to process */ @Parameter(defaultValue = "*.html") private String templates; /** * Comma separated list of patterns that identify files to be joined */ @Parameter(defaultValue = "/js/**/*.js") private String joinable; /** * Location for the generated files */ @Parameter(defaultValue = "${target.dir}", required = true) private File target; /** * Prefix to put before the cache key */ @Parameter(defaultValue = "") private String prefix; @Component(role = org.sonatype.plexus.build.incremental.BuildContext.class) private BuildContext buildContext; // Local fields below this point private File mainFile, appFile; private String[] modulesArray; private String[] templatesArray; private String[] joinableArray; private final Map moduleMap = new HashMap<>(); /** @see org.apache.maven.plugin.Mojo#execute() */ @Override public void execute() throws MojoExecutionException { long start = System.currentTimeMillis(); try { mainFile = new File(source, main); appFile = new File(source, app); modulesArray = modules == null ? null : modules.split(","); templatesArray = templates == null ? null : templates.split(","); joinableArray = joinable == null ? null : joinable.split(","); prefix = prefix == null ? "" : prefix; getLog().info("-------------------------------------------------"); getLog().info("---Join Mojo ------------------------------------"); getLog().info("---source: " + source.getAbsolutePath()); getLog().info("---main: " + mainFile); getLog().info("---app: " + appFile); getLog().info("---modules: " + (modulesArray == null ? "null" : Arrays.asList(modulesArray))); getLog().info("---templates: " + (templatesArray == null ? "null" : Arrays.asList(templatesArray))); getLog().info("---joinable: " + (joinableArray == null ? "null" : Arrays.asList(joinableArray))); getLog().info("---target: " + target.getAbsolutePath()); getLog().info("---prefix: \"" + prefix + "\""); getLog().info("-------------------------------------------------"); // first make a list of all source files List modules = new ArrayList(); modules.add(new Module(mainFile, true)); modules.add(new Module(appFile, false)); for (File module : findModules()) { modules.add(new Module(module, false)); } int count = 0; // process all the files except main - since they update the moduleMap array for (final Module module : modules) { if (!module.isMain() && module.isStale()) { processModule(module, moduleMap); count++; } } // process the main file last for (final Module module : modules) { if (module.isMain() && module.isStale()) { processMain(module); count++; } } // TODO delete files that should no longer be in target? if (count == 0) { getLog().info("Join:: Nothing to do."); return; } } catch (Exception e) { getLog().error(e); throw new MojoExecutionException("Join:: failed.", e); } finally { getLog().info("Join:: took " + (System.currentTimeMillis() - start) + "ms"); } } private String shorten(final File file) { return shorten(file.getAbsolutePath()); } private String shorten(final String absolute) { return absolute.replace(source.getAbsolutePath(), "").replace("\\", "/"); } private void processMain(final Module main) throws Exception { String contents = Files.toString(mainFile, Charsets.UTF_8).trim(); // getLog().info("moduleMap: " + moduleMap); for (Map.Entry entry : moduleMap.entrySet()) { // getLog().info("Replacing: " + entry.getKey() + " with " + entry.getValue()); contents = contents.replace(entry.getKey(), entry.getValue()); } // finally emit the output file emit("main", contents, main.getTarget()); buildContext.refresh(target); } private void processModule(final Module module, final Map moduleMap) throws Exception { // getLog().info("moduleName: " + moduleName); List lines = new ArrayList<>(); lines.add("define([ \"" + Joiner.on("\", \"").join( Iterables.concat(module.getReferences().keySet(), Sets.difference(module.getExternalDeps(), module.getReferences().keySet()))) + "\" ], function(" + Joiner.on(", ").join(module.getReferences().values()) + ") {\n"); for (File dep : module.getInternalDeps()) { lines.add(module.getContents(dep).replaceAll("^\\s*define.*?function\\s*\\([^\\)]*\\)\\s*\\{", "(function() {") .replaceAll("\\s*\\}\\s*\\)\\s*;?\\s*$", "\n})();\n")); } // process the templates if (module.hasTemplates()) { lines.add("angular.module(\"" + module.getName() + "Templates\", []).run([\"$templateCache\", function($templateCache) {"); for (final File file : module.getTemplates()) { String cacheKey = prefix + shorten(file); List fileLines = null; try { fileLines = FileUtils.readLines(file); } catch (IOException ex) { throw new MojoExecutionException("Join:: Unable to read template file: " + file.getAbsolutePath(), ex); } if (fileLines.isEmpty()) { lines.add("\t$templateCache.put(\"" + cacheKey + "\", \"\");"); } else { lines.add("\t$templateCache.put(\"" + cacheKey + "\","); for (String line : fileLines) { lines.add("\t\"" + line.replace("\\", "\\\\").replace("\"", "\\\"") + "\\n\" +"); } lines.set(lines.size() - 1, StringUtils.chomp(lines.get(lines.size() - 1), "\\n\" +") + "\");"); } lines.add(""); } lines.add("}]);\n"); } // now the module - last because of the return String moduleContents = module.getContents().replaceAll("^\\s*define.*?function\\s*\\([^\\)]*\\)\\s*\\{", "return (function() {") .replaceAll("\\s*\\}\\s*\\)\\s*;?\\s*$", "\n})();"); if (module.hasTemplates()) { moduleContents = moduleContents.replaceFirst(".module\\s*\\(([^,]+)\\s*,\\s*\\[\\s*([^\\]]+)\\]", ".module($1, [ \"" + module.getName() + "Templates\", $2]")// .replaceFirst(".module\\s*\\(([^,]+)\\s*,\\s*\\[\\s*\\]", ".module($1, [ \"" + module.getName() + "Templates\" ]"); } lines.add(moduleContents); // the end for the define lines.add("});"); // finally emit the output file emit(module.getName(), Joiner.on("\n").join(lines), module.getTarget()); buildContext.refresh(target); } private void emit(final String moduleName, final String source, final File targetFile) throws MojoExecutionException { try { getLog().info("Join:: Writing output file: " + targetFile.getAbsolutePath()); Files.write(source, targetFile, Charsets.UTF_8); } catch (final IOException ex) { throw new MojoExecutionException("Join:: Unable to write output file: " + targetFile.getAbsolutePath(), ex); } } private List findModules() { final DirectoryScanner scanner = new DirectoryScanner(); scanner.setBasedir(source); return findFiles(scanner); } private List findFiles(final Scanner scanner) { final List results = new ArrayList(); scanner.setIncludes(modulesArray); scanner.scan(); for (final String name : scanner.getIncludedFiles()) { results.add(new File(scanner.getBasedir(), name)); } return results; } private class Module { private final File file; private final File target; private final boolean main; private final String name; private final String contents; private final Map references = new HashMap<>(); private final Set externalDeps = new HashSet<>(); private final Set internalDeps = new HashSet<>(); private final Map internalDepContents = new HashMap<>(); private final List templates; private Module(final File file, final boolean main) throws IOException { this.file = file; this.target = makeTarget(file); this.main = main; name = file.getAbsolutePath().replace(file.getParent() + "/", "").replaceAll("\\.js$", ""); contents = Files.toString(file, Charsets.UTF_8).trim(); templates = findTemplates(file.getParentFile()); if (!main) { findDependencies(file, contents, internalDeps, internalDepContents, externalDeps, references); } } private File getTarget() { if (!target.getParentFile().exists()) { target.getParentFile().mkdirs(); } return target; } private boolean isMain() { return main; } private String getContents() { return contents; } private String getContents(final File dep) { return internalDepContents.get(dep); } private String getName() { return name; } private Map getReferences() { return references; } private Set getExternalDeps() { return externalDeps; } private Set getInternalDeps() { return internalDeps; } private List getTemplates() { return templates; } private boolean hasTemplates() { return !templates.isEmpty(); } private boolean isStale() { if (!target.exists()) { return true; } long result = file.lastModified(); for (File file : internalDeps) { result = Math.max(result, file.lastModified()); } for (File file : templates) { result = Math.max(result, file.lastModified()); } return result > target.lastModified(); } private Set findDependencies(final File startFile, final String contents, final Set internal, final Map contentsMap, final Set external, final Map references) throws IOException { // getLog().info("Checking file: " + startFile.getAbsolutePath()); File startDir = startFile.getParentFile(); GlobMatcher globMatcher = new GlobMatcher(source.getParentFile(), startDir, joinableArray); try { Matcher matcher = Pattern.compile( "^\\s*define\\s*\\(\\s*\\[\\s*([^\\]]+)\\s*\\]\\s*,\\s*function\\s*\\(([^\\)]*)\\)\\s*\\{.*$", Pattern.DOTALL) .matcher(contents); if (matcher.matches()) { String[] deps = matcher.group(1).split("\\s*,\\s*"); String[] refs = matcher.group(2).split("\\s*,\\s*"); // getLog().info("Found deps: " + Arrays.asList(deps) + ", refs: " + Arrays.asList(refs)); int i = 0; for (String dep : deps) { File depFile = globMatcher.makeFile(dequote(dep)); if (globMatcher.matches(depFile)) { if (!internal.contains(depFile)) { String depContents = Files.toString(depFile, Charsets.UTF_8).trim(); internal.add(depFile); contentsMap.put(depFile, depContents); findDependencies(depFile, depContents, internal, contentsMap, external, references); } } else { external.add(dequote(dep)); if (i < refs.length) { references.put(dequote(dep), refs[i]); } // getLog().info( // "Processed external dependency: " + dep + ", external: " + external + ", references: " + references); } i++; } // getLog().info("Found define clause, dependency: " + internal); } else { getLog().warn("Join:: No define found, contents: " + contents); } return internal; } catch (Exception e) { throw new RuntimeException("oops, contents: " + contents, e); } } private String dequote(final String quoted) { String input = StringUtils.trim(quoted); if (input.length() > 1 && input.startsWith("'") && input.endsWith("'")) { return StringUtils.strip(input, "'"); } if (input.length() > 1 && input.startsWith("\"") && input.endsWith("\"")) { return StringUtils.strip(input, "\""); } return input; } private List findTemplates(final File baseDir) { final DirectoryScanner scanner = new DirectoryScanner(); scanner.setBasedir(baseDir); scanner.setIncludes(templatesArray); scanner.addDefaultExcludes(); scanner.scan(); final List results = new ArrayList(); for (final String name : scanner.getIncludedFiles()) { results.add(new File(scanner.getBasedir(), name)); } Collections.sort(results); return results; } } private File makeTarget(final File file) { // getLog().info("makeTarget called for file: " + file); String path = file.getAbsolutePath().replace(source.getAbsolutePath(), target.getAbsolutePath()); List parts = Arrays.asList(path.split("/")); if (parts.size() >= 2) { String moduleFolder = parts.get(parts.size() - 2); if (parts.get(parts.size() - 1).equals(moduleFolder + "Module.js")) { // getLog().info("makeTarget found moduleFolder: " + moduleFolder); String fullPath = path.replace(target.getAbsolutePath(), "").replaceAll("\\.js$", ""); path = path.replace("/" + moduleFolder + "/" + moduleFolder + "Module.js", "/" + moduleFolder + "Module.js"); moduleMap.put(fullPath, path.replace(target.getAbsolutePath(), "").replaceAll("\\.js$", "")); } } // getLog().info("makeTarget returning: " + path); return new File(path); } }