diff --git a/README.md b/README.md
index b463f88..6341cfd 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,122 @@
-wro4j-runner
-============
+# wro4j-runner
+
Command line runner for wro4j
-Building
-============
+# Building
+```
+ $ git clone git@github.com:wro4j/wro4j-runner.git
+ $ mvn clean install
+```
+
+The compiled file will be saved at:
+ target/wro4j-runner-1.7.6.jar
+
+# How to Use wro4j-runner
+
+To use `wro4j-runner`, you need to provide a configuration file (`wro.xml`), a properties file (`wro.properties`), and optionally a `.jshintrc` file for JavaScript linting.
+
```
-$ git clone git@github.com:wro4j/wro4j-runner.git
-$ mvn clean install
+ =======================================
+ USAGE
+ =======================================
+ --contextFolder PATH : Folder used as a root of the context relative
+ resources. By default this is the user current
+ folder.
+ --destinationFolder PATH : Where to store the processed result. By default
+ uses the folder named [wro].
+ --parallel : Turns on the parallel preProcessing of resources.
+ This value is false by default.
+ --postProcessors POST_PROCESSOR : Comma separated list of post-processors
+ --targetGroups GROUPS : Comma separated value of the group names from
+ wro.xml to process. If none is provided, all
+ groups will be processed.
+ --wroConfigurationFile PATH_TO_WRO_PROPERTIES : The path to the wro.properties file. By default
+ the configuration file is searched inse the user
+ current folder.
+ --wroFile PATH_TO_WRO_XML : The path to the wro model file. By default the
+ model is searched inse the user current folder.
+ -c (--compressor, --preProcessors) COMPRESSOR : Comma separated list of pre-processors
+ -i (--ignoreMissingResources) : Ignores missing resources
+ -m (--minimize) : Turns on the minimization by applying compressor
```
+
+### 1. Create `wro.xml`
+
+This XML file defines your resource groups and the resources (JS/CSS files) to process. Example:
+
+```xml
+
+
+ /js/script1.js
+ /js/script2.js
+ /js/**.js
+ /css/style1.css
+
+
+```
+
+For a complete explanation on how to use wro.xml please visit:
+https://wro4j.readthedocs.io/en/stable/WroFileFormat/
+
+### 2. Create `wro.properties`
+
+This file configures wro4j options, such as pre-processors and output directories. Example:
+
+```properties
+preProcessors=cssUrlRewriting,cssMinJawr,semicolonAppender,jsMin
+targetGroups=all
+destinationFolder=dist
+```
+
+For a complete list of available Configuration options please visit:
+https://wro4j.readthedocs.io/en/stable/ConfigurationOptions/
+
+### 3. Create `.jshintrc` (Optional)
+
+If you want to enable JS linting, add a `.jshintrc` file with your linting rules:
+
+```json
+{
+ "esnext": true,
+ "strict": true,
+ "undef": true,
+ "unused": true,
+ "eqeqeq": true,
+ "curly": true,
+ "browser": true,
+ "node": true,
+ "devel": true,
+ "asi": false,
+ "maxerr": 50,
+ "latedef": true,
+ "noarg": true,
+ "nonew": true,
+ "camelcase": true,
+ "quotmark": true,
+ "trailing": true,
+ "freeze": true,
+ "futurehostile": true,
+ "nocomma": true,
+ "varstmt": true
+}
+```
+
+For a complete list of available options please visit:
+https://jshint.com/docs/options/
+
+### 4. Run wro4j-runner
+
+Run the tool from the command line, specifying the configuration files:
+
+```sh
+java -jar ../wro4j-runner-1.7.6.jar \
+ --wroFile wro.xml \
+ --contextFolder test_code_dir \
+ --postProcessors jsMin \
+ --wroConfigurationFile wro.properties \
+ --destinationFolder test_jshint_reports \
+ --minimize
+```
+
+Adjust the paths as needed for your project structure.
diff --git a/pom.xml b/pom.xml
index 7234f9a..9838dc7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,14 +7,19 @@
1.7.5
wro4j-runner
- 1.7.6
+ 1.7.8
jar
wro4j runner
- 1.7.7
+ 1.8.2
ro.isdc.wro.runner.Wro4jCommandLineRunner
+
+ ${project.groupId}
+ wro4j-core
+ ${wro4j.version}
+
${project.groupId}
wro4j-extensions
@@ -29,7 +34,7 @@
com.google.code.gson
gson
- provided
+ compile
org.jruby
@@ -83,6 +88,12 @@
slf4j-log4j12
compile
+
+ org.codehaus.groovy
+ groovy-all
+ 2.1.6
+ compile
+
diff --git a/src/main/java/ro/isdc/wro/runner/Wro4jCommandLineRunner.java b/src/main/java/ro/isdc/wro/runner/Wro4jCommandLineRunner.java
index 177330c..50c0991 100644
--- a/src/main/java/ro/isdc/wro/runner/Wro4jCommandLineRunner.java
+++ b/src/main/java/ro/isdc/wro/runner/Wro4jCommandLineRunner.java
@@ -58,7 +58,6 @@
import ro.isdc.wro.util.StopWatch;
import ro.isdc.wro.util.io.UnclosableBufferedInputStream;
-
/**
* Default command line runner. Interprets arguments and perform a processing.
*
@@ -66,362 +65,373 @@
* @since 1.3.4
*/
public class Wro4jCommandLineRunner {
- private static final Logger LOG = LoggerFactory.getLogger(Wro4jCommandLineRunner.class);
- private static String userDirectory = System.getProperty("user.dir");
- private final File defaultWroFile = newDefaultWroFile();
-
- @Option(name = "-m", aliases = {
- "--minimize"
- }, usage = "Turns on the minimization by applying compressor")
- private boolean minimize;
- @Option(name = "--parallel", usage = "Turns on the parallel preProcessing of resources. This value is false by default.")
- private boolean parallelPreprocessing;
- @Option(name = "--targetGroups", metaVar = "GROUPS", usage = "Comma separated value of the group names from wro.xml to process. If none is provided, all groups will be processed.")
- private String targetGroups;
- @Option(name = "-i", aliases = {
- "--ignoreMissingResources"
- }, usage = "Ignores missing resources")
- private boolean ignoreMissingResources;
- @Option(name = "--wroFile", metaVar = "PATH_TO_WRO_XML", usage = "The path to the wro model file. By default the model is searched inse the user current folder.")
- private final File wroFile = defaultWroFile;
- @Option(name = "--wroConfigurationFile", metaVar = "PATH_TO_WRO_PROPERTIES", usage = "The path to the wro.properties file. By default the configuration file is searched inse the user current folder.")
- private final File wroConfigurationFile = newWroConfigurationFile();
- @Option(name = "--contextFolder", metaVar = "PATH", usage = "Folder used as a root of the context relative resources. By default this is the user current folder.")
- private final File contextFolder = new File(System.getProperty("user.dir"));
- @Option(name = "--destinationFolder", metaVar = "PATH", usage = "Where to store the processed result. By default uses the folder named [wro].")
- private File destinationFolder = new File(System.getProperty("user.dir"), "wro");
- @Option(name = "-c", aliases = {
- "--compressor", "--preProcessors"
- }, metaVar = "COMPRESSOR", usage = "Comma separated list of pre-processors")
- private String preProcessorsList;
- @Option(name = "--postProcessors", metaVar = "POST_PROCESSOR", usage = "Comma separated list of post-processors")
- private String postProcessorsList;
-
- private Properties wroConfigurationAsProperties;
-
- public static void main(final String[] args)
- throws Exception {
- new Wro4jCommandLineRunner().doMain(args);
- }
-
- /**
- * @return the location where wro file is located by default. Default implementation uses current user directory.
- * @VisibleForTesting
- */
- protected File newDefaultWroFile() {
- return new File(userDirectory, "wro.xml");
- }
-
- /**
- * @return the location where wro configuration file is located by default. Default implementation uses current user
- * directory.
- * @VisibleForTesting
- */
- protected File newWroConfigurationFile() {
- return new File(userDirectory, "wro.properties");
- }
-
- /**
- * @return the context folder used by runner.
- * @VisibleForTesting
- */
- protected File getContextFolder() {
- return contextFolder;
- }
-
- /**
- * @return the destination folder where the result will be written.
- * @VisibleForTesting
- */
- protected File getDestinationFolder() {
- return contextFolder;
- }
-
- /**
- * @param args
- */
- protected void doMain(final String[] args) {
- LOG.debug("arguments: " + Arrays.toString(args));
- final CmdLineParser parser = new CmdLineParser(this);
- parser.setUsageWidth(100);
- final StopWatch watch = new StopWatch();
- watch.start("processing");
- try {
- parser.parseArgument(args);
- LOG.debug("Options: {}", this);
- process();
- } catch (final Exception e) {
- e.printStackTrace();
- System.err.println(e.getMessage() + "\n\n");
- System.err.println("=======================================");
- System.err.println("USAGE");
- System.err.println("=======================================");
- parser.printUsage(System.err);
- onRunnerException(e);
- } finally {
- watch.stop();
- LOG.debug(watch.prettyPrint());
- LOG.info("Processing took: {}ms", watch.getLastTaskTimeMillis());
+ private static final Logger LOG = LoggerFactory.getLogger(Wro4jCommandLineRunner.class);
+ private static String userDirectory = System.getProperty("user.dir");
+ private final File defaultWroFile = newDefaultWroFile();
+
+ @Option(name = "-m", aliases = {
+ "--minimize"
+ }, usage = "Turns on the minimization by applying compressor")
+ private boolean minimize;
+ @Option(name = "--parallel", usage = "Turns on the parallel preProcessing of resources. This value is false by default.")
+ private boolean parallelPreprocessing;
+ @Option(name = "--targetGroups", metaVar = "GROUPS", usage = "Comma separated value of the group names from wro.xml to process. If none is provided, all groups will be processed.")
+ private String targetGroups;
+ @Option(name = "-i", aliases = {
+ "--ignoreMissingResources"
+ }, usage = "Ignores missing resources")
+ private boolean ignoreMissingResources;
+ @Option(name = "--wroFile", metaVar = "PATH_TO_WRO_XML", usage = "The path to the wro model file. By default the model is searched inse the user current folder.")
+ private final File wroFile = defaultWroFile;
+ @Option(name = "--wroConfigurationFile", metaVar = "PATH_TO_WRO_PROPERTIES", usage = "The path to the wro.properties file. By default the configuration file is searched inse the user current folder.")
+ private final File wroConfigurationFile = newWroConfigurationFile();
+ @Option(name = "--contextFolder", metaVar = "PATH", usage = "Folder used as a root of the context relative resources. By default this is the user current folder.")
+ private final File contextFolder = new File(System.getProperty("user.dir"));
+ @Option(name = "--destinationFolder", metaVar = "PATH", usage = "Where to store the processed result. By default uses the folder named [wro].")
+ private File destinationFolder = new File(System.getProperty("user.dir"), "wro");
+ @Option(name = "-c", aliases = {
+ "--compressor", "--preProcessors"
+ }, metaVar = "COMPRESSOR", usage = "Comma separated list of pre-processors")
+ private String preProcessorsList;
+ @Option(name = "--postProcessors", metaVar = "POST_PROCESSOR", usage = "Comma separated list of post-processors")
+ private String postProcessorsList;
+
+ private Properties wroConfigurationAsProperties;
+
+ public static void main(final String[] args)
+ throws Exception {
+ new Wro4jCommandLineRunner().doMain(args);
+ }
+
+ /**
+ * @return the location where wro file is located by default. Default
+ * implementation uses current user directory.
+ * @VisibleForTesting
+ */
+ protected File newDefaultWroFile() {
+ return new File(userDirectory, "wro.xml");
}
- }
-
- /**
- * Exception handler.
- */
- protected void onRunnerException(final Exception e) {
- System.out.println(e.getMessage());
- System.exit(1); // non-zero exit code indicates there was an error
- }
-
- private void process() {
- try {
- Context.set(Context.standaloneContext());
- // create destinationFolder if needed
- if (!destinationFolder.exists()) {
- destinationFolder.mkdirs();
- }
- final Collection groupsAsList = getTargetGroupsAsList();
- for (final String group : groupsAsList) {
- for (final ResourceType resourceType : ResourceType.values()) {
- final String groupWithExtension = group + "." + resourceType.name().toLowerCase();
- processGroup(groupWithExtension, destinationFolder);
+
+ /**
+ * @return the location where wro configuration file is located by default.
+ * Default implementation uses current user
+ * directory.
+ * @VisibleForTesting
+ */
+ protected File newWroConfigurationFile() {
+ return new File(userDirectory, "wro.properties");
+ }
+
+ /**
+ * @return the context folder used by runner.
+ * @VisibleForTesting
+ */
+ protected File getContextFolder() {
+ return contextFolder;
+ }
+
+ /**
+ * @return the destination folder where the result will be written.
+ * @VisibleForTesting
+ */
+ protected File getDestinationFolder() {
+ return contextFolder;
+ }
+
+ /**
+ * @param args
+ */
+ protected void doMain(final String[] args) {
+ LOG.debug("arguments: " + Arrays.toString(args));
+ final CmdLineParser parser = new CmdLineParser(this);
+ parser.setUsageWidth(100);
+ final StopWatch watch = new StopWatch();
+ watch.start("processing");
+ try {
+ parser.parseArgument(args);
+ LOG.debug("Options: {}", this);
+ process();
+ } catch (final Exception e) {
+ e.printStackTrace();
+ System.err.println(e.getMessage() + "\n\n");
+ System.err.println("=======================================");
+ System.err.println("USAGE");
+ System.err.println("=======================================");
+ parser.printUsage(System.err);
+ onRunnerException(e);
+ } finally {
+ watch.stop();
+ LOG.debug(watch.prettyPrint());
+ LOG.info("Processing took: {}ms", watch.getLastTaskTimeMillis());
+ }
+ }
+
+ /**
+ * Exception handler.
+ */
+ protected void onRunnerException(final Exception e) {
+ System.out.println(e.getMessage());
+ System.exit(1); // non-zero exit code indicates there was an error
+ }
+
+ private void process() {
+ try {
+ Context.set(Context.standaloneContext());
+ // create destinationFolder if needed
+ if (!destinationFolder.exists()) {
+ destinationFolder.mkdirs();
+ }
+ final Collection groupsAsList = getTargetGroupsAsList();
+ for (final String group : groupsAsList) {
+ for (final ResourceType resourceType : ResourceType.values()) {
+ final String groupWithExtension = group + "." + resourceType.name().toLowerCase();
+ processGroup(groupWithExtension, destinationFolder);
+ }
+ }
+ } catch (final IOException e) {
+ System.err.println(e.getMessage());
+ }
+ }
+
+ /**
+ * @return a list containing all groups needs to be processed.
+ */
+ private List getTargetGroupsAsList()
+ throws IOException {
+ if (targetGroups == null) {
+ final WroModel model = getManagerFactory().create().getModelFactory().create();
+ return new WroModelInspector(model).getGroupNames();
+ }
+ return Arrays.asList(targetGroups.split(","));
+ }
+
+ /**
+ * Process a single group.
+ *
+ * @throws IOException
+ * if any IO related exception occurs.
+ */
+ private void processGroup(final String group, final File parentFoder)
+ throws IOException {
+ final ByteArrayOutputStream resultOutputStream = new ByteArrayOutputStream();
+ InputStream resultInputStream = null;
+ try {
+ LOG.info("processing group: " + group);
+ initContext(group, resultOutputStream);
+ doProcess();
+
+ // encode version & write result to file
+ resultInputStream = new UnclosableBufferedInputStream(resultOutputStream.toByteArray());
+ final File destinationFile = new File(parentFoder, rename(group, resultInputStream));
+ destinationFile.createNewFile();
+ // allow the same stream to be read again
+ resultInputStream.reset();
+ LOG.debug("Created file: {}", destinationFile.getName());
+
+ final OutputStream fos = new FileOutputStream(destinationFile);
+ // use reader to detect encoding
+ IOUtils.copy(resultInputStream, fos);
+ fos.close();
+ // delete empty files
+ if (destinationFile.length() == 0) {
+ LOG.debug("No content found for group: {}", group);
+ destinationFile.delete();
+ } else {
+ LOG.info("file size: {} -> {}bytes", destinationFile.getName(), destinationFile.length());
+ LOG.info("{} ({}bytes) has been created!", destinationFile.getAbsolutePath(), destinationFile.length());
+ }
+ } finally {
+ if (resultOutputStream != null) {
+ resultOutputStream.close();
+ }
+ if (resultInputStream != null) {
+ resultInputStream.close();
+ }
}
- }
- } catch (final IOException e) {
- System.err.println(e.getMessage());
}
- }
-
- /**
- * @return a list containing all groups needs to be processed.
- */
- private List getTargetGroupsAsList()
- throws IOException {
- if (targetGroups == null) {
- final WroModel model = getManagerFactory().create().getModelFactory().create();
- return new WroModelInspector(model).getGroupNames();
+
+ /**
+ * Initialize the context for standalone execution.
+ */
+ private void initContext(final String group, final ByteArrayOutputStream resultOutputStream)
+ throws IOException {
+ // mock request
+ final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ Mockito.when(request.getRequestURI()).thenReturn(group);
+ // mock response
+ final HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+ Mockito.when(response.getOutputStream()).thenReturn(new DelegatingServletOutputStream(resultOutputStream));
+
+ // init context
+ Context.set(Context.webContext(request, response, Mockito.mock(FilterConfig.class)), initWroConfiguration());
+ Context.get().setAggregatedFolderPath(computeAggregatedFolderPath());
+ }
+
+ /**
+ * Perform actual processing by delegating the process call to
+ * {@link WroManager}.
+ *
+ * @throws IOException
+ * @VisibleForTesting
+ */
+ void doProcess()
+ throws IOException {
+ // perform processing
+ getManagerFactory().create().process();
}
- return Arrays.asList(targetGroups.split(","));
- }
-
- /**
- * Process a single group.
- *
- * @throws IOException
- * if any IO related exception occurs.
- */
- private void processGroup(final String group, final File parentFoder)
- throws IOException {
- final ByteArrayOutputStream resultOutputStream = new ByteArrayOutputStream();
- InputStream resultInputStream = null;
- try {
- LOG.info("processing group: " + group);
- initContext(group, resultOutputStream);
- doProcess();
-
- // encode version & write result to file
- resultInputStream = new UnclosableBufferedInputStream(resultOutputStream.toByteArray());
- final File destinationFile = new File(parentFoder, rename(group, resultInputStream));
- destinationFile.createNewFile();
- // allow the same stream to be read again
- resultInputStream.reset();
- LOG.debug("Created file: {}", destinationFile.getName());
-
- final OutputStream fos = new FileOutputStream(destinationFile);
- // use reader to detect encoding
- IOUtils.copy(resultInputStream, fos);
- fos.close();
- // delete empty files
- if (destinationFile.length() == 0) {
- LOG.debug("No content found for group: {}", group);
- destinationFile.delete();
- } else {
- LOG.info("file size: {} -> {}bytes", destinationFile.getName(), destinationFile.length());
- LOG.info("{} ({}bytes) has been created!", destinationFile.getAbsolutePath(), destinationFile.length());
- }
- } finally {
- if (resultOutputStream != null) {
- resultOutputStream.close();
- }
- if (resultInputStream != null) {
- resultInputStream.close();
- }
+
+ private WroConfiguration initWroConfiguration()
+ throws IOException {
+ final PropertyWroConfigurationFactory factory = new PropertyWroConfigurationFactory(
+ getWroConfigurationProperties());
+ final WroConfiguration config = factory.create();
+ // keep backward compatibility configuration of some config properties
+ config.setParallelPreprocessing(parallelPreprocessing);
+ return config;
}
- }
-
- /**
- * Initialize the context for standalone execution.
- */
- private void initContext(final String group, final ByteArrayOutputStream resultOutputStream)
- throws IOException {
- // mock request
- final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
- Mockito.when(request.getRequestURI()).thenReturn(group);
- // mock response
- final HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
- Mockito.when(response.getOutputStream()).thenReturn(new DelegatingServletOutputStream(resultOutputStream));
-
- // init context
- Context.set(Context.webContext(request, response, Mockito.mock(FilterConfig.class)), initWroConfiguration());
- Context.get().setAggregatedFolderPath(computeAggregatedFolderPath());
- }
-
- /**
- * Perform actual processing by delegating the process call to {@link WroManager}.
- *
- * @throws IOException
- * @VisibleForTesting
- */
- void doProcess()
- throws IOException {
- // perform processing
- getManagerFactory().create().process();
- }
-
- private WroConfiguration initWroConfiguration()
- throws IOException {
- final PropertyWroConfigurationFactory factory = new PropertyWroConfigurationFactory(getWroConfigurationProperties());
- final WroConfiguration config = factory.create();
- // keep backward compatibility configuration of some config properties
- config.setParallelPreprocessing(parallelPreprocessing);
- return config;
- }
-
- /**
- * @return {@link Properties} object loaded from wro.properties file located in current user directory. This location
- * is configurable. If the wro.properties file does not exist, the returned value will be an empty
- * {@link Properties} object, equivalent to no config properties.
- * @throws IOException
- * if loaded file is corrupt.
- */
- private Properties getWroConfigurationProperties()
- throws IOException {
- if (wroConfigurationAsProperties == null) {
- try {
- wroConfigurationAsProperties = new Properties();
- if (wroConfigurationFile != null && wroConfigurationFile.exists()) {
- LOG.debug("Using {} to load WroConfiguration.", wroConfigurationFile.getPath());
- wroConfigurationAsProperties.load(new FileInputStream(wroConfigurationFile));
- } else {
- LOG.warn("Configuration file: '{}' does not exist. Using default configuration.", wroConfigurationFile);
+
+ /**
+ * @return {@link Properties} object loaded from wro.properties file located in
+ * current user directory. This location
+ * is configurable. If the wro.properties file does not exist, the
+ * returned value will be an empty
+ * {@link Properties} object, equivalent to no config properties.
+ * @throws IOException
+ * if loaded file is corrupt.
+ */
+ private Properties getWroConfigurationProperties()
+ throws IOException {
+ if (wroConfigurationAsProperties == null) {
+ try {
+ wroConfigurationAsProperties = new Properties();
+ if (wroConfigurationFile != null && wroConfigurationFile.exists()) {
+ LOG.debug("Using {} to load WroConfiguration.", wroConfigurationFile.getPath());
+ wroConfigurationAsProperties.load(new FileInputStream(wroConfigurationFile));
+ } else {
+ LOG.warn("Configuration file: '{}' does not exist. Using default configuration.",
+ wroConfigurationFile);
+ }
+ } catch (final IOException e) {
+ LOG.error("Problem while loading WroConfiguration", e);
+ throw e;
+ }
}
- } catch (final IOException e) {
- LOG.error("Problem while loading WroConfiguration", e);
- throw e;
- }
+ return wroConfigurationAsProperties;
}
- return wroConfigurationAsProperties;
- }
-
- /**
- * This implementation is similar to the one from Wro4jMojo. TODO: reuse if possible.
- */
- private String computeAggregatedFolderPath() {
- Validate.notNull(destinationFolder, "DestinationFolder cannot be null!");
- Validate.notNull(getContextFolder(), "ContextFolder cannot be null!");
- final File cssTargetFolder = destinationFolder;
- File rootFolder = null;
- if (cssTargetFolder.getPath().startsWith(getContextFolder().getPath())) {
- rootFolder = getContextFolder();
+
+ /**
+ * This implementation is similar to the one from Wro4jMojo. TODO: reuse if
+ * possible.
+ */
+ private String computeAggregatedFolderPath() {
+ Validate.notNull(destinationFolder, "DestinationFolder cannot be null!");
+ Validate.notNull(getContextFolder(), "ContextFolder cannot be null!");
+ final File cssTargetFolder = destinationFolder;
+ File rootFolder = null;
+ if (cssTargetFolder.getPath().startsWith(getContextFolder().getPath())) {
+ rootFolder = getContextFolder();
+ }
+ // compute aggregatedFolderPath
+ String aggregatedFolderPath = null;
+ if (rootFolder != null) {
+ aggregatedFolderPath = StringUtils.removeStart(cssTargetFolder.getPath(), rootFolder.getPath());
+ }
+ LOG.debug("aggregatedFolderPath: {}", aggregatedFolderPath);
+ return aggregatedFolderPath;
}
- // compute aggregatedFolderPath
- String aggregatedFolderPath = null;
- if (rootFolder != null) {
- aggregatedFolderPath = StringUtils.removeStart(cssTargetFolder.getPath(), rootFolder.getPath());
+
+ /**
+ * Encodes a version using some logic.
+ *
+ * @param group
+ * the name of the resource to encode.
+ * @param input
+ * the stream of the result content.
+ * @return the name of the resource with the version encoded.
+ */
+ private String rename(final String group, final InputStream input)
+ throws IOException {
+ return getManagerFactory().create().getNamingStrategy().rename(group, input);
}
- LOG.debug("aggregatedFolderPath: {}", aggregatedFolderPath);
- return aggregatedFolderPath;
- }
-
- /**
- * Encodes a version using some logic.
- *
- * @param group
- * the name of the resource to encode.
- * @param input
- * the stream of the result content.
- * @return the name of the resource with the version encoded.
- */
- private String rename(final String group, final InputStream input)
- throws IOException {
- return getManagerFactory().create().getNamingStrategy().rename(group, input);
- }
-
- /**
- * This method will ensure that you have a right and initialized instance of {@link StandaloneContextAware}.
- */
- private WroManagerFactory getManagerFactory()
- throws IOException {
- final DefaultStandaloneContextAwareManagerFactory managerFactory = new DefaultStandaloneContextAwareManagerFactory();
- managerFactory.setProcessorsFactory(createProcessorsFactory());
- managerFactory.setNamingStrategy(createNamingStrategy());
- managerFactory.setModelFactory(createWroModelFactory());
- managerFactory.initialize(createStandaloneContext());
- // allow created manager to get injected immediately after creation
- return managerFactory;
- }
-
- private NamingStrategy createNamingStrategy() throws IOException {
- final ConfigurableNamingStrategy namingStrategy = new ConfigurableNamingStrategy();
- namingStrategy.setProperties(getWroConfigurationProperties());
- return namingStrategy;
- }
-
- private WroModelFactory createWroModelFactory() {
- // autodetect if user didn't specify explicitly the wro file path (aka default is used).
- notNull(defaultWroFile, "default wroFile cannot be null!");
- final boolean autoDetectWroFile = defaultWroFile.getPath().equals(wroFile.getPath());
- return new SmartWroModelFactory().setWroFile(wroFile).setAutoDetectWroFile(autoDetectWroFile);
- }
-
- private ProcessorsFactory createProcessorsFactory()
- throws IOException {
- final Properties props = getWroConfigurationProperties();
- if (preProcessorsList != null) {
- props.setProperty(ConfigurableProcessorsFactory.PARAM_PRE_PROCESSORS, preProcessorsList);
+
+ /**
+ * This method will ensure that you have a right and initialized instance of
+ * {@link StandaloneContextAware}.
+ */
+ private WroManagerFactory getManagerFactory()
+ throws IOException {
+ final DefaultStandaloneContextAwareManagerFactory managerFactory = new DefaultStandaloneContextAwareManagerFactory();
+ managerFactory.setProcessorsFactory(createProcessorsFactory());
+ managerFactory.setNamingStrategy(createNamingStrategy());
+ managerFactory.setModelFactory(createWroModelFactory());
+ managerFactory.initialize(createStandaloneContext());
+ // allow created manager to get injected immediately after creation
+ return managerFactory;
}
- if (postProcessorsList != null) {
- props.setProperty(ConfigurableProcessorsFactory.PARAM_POST_PROCESSORS, postProcessorsList);
+
+ private NamingStrategy createNamingStrategy() throws IOException {
+ final ConfigurableNamingStrategy namingStrategy = new ConfigurableNamingStrategy();
+ namingStrategy.setProperties(getWroConfigurationProperties());
+ return namingStrategy;
+ }
+
+ private WroModelFactory createWroModelFactory() {
+ // autodetect if user didn't specify explicitly the wro file path (aka default
+ // is used).
+ notNull(defaultWroFile, "default wroFile cannot be null!");
+ final boolean autoDetectWroFile = defaultWroFile.getPath().equals(wroFile.getPath());
+ return new SmartWroModelFactory().setWroFile(wroFile).setAutoDetectWroFile(autoDetectWroFile);
+ }
+
+ private ProcessorsFactory createProcessorsFactory()
+ throws IOException {
+ final Properties props = getWroConfigurationProperties();
+ if (preProcessorsList != null) {
+ props.setProperty(ConfigurableProcessorsFactory.PARAM_PRE_PROCESSORS, preProcessorsList);
+ }
+ if (postProcessorsList != null) {
+ props.setProperty(ConfigurableProcessorsFactory.PARAM_POST_PROCESSORS, postProcessorsList);
+ }
+ return new ConfigurableProcessorsFactory() {
+ @Override
+ protected Map newPreProcessorsMap() {
+ final Map map = super.newPreProcessorsMap();
+ // override csslint & jsHint aliases
+ map.put(CssLintProcessor.ALIAS, new RunnerCssLintProcessor());
+ map.put(JsHintProcessor.ALIAS, new RunnerJsHintProcessor(contextFolder));
+ return map;
+ }
+
+ @Override
+ protected Map newPostProcessorsMap() {
+ final Map map = super.newPostProcessorsMap();
+ // override csslint & jsHint aliases
+ map.put(CssLintProcessor.ALIAS, new RunnerCssLintProcessor());
+ map.put(JsHintProcessor.ALIAS, new RunnerJsHintProcessor(contextFolder));
+ return map;
+ }
+ }.setProperties(props);
+ }
+
+ /**
+ * Creates a {@link StandaloneContext} by setting properties passed after mojo
+ * is initialized.
+ */
+ private StandaloneContext createStandaloneContext() {
+ final StandaloneContext runContext = new StandaloneContext();
+ runContext.setContextFoldersAsCSV(getContextFolder().getPath());
+ runContext.setMinimize(minimize);
+ runContext.setWroFile(wroFile);
+ runContext.setIgnoreMissingResourcesAsString(Boolean.toString(ignoreMissingResources));
+ return runContext;
+ }
+
+ /**
+ * @param destinationFolder
+ * the destinationFolder to set
+ * @VisibleForTestOnly
+ */
+ void setDestinationFolder(final File destinationFolder) {
+ this.destinationFolder = destinationFolder;
}
- return new ConfigurableProcessorsFactory() {
- @Override
- protected Map newPreProcessorsMap() {
- final Map map = super.newPreProcessorsMap();
- // override csslint & jsHint aliases
- map.put(CssLintProcessor.ALIAS, new RunnerCssLintProcessor());
- map.put(JsHintProcessor.ALIAS, new RunnerJsHintProcessor());
- return map;
- }
-
- @Override
- protected Map newPostProcessorsMap() {
- final Map map = super.newPostProcessorsMap();
- // override csslint & jsHint aliases
- map.put(CssLintProcessor.ALIAS, new RunnerCssLintProcessor());
- map.put(JsHintProcessor.ALIAS, new RunnerJsHintProcessor());
- return map;
- }
- }.setProperties(props);
- }
-
- /**
- * Creates a {@link StandaloneContext} by setting properties passed after mojo is initialized.
- */
- private StandaloneContext createStandaloneContext() {
- final StandaloneContext runContext = new StandaloneContext();
- runContext.setContextFoldersAsCSV(getContextFolder().getPath());
- runContext.setMinimize(minimize);
- runContext.setWroFile(wroFile);
- runContext.setIgnoreMissingResourcesAsString(Boolean.toString(ignoreMissingResources));
- return runContext;
- }
-
- /**
- * @param destinationFolder
- * the destinationFolder to set
- * @VisibleForTestOnly
- */
- void setDestinationFolder(final File destinationFolder) {
- this.destinationFolder = destinationFolder;
- }
}
diff --git a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java
index 567c3f1..d0c4257 100644
--- a/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java
+++ b/src/main/java/ro/isdc/wro/runner/processor/RunnerJsHintProcessor.java
@@ -1,27 +1,285 @@
package ro.isdc.wro.runner.processor;
import ro.isdc.wro.extensions.processor.js.JsHintProcessor;
+import ro.isdc.wro.extensions.processor.support.linter.AbstractLinter;
import ro.isdc.wro.extensions.processor.support.linter.LinterException;
import ro.isdc.wro.model.resource.Resource;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Map;
+import java.util.regex.Pattern;
-/**
- * Custom extension of {@link JsHintProcessor} created for wro4j-runner.
- *
- * @author Alex Objelean
- * @since 1.7.2
- */
-public class RunnerJsHintProcessor
- extends JsHintProcessor {
- /**
- * Override the alias of original jsHint processor implementation.
- */
- public static String ALIAS = JsHintProcessor.ALIAS;
- @Override
- protected void onLinterException(final LinterException e, final Resource resource) {
- super.onLinterException(e, resource);
- System.err.println("The following resource: " + resource + " has " + e.getErrors().size() + " errors.");
- System.err.println(e.getErrors());
- throw e;
- }
-}
+import org.omg.CORBA.SystemException;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
+import ro.isdc.wro.extensions.processor.support.linter.LinterError;
+
+public class RunnerJsHintProcessor extends JsHintProcessor {
+ public static String ALIAS = JsHintProcessor.ALIAS;
+ private final File contextFolder; // <-- AGREGA ESTA LÍNEA
+
+ public RunnerJsHintProcessor(File contextFolder) {
+ super();
+ this.contextFolder = contextFolder;
+ }
+
+ public RunnerJsHintProcessor() {
+ this(new File(System.getProperty("user.dir")));
+ }
+
+ @Override
+ protected void onLinterException(final LinterException e, final Resource resource) {
+ // super.onLinterException(e, resource);
+ System.out.println("The following resource: " + (resource != null ? resource.getUri() : "null") + " has "
+ + e.getErrors().size() + " errors.");
+ System.out.println("ERRORS:");
+ for (Object err : e.getErrors()) {
+ String errStr = err.toString();
+ // if (errStr.contains("reason")) {
+ // // Extrae los campos manualmente del string
+ // String line = extractField(errStr, "line");
+ // String character = extractField(errStr, "character");
+ // String reason = extractField(errStr, "reason");
+ // String evidence = extractField(errStr, "evidence");
+ // System.out.println(
+ // " Line: " + line + "\n Char: " + character + "\n Reason: " + reason + "\n
+ // Code: "
+ // + evidence);
+ if (err instanceof LinterError) {
+ LinterError linterError = (LinterError) err;
+ System.out.println(
+ "[\n Line: " + linterError.getLine() +
+ "\n Char: " + linterError.getCharacter() +
+ " \n Reason: " + linterError.getReason() +
+ (linterError.getEvidence() != null && !linterError.getEvidence().isEmpty()
+ ? "\n Code: " + linterError.getEvidence()
+ : ""));
+ System.out.println("],");
+ } else {
+ System.err.println(err);
+ }
+ }
+
+ // Detecta si estamos en un entorno de test (JUnit/Surefire)
+ boolean inTest = false;
+ for (StackTraceElement ste : Thread.currentThread().getStackTrace()) {
+ String cls = ste.getClassName();
+ if (cls.startsWith("org.junit.") || cls.startsWith("org.apache.maven.surefire.")) {
+ inTest = true;
+ break;
+ }
+ }
+ if (inTest) {
+ throw e;
+ }
+ }
+
+ private File getResourceFile(Resource resource) {
+ if (resource == null || resource.getUri() == null) {
+ return null;
+ }
+ File file = new File(resource.getUri());
+ if (file.exists()) {
+ return file;
+ }
+ if (this.contextFolder != null) {
+ String relativePath = resource.getUri().replaceFirst("^/", "");
+ file = new File(this.contextFolder, relativePath);
+ if (file.exists()) {
+ return file;
+ }
+ } else {
+ System.out.println("[JSHint] contextFolder not defined in System properties");
+ }
+ System.out.println("[JSHint] getResourceFile: No file found for resource.getUri(): " + resource.getUri());
+ return null;
+ }
+
+ /**
+ * Busca .jshintrc desde el directorio inicial hacia arriba hasta contextFolder
+ * o la raíz.
+ */
+ private Map findAndLoadJshintrc(File startDir) {
+ File dir = startDir.getAbsoluteFile(); // <-- Asegura que sea absoluto
+ while (dir != null) {
+ File jshintrc = new File(dir, ".jshintrc");
+ if (jshintrc.exists()) {
+ FileReader reader = null;
+ try {
+ reader = new FileReader(jshintrc);
+ Gson gson = new Gson();
+ Type type = new TypeToken