<?xml version="1.0"?>

<project name="jala-utils" default="usage">

   <property environment="env"/>
	<property name="work" value="${env.BUILD_HOME}/work" />
   
   <!-- load javascript libraries -->
   <loadfile property="file_jsmin" srcFile="${env.BUILD_HOME}/scripts/jsmin.js" />
   <loadfile property="file_jslint" srcFile="${env.BUILD_HOME}/scripts/jslint.js" />
   <loadfile property="file_jsant" srcFile="${env.BUILD_HOME}/scripts/jsant.js" />

   <!-- =================================================================== -->
   <!-- Help on usage                                                       -->
   <!-- =================================================================== -->
   <target name="help" depends="usage" />
   <target name="usage">
      <echo message="" />
      <echo message=" Available targets are:" />
      <echo message=" jslint   -> Run jslint on the current directory (validation)" />
      <echo message=" docs     -> Run jsdoc on a helma application" />
      <echo message=" pot      -> Parse i18n methods out of function files and skins and" />
      <echo message="             create a gettext template file." />
      <echo message=" messages -> Compile Gnu PO-files into JavaScript message files" />
      <echo message="" />
      <echo message=" Macro definitions that can be used in other build files:" />
      <echo message=" - jslint" />
      <echo message=" - jsdoc" />
      <echo message=" - jsmin" />
      <echo message="" />
      <echo message=" See comments inside this file (lib.xml) for more details." />
      <echo message="----------------------------------------------------------" />
      <echo message="" />
      <echo message="" />
   </target>


   <!-- =================================================================== -->
   <!-- Validates a version                                                 -->
   <!-- =================================================================== -->
   <target name="jslint">
      <echo message="Validating source files in ${user.dir}" />
      <jslint printWarnings="true" failOnError="false">
         <fileset dir="${user.dir}" includes="**/*.js" />
      </jslint>
   </target>


   <!-- =================================================================== -->
   <!-- Create documentation for a Helma application with jsdoc.pl          -->
   <!-- =================================================================== -->
   <target name="hopdoc">
      <input message="Directory containing the application code (./code):"
               addproperty="docs.source" defaultvalue="./code" />
      <input message="Destination directory for the documentation (./docs):"
               addproperty="docs.destination" defaultvalue="./docs" />
      <input message="Name of the project:"
               addproperty="docs.projectName" defaultvalue="" />
   	<!-- convert inputs to absolute paths -->
   	<property name="srcdir" location="${docs.source}" />
   	<property name="destdir" location="${docs.destination}" />
   	<mkdir dir="${work}" />
      <helmadoc srcdir="${srcdir}"
             	 destdir="${destdir}"
             	 projectName="${docs.projectName}" />
      <delete dir="${work}" />
   </target>


   <!-- =================================================================== -->
   <!-- Create documentation with jsdoc.pl                                  -->
   <!-- =================================================================== -->
   <target name="docs">
      <input message="Directory containing the code (./code):"
               addproperty="docs.source" defaultvalue="./code" />
      <input message="Destination directory for the documentation (./docs):"
               addproperty="docs.destination" defaultvalue="./docs" />
      <input message="Name of the project:"
               addproperty="docs.projectName" defaultvalue="" />
   	<!-- convert inputs to absolute paths -->
   	<property name="srcdir" location="${docs.source}" />
   	<property name="destdir" location="${docs.destination}" />
   	<mkdir dir="${work}" />
      <jsdoc srcdir="${srcdir}"
             destdir="${destdir}"
             projectName="${docs.projectName}" />
      <delete dir="${work}" />
   </target>


   <!-- =================================================================== -->
   <!-- Parse i18n methods and macros in function files and skins           -->
   <!-- and create a GNU gettext compatible template file                   -->
   <!-- =================================================================== -->
   <target name="pot">
      <input message="Name of the template file to generate:"
             addproperty="i18n.template" defaultvalue="messages.pot" />
      <echo message="Using '${i18n.template}' as output" />
      <input message="Directories containing code/skins separated by spaces:"
             addproperty="i18n.scan" />
      <input message="Encoding of code/skin files (default is UTF-8):"
             addproperty="i18n.fileEncoding" />
   	<!-- convert inputs to absolute paths -->
   	<property name="dest" location="${i18n.template}" />
     	<property name="src" location="${i18n.scan}" />
      <java classname="org.mozilla.javascript.tools.shell.Main">
         <arg value="${env.BUILD_HOME}/scripts/MessageParser.js" />
         <arg value="-o" />
         <arg value="${dest}" />
         <arg value="-e" />
         <arg value="${i18n.fileEncoding}" />
         <arg line="${src}" />
      </java>
   </target>

   <!-- =================================================================== -->
   <!-- Generates JavaScript message files out of the .po files in a given  -->
   <!-- directory                                                           -->
   <!-- =================================================================== -->
   <target name="messages">
      <input message="Directory containing PO-files:"
             addproperty="i18n.poDirectory" />
      <input message="Destination directory for generated message files:"
             addproperty="i18n.destination" />
      <input message="Namespace to use for generated messages (optional):"
             addproperty="i18n.namespace" />
   	<!-- convert inputs to absolute paths -->
     	<property name="src" location="${i18n.poDirectory}" />
   	<property name="dest" location="${i18n.destination}" />
      <echo message="Generating message files from PO-files in '${src}'..." />
      <echo message="Using '${dest}' as output directory" />
      <java classname="org.mozilla.javascript.tools.shell.Main">
         <arg value="${env.BUILD_HOME}/scripts/PoParser.js" />
         <arg value="${src}" />
         <arg value="${dest}" />
         <arg line="${i18n.namespace}" />
      </java>
   </target>

   <!-- =================================================================== -->
   <!-- Create documentation with jsdoc.pl                                  -->
   <!-- =================================================================== -->
   <macrodef name="jsdoc">
   	<attribute name="srcdir" />
      <attribute name="destdir" />
      <attribute name="projectName" />
      <sequential>
         <echo message="Generating JSDoc API documentation for @{srcdir}" />
         <property name="workJSdoc" value="${work}/jsdoc-worker" />
         <mkdir dir="${workJSdoc}" />
         <copy todir="${workJSdoc}">
            <fileset dir="@{srcdir}">
               <exclude name=".svn/**" />
               <include name="**/*.js" />
            </fileset>
         </copy>
         <mkdir dir="@{destdir}" />
         <exec executable="perl">
            <arg value="${env.BUILD_HOME}/JSDoc/jsdoc.pl" />
            <arg value="--template-dir=${env.BUILD_HOME}/JSDoc/templates" />
            <arg value="-d=@{destdir}" />
            <arg value="--extensions=js" />
            <arg value="--no-sources" />
            <arg value="--recursive" />
            <arg value="--quiet" />
            <arg value="--package-naming" />
            <arg value="--project-name=@{projectName}" />
            <arg value="--project-summary=@{srcdir}/.jsdoc/summary.html" />
            <arg value="--globals-name=Global" />
         	<arg value="${workJSdoc}" />
         </exec>
      
         <delete dir="${workJSdoc}" />
      </sequential>
   </macrodef>



   <!-- =================================================================== -->
   <!-- Create documentation for a helma appplication with jsdoc.pl         -->
   <!-- (copies the application to a temp directory, works on the js        -->
   <!-- source code to make it work with jsdoc and runs the doc task)       -->
   <!-- =================================================================== -->
   <macrodef name="helmadoc">
      <attribute name="srcdir" />
      <attribute name="destdir" />
   	<attribute name="projectName" />
      <sequential>
         <echo message="Generating API specification for Helma application in @{srcdir}" />
         <property name="workHelmadoc" value="${work}/helmadocs-worker" />
         <mkdir dir="${workHelmadoc}" />
         <copy todir="${workHelmadoc}">
            <fileset dir="@{srcdir}">
               <exclude name=".svn/**" />
               <include name="**/*.js" />
               <include name="**/*.hac" />
               <include name="**/*.properties" />
            </fileset>
         </copy>
         <echo message="Preparing files for JSDoc ..." />
         <script language="javascript"> <![CDATA[
      
            // load libraries:
            eval(file_jsant);

            var fs = project.createDataType("fileset");
            fs.setDir(new java.io.File(workHelmadoc));
            fs.setIncludes("**/*.js,**/*.hac,**/*.properties");
            fs.setExcludes("app.properties,db.properties,Global/**");
            var ds = fs.getDirectoryScanner(project);
            var srcFiles = ds.getIncludedFiles();

            var files = [];
            for (var i=0; i<srcFiles.length; i++) {
               var f = new java.io.File(workHelmadoc + "/" + srcFiles[i]);
               files[files.length] = f.getCanonicalPath();
            }
      
            // slice filename into dir (= name of prototype), filename, file-extension
            var fileNameReg = /([^\\\/]+)[\\\/]([^\\\/]+)\.([^\.]*)$/i;
      
            // find plain function definitions
            var functionReg = /(^|[^=]\s+)function\s+(\w+)\s*\(/gi;
      
            // find constructor functions (after replacing plain function definitions
            var ctorReg = /.prototype.constructor/gi;
      
            // find object/collection/mountpoint mappings in type.properties
            var mappingReg  = /(.*)\s*\(\s*(.*)\s*\)/;
      
            // store helper javascripts
            var propsDef = "";
      
            // keep track of constructor functions
            var ctorTracker = {};
            
      
            for (var i=0; i<files.length; i++) {
               var fileNameResult = files[i].match(fileNameReg);
               if (ctorTracker[fileNameResult[1]] === null) {
                  ctorTracker[fileNameResult[1]] = false;
               }
               var content = Util.readFile(files[i]);
               if (fileNameResult[3].toLowerCase() == "properties") {
      
                  // add mappings of type.properties to the field summary
                  var props = Util.loadProperties(files[i]);
                  for (var e=props.keys(); e.hasMoreElements();) {
                     var key = e.nextElement();
                     var value = props.getProperty(key);
                     if (key.indexOf(".") > -1 || (key.indexOf("_") == 0 && key != "_id" && key != "_children")) {
                        continue;
                     }
                     var re = value.match(mappingReg);
                     if (re) {
                        propsDef += "/**\n  * mapped to " + re[1] + "\n  * {@link " + re[2].capitalize() + "}\n  */\n";
                     } else {
                        propsDef += "/** mapped to database column <b>" + value + "</b>  */\n";
                     }
                     propsDef += fileNameResult[1] + ".prototype." + key + " = null;\n\n";
                  }
      
               } else if (fileNameResult[3].toLowerCase() == "hac") {
      
                  // wrap helma action files in a block: function XXX_action  {  }
                  var str = fileNameResult[1] + ".prototype." + fileNameResult[2] + "_action = function()  {\n" + content + "\n}\n";
                  Util.writeToFile(files[i], str);
      
               } else {
      
                  // in common javascript files switch to .prototype. definition
                  var str = content.replace(functionReg, "$1" + fileNameResult[1] + ".prototype.$2 = function(");
                  if (str.match(ctorReg)) {
                     str = str.replace(ctorReg, "");
                     ctorTracker[fileNameResult[1]] = true;
                  }
                  Util.writeToFile(files[i], str);
               }
            }
      
            // all constructors that haven't been defined in functions are added to helper-file
            for (var key in ctorTracker) {
               if (key.toLowerCase() == "root" || key.toLowerCase() == "hopobject") {
                  continue;
               }
               if (ctorTracker[key] === false) {
                  propsDef += "function " + key + "(){return;}\n"
               }
            }
      
            var file = Util.getFile(workHelmadoc, "definitions.js");
            Util.writeToFile(file, propsDef);
            
         ]]> </script>
         <echo message="... prepared files for JSDoc." />
         <mkdir dir="@{destdir}" />
         <exec executable="perl">
            <arg value="${env.BUILD_HOME}/JSDoc/jsdoc.pl" />
            <arg value="--template-dir=${env.BUILD_HOME}/JSDoc/templates" />
            <arg value="-d=@{destdir}" />
            <arg value="--extensions=js,hac,properties" />
            <arg value="--no-sources" />
            <arg value="--recursive" />
            <arg value="--quiet" />
            <arg value="--package-naming" />
            <arg value="--project-name=@{projectName}" />
            <arg value="--project-summary=@{srcdir}/.jsdoc/summary.html" />
            <arg value="--globals-name=Global" />
         	<arg value="${workHelmadoc}" />
         </exec>
      
         <delete dir="${workHelmadoc}" />
      </sequential>
   </macrodef>


   <!-- =================================================================== -->
   <!-- Compresses javascript files in a fileset                            -->
   <!--                                                                     -->
   <!-- nested element:                                                     -->
   <!-- <fileset> - the files to compress                                   -->
   <!-- =================================================================== -->
   <macrodef name="jsmin">
      <element name="filesetEl" implicit="true" />
      <sequential>
         <path id="filepath">
            <filesetEl />
         </path>
         <script language="javascript"> <![CDATA[
            // load libraries:
            eval(file_jsant);
            eval(file_jsmin);
   
            // loop through files in fileset passed by reference
            var files = filepath.list();
            for (var i=0; i<files.length; i++) {
               // read original code
               var f = new java.io.File(files[i]);
               var oldCode = Util.readFile(f.getCanonicalPath());
   
               // compress
               try {
                  var newCode = jsmin("", oldCode, 2);
               } catch (anyerror) {
                  Util.log(files[i] + " could not be compressed because of an error: " + anyerror);
                  continue;
               }
   
               // load comment
               var comment = Util.readFile(new java.io.File(f.getParent(), "COPYRIGHT.txt").toString());
               newCode = comment + newCode + "\n";
   
               Util.writeToFile(f.getCanonicalPath(), newCode);
   
               // log message
               var msg = "Reduced " + files[i] + " from " + oldCode.length + " to " + newCode.length + " bytes";
               msg += " (" + (Math.round(newCode.length / oldCode.length * 1000) / 10) + "% of original size)";
               Util.log(msg);
            }
         ]]> </script>
      </sequential>
   </macrodef>


   
   <!-- =================================================================== -->
   <!-- Validate javascript files in a fileset                              -->
   <!--                                                                     -->
   <!-- nested element:                                                     -->
   <!-- <fileset> - the files to validate                                   -->
   <!--                                                                     -->
   <!-- attributes:                                                         -->
   <!-- printWarnings: if "true", syntax warnings are written too           -->
   <!--                      (and not just errors)                          -->
   <!-- failOnError: if "true" build process fails if there are             -->
   <!--                      any javascript errors                          -->
   <!-- =================================================================== -->
   <macrodef name="jslint">
      <attribute name="printWarnings" default="true" />
      <attribute name="failOnError" default="false" />
      <element name="filesetEl" implicit="true" />
      <sequential>
         <!-- copy attributes to local properties (that can be used in the script task) -->
         <property name="printWarningsProp" value="@{printWarnings}" />
         <property name="failOnErrorProp" value="@{failOnError}" />
         <!-- nested elements can't be used from script task, so wrap <path> around the <fileset> -->
         <path id="filepath">
            <filesetEl />
         </path>
         <script language="javascript"> <![CDATA[
            // load libraries:
            eval(file_jsant);
            eval(file_jslint);

            // store whether there was a major error in any of the files
            var fail = false;
            var failStr = "";

            // loop through files in fileset passed by reference
            var files = filepath.list();
            for (var i=0; i<files.length; i++) {
               // get file object and parse source file
               var f = new java.io.File(files[i]);
               var re = JSLINT(Util.readFile(f.getCanonicalPath()) ,{browser:true, passfail:false, undef:true});
   
               // loop through error and warning messages
               for (var j=0; j<JSLINT.errors.length; j++) {
                  var e = JSLINT.errors[j];
                  if(e && printWarningsProp == "true") {
                     // warning message
                     Util.log("Javascript Warning in " + files[i] + ", line " + (e.line+1) + ", character " + (e.character+1)+ ":\n" + e.reason + "\n\n");
                  } else if (!e) {
                     // if last element of the array is null, the one 
                     // before that element was an error
                     e = JSLINT.errors[j-1];
                     failStr += "Javascript error in " + files[i] + ", line " + (e.line+1) + ", character " + (e.character+1)+ ":\n" + e.reason + "\n\n";
                     fail = true;
                  }
               }
            }
            if (fail && failOnErrorProp == "true")  {
               // failing directly from javascript using createTask("fail")
               // results in a bulk of stack traces.
               // so set the property here ... and fail outside the script task
               Util.setProperty("validateFailed", "true");
               Util.log(failStr);
            }
         ]]> </script>
         <fail if="validateFailed">validate failed: error(s) in javascript code.</fail>
      </sequential>
   </macrodef>

</project>
