pax_global_header 0000666 0000000 0000000 00000000064 12521312416 0014507 g ustar 00root root 0000000 0000000 52 comment=eb254d106a49bf64ee9c5a8054aefebc6daea0be
angular-maven-plugin-angular-maven-plugin-0.3.4/ 0000775 0000000 0000000 00000000000 12521312416 0021573 5 ustar 00root root 0000000 0000000 angular-maven-plugin-angular-maven-plugin-0.3.4/.gitignore 0000664 0000000 0000000 00000000066 12521312416 0023565 0 ustar 00root root 0000000 0000000 /bin
/target
.project
.classpath
.directory
.settings
angular-maven-plugin-angular-maven-plugin-0.3.4/LICENSE 0000664 0000000 0000000 00000002070 12521312416 0022577 0 ustar 00root root 0000000 0000000 The 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.md 0000664 0000000 0000000 00000001447 12521312416 0023060 0 ustar 00root root 0000000 0000000 angular-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.mojoangular-maven-plugin0.3.4
angular-maven-plugin-angular-maven-plugin-0.3.4/doc/ 0000775 0000000 0000000 00000000000 12521312416 0022340 5 ustar 00root root 0000000 0000000 angular-maven-plugin-angular-maven-plugin-0.3.4/doc/html2js.md 0000664 0000000 0000000 00000007215 12521312416 0024252 0 ustar 00root root 0000000 0000000 html2js
=======
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.mojoangular-maven-plugin0.3.4generate-resourceshtml2js${basedir}/src/main/template/angular**/*.htmlnot-this.html${basedir}/src/main/generated/js/templates.jstrue/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.m2elifecycle-mapping1.0.0com.keithbranton.mojoangular-maven-plugin[0.1-SNAPSHOT,)html2jstrue
...
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.md 0000664 0000000 0000000 00000011701 12521312416 0023621 0 ustar 00root root 0000000 0000000 join
====
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.mojoangular-maven-plugin0.3.4generate-sourcesjoinsrc/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.m2elifecycle-mapping1.0.0com.keithbranton.mojoangular-maven-plugin[0.1-SNAPSHOT,)jointrue
...
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.xml 0000664 0000000 0000000 00000006551 12521312416 0023117 0 ustar 00root root 0000000 0000000 4.0.0Angular Maven Pluginorg.sonatype.ossoss-parent7com.keithbranton.mojoangular-maven-plugin0.3.4maven-pluginhttps://github.com/keithbranton/angular-maven-pluginMIT Licensehttp://www.opensource.org/licenses/mit-license.phpscm:git:git@github.com:keithbranton/angular-maven-plugin.gitscm:git:git@github.com:keithbranton/angular-maven-plugin.gitgit@github.com:keithbranton/angular-maven-plugin.gitUTF-8org.apache.maven.pluginsmaven-compiler-plugin3.01.71.7falsetruefalsejavacorg.codehaus.plexusplexus-compiler-eclipse2.1org.apache.maven.pluginsmaven-plugin-plugin3.3truemojo-descriptordescriptorcommons-langcommons-lang2.6commons-iocommons-io2.4com.google.guavaguava17.0org.apache.mavenmaven-plugin-api3.2.3org.apache.maven.plugin-toolsmaven-plugin-annotations3.3org.apache.mavenmaven-project2.2.1org.sonatype.plexusplexus-build-api0.0.7
angular-maven-plugin-angular-maven-plugin-0.3.4/src/ 0000775 0000000 0000000 00000000000 12521312416 0022362 5 ustar 00root root 0000000 0000000 angular-maven-plugin-angular-maven-plugin-0.3.4/src/main/ 0000775 0000000 0000000 00000000000 12521312416 0023306 5 ustar 00root root 0000000 0000000 angular-maven-plugin-angular-maven-plugin-0.3.4/src/main/java/ 0000775 0000000 0000000 00000000000 12521312416 0024227 5 ustar 00root root 0000000 0000000 angular-maven-plugin-angular-maven-plugin-0.3.4/src/main/java/com/ 0000775 0000000 0000000 00000000000 12521312416 0025005 5 ustar 00root root 0000000 0000000 angular-maven-plugin-angular-maven-plugin-0.3.4/src/main/java/com/keithbranton/ 0000775 0000000 0000000 00000000000 12521312416 0027475 5 ustar 00root root 0000000 0000000 angular-maven-plugin-angular-maven-plugin-0.3.4/src/main/java/com/keithbranton/mojo/ 0000775 0000000 0000000 00000000000 12521312416 0030441 5 ustar 00root root 0000000 0000000 angular-maven-plugin-angular-maven-plugin-0.3.4/src/main/java/com/keithbranton/mojo/GlobMatcher.java0000664 0000000 0000000 00000002726 12521312416 0033502 0 ustar 00root root 0000000 0000000 package 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.java0000664 0000000 0000000 00000022773 12521312416 0033467 0 ustar 00root root 0000000 0000000 package 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.java 0000664 0000000 0000000 00000035107 12521312416 0033036 0 ustar 00root root 0000000 0000000 package 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);
}
}