Package de.bezier.mode.coffeescript

Source Code of de.bezier.mode.coffeescript.CoffeeScriptBuild

package de.bezier.mode.coffeescript;

import de.bezier.mode.coffeescript.compiler.*;
import de.bezier.mode.javascript.JavaScriptBuild;

import processing.core.PApplet;

import processing.app.Base;
import processing.app.Mode;
import processing.app.Sketch;
import processing.app.SketchCode;
import processing.app.Library;
import processing.app.SketchException;
import processing.app.Preferences;

import java.io.File;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.*;
import java.io.FileInputStream;

import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;

import javax.script.*;

/**
*  This clearly needs a cleanup run.
*/
public class CoffeeScriptBuild extends JavaScriptBuild
  public static String TEMPLATE_FOLDER_NAME = "template-coffee";
  public static String EXPORTED_FOLDER_NAME = "web-export-coffee";
 
  public static final String SIZE_REGEX =
      "(?:^|\\s|;)[@]?size\\s*[(]?\\s*([^\\s,]+)\\s*,\\s*([^\\s,\\)]+),?\\s*([^\\)]*)\\s*[)]?\\s*[;]?";
 
  public final static String IMPORT_REGEX =
    "^[\\s]*#[\\s]*import[\\s]+([^\\s]+)[\\s]*";
 
  private final static String SETUP_REGEX =
    "^[\\s]*setup[\\s]*[:][\\s]*->[\\s]*?";

  private final static String LOOP_CHECK_REGEX =
    "[\\s]+loop[\\s]*\\([\\s]*\\)";

  private File binFolder;
  private Sketch sketch;
  private Mode mode;
 
  public CoffeeScriptBuild ( Sketch sketch )
  {
    super( sketch );
   
    this.sketch = sketch;
    this.mode = sketch.getMode();
  }
 
  /**
   * Export the sketch to the default web-export-coffee folder. 
   * @return success of the operation
   */
  public boolean export() throws IOException, SketchException
  {
    File exportFolder = new File(sketch.getFolder(), EXPORTED_FOLDER_NAME);
    return build( exportFolder );
  }
 
  /**
   * Builds the sketch
   *
   * @param bin the output folder for the built sketch
   * @return boolean whether the build was successful
   */
  public boolean build ( File bin ) throws IOException, SketchException
  {
    // make sure the user isn't playing "hide-the-sketch-folder" again
    sketch.ensureExistence();

    this.binFolder = bin;

    if ( bin.exists() )
    {   
      Base.removeDescendants( bin );
    }

    // generate an ID for the sketch to use with <canvas id="XXXX"></canvas>
    String sketchID = sketch.getName().replaceAll("[^a-zA-Z0-9]+", "").replaceAll("^[^a-zA-Z]+","");

    String coffeeSketchName = "Sketch" +
                  sketchID.substring(0,1).toUpperCase() +
                  sketchID.substring(1).toLowerCase();

    // use as many spaces as defined in preferences
    String oneTab = String.format( "%" + ((CoffeeScriptMode)mode).getEditor().getSketchTabsSize() + "s", " " );
   
    StringBuffer bigCode = new StringBuffer();
    String modeExt = mode.getDefaultExtension(); // only loads the .pde files
    boolean setupFound = false;
    String apiInjectToken = "\"Processing API injection iffy goes here\";";

    for (SketchCode sc : sketch.getCode())
    {
      if (sc.isExtension(modeExt))
      {
        String prog = sc.getProgram();
        String[] progLns = prog.split("\n");
        // TODO:
        // as we are adding a class around the code right shift every line by one tab
        // this is potantially problematic with multiline strings ..
        for ( String l : progLns )
        {
          if ( !setupFound )
          {
            if ( l.replaceAll("[\\s]", "").equals("setup:->") )
            {
              String indent = "";
              int indentSetup = l.indexOf("setup");
              if ( indentSetup > 0 ) {
                indent = String.format( "%" + indentSetup + "s", " " );
              }
              bigCode.append( oneTab + indent + l + "\n" );
              l = oneTab + apiInjectToken; // add a marker where inject snip will go later
              setupFound = true;
            }
          }
          bigCode.append( oneTab + l + "\n" );
        }
      }
    }

    String coffeeCode = "\n" + "class " + coffeeSketchName +
              "\n" + bigCode.toString() +
              "\n";
   
    // ------------------------------------------
    //   CHECK FOR loop KEYWORD
    // ------------------------------------------

    String[] loopMatches = PApplet.match( coffeeCode, LOOP_CHECK_REGEX );
    if ( loopMatches != null && loopMatches.length > 0 )
    {
      Base.showWarning(
        "\"loop\" is a CoffeeScript keyword",
        "The precompiler found one or more instances of \"loop()\" in your code.\n"+
        "If you were intending to use the Processing loop() function there,\n"+
        "please change it to \"doLoop()\" as loop is a CoffeeScript keyword.",
        null );
      return false;
    }
   
    // ------------------------------------------
    //   PRE-COMPILE
    // ------------------------------------------
   
    File csCompilerScript = sketch.getMode().getContentFile(
                  TEMPLATE_FOLDER_NAME + File.separator + "coffee-script.js" );
    File compiledSketchFile = new File( bin, sketch.getName()+"-compiled.js" );
    String compiledSketch = CSCompiler.compile(
      csCompilerScript,
      coffeeCode,
      compiledSketchFile
    );
    if ( compiledSketch == null ) {
      System.out.println( coffeeCode );
      return false;
    }

    // ------------------------------------------
    //   INJECT Processing API
    // ------------------------------------------

    // inject Processing API to remove need for "@" ("this") everywhere ..

    // read file in
    File api = sketch.getMode().getContentFile( "processing-api-min.js" );
    BufferedReader reader = PApplet.createReader(api);
    StringBuilder builder = new StringBuilder();
    String oneLine;
    while ((oneLine = reader.readLine()) != null) builder.append( oneTab + oneLine.replaceAll("\r","\n") + "\n" );
    String apiStr = builder.toString();

    // find beginning and add api (see processing-api.js) after it
    String sketchHead = coffeeSketchName + " = (function() {";
    String compiledInjectedSketch = compiledSketch.replace( sketchHead, sketchHead + "\n" + apiStr + "\n" );

    // add an IIFE (iffy) to inject on runtime
    compiledInjectedSketch = compiledInjectedSketch.replace(
                    apiInjectToken,
                    "(function(processing){injectProcessingApi(processing);"+ // TODO: call on instance or proto?
                    "size=function csModeApiInjectIffy (){"+
                    "processing.size.apply(processing,arguments);"+
                    "injectProcessingApi(processing);"+
                    "}})(this);" );

    // write it back out
    PrintWriter writer = PApplet.createWriter( compiledSketchFile );
    String[] csLines = compiledInjectedSketch.split( "\n" );
    for ( String l : csLines )
    {
      writer.println( l );
    }
    writer.close();

    // ------------------------------------------
    //   ADD FILES
    // ------------------------------------------
   
    // move the data files, copies contents of sketch/data/ to web-export-coffee/
    if ( sketch.hasDataFolder() )
    {
      try {
        Base.copyDir( sketch.getDataFolder(), bin );

      } catch ( IOException e ) {
        final String msg = "An exception occured while trying to copy the data folder. " +
                   "You may have to manually move the contents of sketch/data to " +
                   "the web-export-coffee/ folder. Processing.js doesn't look for a data " +
                   "folder, so lump them together.";
        Base.showWarning("Problem building the sketch", msg, e);
      }
    }

    // as .js and .coffee files are allowed now include these into the mix,
    // first find and compile them ..
    String[] sketchFolderFilesRaw = sketch.getFolder().list();
    String[] sketchFolderFiles = new String[0];
    ArrayList sffList = new ArrayList();
    if ( sketchFolderFilesRaw != null )
    {
      for ( String s : sketchFolderFilesRaw )
      {
        if ( s.toLowerCase().startsWith(".") ) continue;
        if ( s.toLowerCase().endsWith(".pde") )
        { 
          sffList.add(s);
        }
        else if ( s.toLowerCase().endsWith(".js") )
        { 
          sffList.add(s);
        }
        else if ( s.toLowerCase().endsWith(".coffee") )
        {
          String t = s.replace(".coffee","-compiled.js");
          String res2 = CSCompiler.compile(
            csCompilerScript,
            new File( sketch.getFolder(), s ),
            new File( bin, t )
          );
          if ( res2 == null ) {
            System.err.println( "Something went wrong compiling: " + s );
            return false;
          }
          sffList.add(s);
        }
        else
          continue;
      }
      if ( sffList.size() > 0 )
        sketchFolderFiles = (String[])sffList.toArray(new String[0]);
    }
   
    // ... now copy them over.
    for ( String s : sketchFolderFiles )
    {
      try {
        Base.copyFile( new File(sketch.getFolder(), s), new File(bin, s) );
      } catch ( IOException ioe ) {
        String msg = "Unable to copy file: "+s;
        Base.showWarning("Problem building the sketch", msg, ioe);
        return false;
      }
    }
   
    // ------------------------------------------
    //   IMPORT LIBRARIES
    // ------------------------------------------
   
    String[] matches;
   
    ArrayList<String> importPackages = new ArrayList<String>();
    ArrayList<String> csImports = new ArrayList<String>();
    String[] lines = bigCode.toString().split( "\n" );
   
    for ( String l : lines )
    {
      int iIndex = l.indexOf( "import" );
      if ( iIndex != -1 )
      {
        String[] iStatements = l.split(";");
        for ( String iExpression : iStatements )
        {
          matches = PApplet.match( iExpression, IMPORT_REGEX );
          if ( matches != null && matches.length >= 2 && matches[1] != null )
          {
            String iPackage = matches[1];
            iPackage = iPackage.trim();

            if ( iPackage.indexOf(".*") != -1 ) {
              // de.bezier.tutti.*
              iPackage = iPackage.replace( ".*", "" );
            } else {
              // de.bezier.uno.SingleClass
              iPackage = iPackage.replaceAll( "\\.[^.]+$", "" );
            }
            if ( !importPackages.contains(iPackage) ) // is this a "==" or ".equals()" ?
              importPackages.add( iPackage );
          }
        }
      }
    }
   
    if ( importPackages.size() > 0 )
    {
      File libsExport = new File( bin, "libs" );
      if ( !libsExport.mkdir() )
      {
        Base.showWarning( "Error",
                   "Unable to create 'libs' in export folder.",
                  null );
        return false;
      }
    }
   
    for ( String pack : importPackages )
    {
      Library lib = mode.getLibrary( pack );
      if ( lib != null )
      {
        String libPath = lib.getJarPath();
        File libJar = new File( libPath );
        if ( libJar.exists() )
        {
          File libCS = new File( libJar.getParent(), libJar.getName().replace(".jar",".js") );
          //System.out.println( libCS.getPath() );
          if ( libCS.exists() )
          {
            String libCSDest = "libs" + File.separator + libCS.getName();
            File libCSDestFile = new File( bin, libCSDest );
            if ( libCSDestFile.exists() )
            {
              System.out.println( "Duplicate import!" );
            }
            try
            {
              Base.copyFile( libCS,
                       libCSDestFile );
              csImports.add( libCSDest );

            } catch ( Exception se ) {
              se.printStackTrace();
            }
          }
        }
      }
    }
   
    // ------------------------------------------
    //   GRAB WIDTH, HEIGHT FOR HTML
    // ------------------------------------------

    // get width and height
    int wide = PApplet.DEFAULT_WIDTH;
    int high = PApplet.DEFAULT_HEIGHT;

    // TODO
    // Really scrub comments from code?
    // Con: larger files, PJS/coffee needs to do it later
    // Pro: being literate as we are in a script language.
    String scrubbed = scrubComments( sketch.getCode(0).getProgram() );
    matches = PApplet.match( scrubbed, SIZE_REGEX );
   
    if ( matches != null )
    {
      try
       {
        wide = Integer.parseInt(matches[1]);
        high = Integer.parseInt(matches[2]);
        // renderer

      } catch ( NumberFormatException e ) {
        // if ( ((JavaScriptMode)mode).showSizeWarning ) {
        //              // found a reference to size, but it didn't seem to contain numbers
        //  final String message =
        //    "The size of this applet could not automatically be\n" +
        //    "determined from your code. You'll have to edit the\n" +
        //    "HTML file to set the size of the applet.\n" +
        //    "Use only numeric values (not variables) for the size()\n" +
        //    "command. See the size() reference for an explanation.";
        //  Base.showWarning("Could not find applet size", message, null);
        // // warn only once ..
        // ((JavaScriptMode)mode).showSizeWarning = false;
        //}
      }
     
    }  // else no size() command found, defaults will be used
   
    // ------------------------------------------
    //   PREP TEMPLATE
    // ------------------------------------------
   
    // final prep and write to template.
    // getTemplateFile() is very important as it looks up and preps
    // any custom templates present in the sketch folder.
   
    File templateFile = getTemplateFile();
    File htmlOutputFile = new File(bin, "index.html");

    Map<String, String> templateFields = new HashMap<String, String>();
    templateFields.put( "width",     String.valueOf(wide) );
    templateFields.put( "height",     String.valueOf(high) );
    templateFields.put( "sketch",     sketch.getName() );
    templateFields.put( "description",   getSketchDescription() );
   
    // add a handy method to read the generated sketchID
    String scriptFiles = "<script type=\"text/javascript\">" +
                 "function getProcessingSketchID () { return '"+sketchID+"'; }" +
                 "</script>\n";
   
    // add imports if any ...
    for ( String importScript : csImports )
    {
      scriptFiles += "<script type=\"text/javascript\" src=\""+importScript+"\"></script>";
    }

    // main .pde file first
    String sourceFiles = "<a href=\"" + sketch.getName() + ".pde\">" + sketch.getName() + "</a> ";

    // add all other files (both types: .pde and .js)
    if ( sketchFolderFiles != null )
    {
      for ( String s : sketchFolderFiles )
      {
        if ( s.toLowerCase().endsWith(".pde") && !s.equals(sketch.getName()+".pde") )
        {
          sourceFiles += "<a href=\"" + s + "\">" + s.replace(".pde","") + "</a> ";
        }
        else if ( s.toLowerCase().endsWith(".js") )
        {
          sourceFiles += "<a href=\"" + s + "\">" + s + "</a> ";
          scriptFiles += "<script src=\""+ s +"\" type=\"text/javascript\"></script>\n";
        }
        else if ( s.toLowerCase().endsWith(".coffee") )
        {
          sourceFiles += "<a href=\"" + s + "\">" + s + "</a> ";
          scriptFiles += "<script src=\"" +
                    s.replace(".coffee","-compiled.js") +
                    "\" type=\"text/javascript\"></script>\n";
        }
      }
    }
    templateFields.put( "source", sourceFiles );
    templateFields.put( "scripts", scriptFiles );
    templateFields.put( "id", sketchID );
   
    templateFields.put( "coffee", coffeeCode );
    templateFields.put( "coffeescripts", "" );
    templateFields.put( "coffeesketch", coffeeSketchName );
   
    // ------------------------------------------
    //   WRITE / MOVE FILES
    // ------------------------------------------
   
    // need these ..
    String[] needed = new String[]{
      "processing.js"
    };
   
    // process template replace tokens with content
    try
    {
      writeTemplate(templateFile, htmlOutputFile, templateFields);

    } catch (IOException ioe) {
      final String msg = "There was a problem writing the html template " +
                 "to the build folder.";
      Base.showWarning( "A problem occured during the build", msg, ioe );
      return false;
    }

    try {
      for ( String n : needed )
      {
        File nf = new File( bin, n );
        if ( !nf.exists() )
        {
          Base.copyFile(
            sketch.getMode().getContentFile( TEMPLATE_FOLDER_NAME + File.separator + n ),
            nf );
        }
      }
    } catch (IOException ioe) {
      final String msg = "There was a problem copying one or more files to the " +
                 "build folder. You will have to manually add " +
                 "processing.js to the build folder before the sketch will run.";
      Base.showWarning( "There was a problem writing to the build folder", msg, ioe );
      //return false;
    }

    return true;
  }
 
  /**
   * Collects the sketch code.
   *
   * @param bin the output folder
   */
  public void preprocess ( File bin ) throws IOException
  {
    // COLLECT .pde FILES INTO ONE,
    // essentially... cat sketchFolder/*.pde > bin/sketchname.pde

    // StringBuffer bigCode = new StringBuffer();
    // String modeExt = mode.getDefaultExtension();
    // for (SketchCode sc : sketch.getCode())
    // {
    //   if (sc.isExtension(modeExt))
    //   {
    //     bigCode.append(sc.getProgram());
    //     bigCode.append("\n");
    //   }
    // }
    //
    // if (!bin.exists()) {
    //   bin.mkdirs();
    // }
    //
    // File bigFile = new File( bin, sketch.getName() + "." + modeExt );
    // String bigCodeContents = bigCode.toString();
    // Base.saveFile( bigCodeContents, bigFile );
  }
 
  /**
   *  Find and return the template HTML file to use. This also checks for custom
   *  templates that might be living in the sketch folder. If such a "template_js"
   *  folder exists then it's contents will be copied over to "web-export-coffee" and
   *  it's template.html will be used as template.
   *
   *  @return the main template file renamed to index.html
   */
  private File getTemplateFile ()
  {
    File sketchFolder = sketch.getFolder();
    File customTemplateFolder = new File( sketchFolder, TEMPLATE_FOLDER_NAME );
    if ( customTemplateFolder.exists() &&
       customTemplateFolder.isDirectory() &&
       customTemplateFolder.canRead() )
    {
      File exportFolder = new File( sketchFolder, EXPORTED_FOLDER_NAME );

      try {
        //TODO: this is potentially dangerous as it might override files in web-export-coffee
        Base.copyDir( customTemplateFolder, exportFolder );
        if ( !(new File( exportFolder, TEMPLATE_FILE_NAME )).delete() )
        {
          // ignore?
        }
        return new File( customTemplateFolder, TEMPLATE_FILE_NAME );
      } catch ( Exception e ) { 
        String msg = "";
        Base.showWarning( "There was a problem copying your custom template folder", msg, e );
        return sketch.getMode().getContentFile(
          TEMPLATE_FOLDER_NAME + File.separator + TEMPLATE_FILE_NAME
        );
      }
    }
    else
    {
      return sketch.getMode().getContentFile(
        TEMPLATE_FOLDER_NAME + File.separator + TEMPLATE_FILE_NAME
      );
    }
   }
 
  /**
   * Reads in a simple template file, with fields of the form '@@somekey@@'
   * and replaces each field with the value in the map for 'somekey', writing
   * the output to the output file.
   *
   * Keys not in the map will be replaced with empty strings.
   *
   * @param template File object mapping to the template
   * @param output File object handle to the output
   * @param args template keys, data values to replace them
   * @throws IOException when there are problems writing to or from the files
   */
  public static void writeTemplate ( File template, File output, Map<String, String> fields )
  throws IOException
  {
    BufferedReader reader = PApplet.createReader(template);
    PrintWriter writer = PApplet.createWriter(output);

    String line = null;
    while ((line = reader.readLine()) != null) {
      if (line.indexOf("@@") != -1) {
        StringBuffer sb = new StringBuffer(line);
        int start = 0, end = 0;
        while ((start = sb.indexOf("@@")) != -1) {
          if ((end = sb.indexOf("@@", start+1)) != -1) {
            String value = fields.get(sb.substring(start+2, end));
            sb.replace(start, end+2, value == null ? "" : value );
          } else {
            Base.showWarning( "Problem writing file from template",
                      "The template appears to have an unterminated " +
                      "field. The output may look a little funny.",
                      null );
          }
        }
        line = sb.toString();
      }
      writer.println(line);
    }
    writer.close();
  }
 
  /**
   * Read the first / head comment from tab one.
   */
  public String getSketchDescription() {
    // TODO!
    return "";
  }
 
  /**
   * Remove all comments from source.
   */
  private String scrubComments ( String commentedSource ) {
    // TODO!
    // single line comments start with '#'
    // multiline comments are wrapped in '###'
    return commentedSource;
  }
}
TOP

Related Classes of de.bezier.mode.coffeescript.CoffeeScriptBuild

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.