Initial JavaScriptMode commit

This commit is contained in:
lonnen
2011-03-27 02:13:19 +00:00
parent e392df8705
commit 4b9e2be6d4
19 changed files with 20224 additions and 2 deletions

View File

@@ -0,0 +1,272 @@
package processing.mode.javascript;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import processing.app.Base;
import processing.app.Mode;
import processing.app.Sketch;
import processing.app.SketchCode;
import processing.core.PApplet;
import processing.mode.java.JavaBuild;
/**
* A collection of static methods to aid in building a
* web page with Processing.js from a Sketch object.
*/
public class JavaScriptBuild {
// public static final String SIZE_REGEX
// public static final String PACKAGE_REGEX
/**
* Answers with the first java doc style comment in the string,
* or an empty string if no such comment can be found.
*/
public static String getDocString(String s) {
String[] javadoc = PApplet.match(s, "/\\*{2,}(.*?)\\*+/");
if (javadoc != null) {
StringBuffer dbuffer = new StringBuffer();
String[] pieces = PApplet.split(javadoc[1], '\n');
for (String line : pieces) {
// if this line starts with * characters, remove 'em
String[] m = PApplet.match(line, "^\\s*\\*+(.*)");
dbuffer.append(m != null ? m[1] : line);
// insert the new line into the html to help w/ line breaks
dbuffer.append('\n');
}
return dbuffer.toString().trim();
}
return "";
}
/**
* 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 theOutWriter = 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();
}
theOutWriter.println(line);
}
theOutWriter.close();
}
// -----------------------------------------------------
/**
* The sketch this builder is working on.
* <p>
* Each builder instance should only work on a single sketch, so if
* you have more than one sketch each will need a separate builder.
*/
protected Sketch sketch;
protected Mode mode;
protected File binFolder;
public JavaScriptBuild(Sketch sketch) {
this.sketch = sketch;
this.mode = sketch.getMode();
}
/**
* Builds the sketch
* <p>
* The process goes a little something like this:
* 1. rm -R bin/*
* 2. cat *.pde > bin/sketchname.pde
* 3. cp -r sketch/data/* bin/ (p.js doesn't recognize the data folder)
* 4. series of greps to find height, width, name, desc
* 5. cat template.html | sed 's/@@sketch@@/[name]/g' ... [many sed filters] > bin/index.html
*
* @param bin the output folder for the built sketch
* @return boolean whether the build was successful
*/
public boolean build(File bin) {
// make sure the user isn't playing "hide-the-sketch-folder" again
sketch.ensureExistence();
this.binFolder = bin;
if (bin.exists()) {
Base.removeDescendants(bin);
} //else will be created during preprocesss
try {
preprocess(bin);
} catch (IOException e) {
final String msg = "A problem occured while writing to the output folder.";
Base.showWarning("Could not build the sketch", msg, e);
return false;
}
// move the data files
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 applet_js/ folder. Processing.js doesn't look for a data " +
"folder, so lump them together.";
Base.showWarning("Problem building the sketch", msg, e);
}
}
// TODO Code folder contents ending in .js could be moved and added as script tags?
// get width and height
int wide = PApplet.DEFAULT_WIDTH;
int high = PApplet.DEFAULT_HEIGHT;
String scrubbed = JavaBuild.scrubComments(sketch.getCode(0).getProgram());
String[] matches = PApplet.match(scrubbed, JavaBuild.SIZE_REGEX);
if (matches != null) {
try {
wide = Integer.parseInt(matches[1]);
high = Integer.parseInt(matches[2]);
// renderer
} catch (NumberFormatException e) {
// 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);
}
} // else no size() command found, defaults will be used
// final prep and write to template
File templateFile = sketch.getMode().getContentFile("applet_js/template.html");
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());
templateFields.put("source",
"<a href=\"" + sketch.getName() + ".pde\">" +
sketch.getName() + "</a>");
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;
}
// finally, add Processing.js
try {
Base.copyFile(sketch.getMode().getContentFile("applet_js/processing.js"),
new File(bin, "processing.js"));
} catch (IOException ioe) {
final String msg = "There was a problem copying processing.js 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;
}
/**
* Prepares the sketch code objects for use with Processing.js
* @param bin the output folder
*/
public void preprocess(File bin) throws IOException {
// essentially... cat sketchFolder/*.pde > bin/sketchname.pde
StringBuffer bigCode = new StringBuffer();
for (SketchCode sc : sketch.getCode()){
if (sc.isExtension("pde")) {
bigCode.append(sc.getProgram());
bigCode.append("\n");
}
}
if (!bin.exists()) {
bin.mkdirs();
}
File bigFile = new File(bin, sketch.getName() + ".pde");
Base.saveFile(bigCode.toString(), bigFile);
}
/**
* Parse the sketch to retrieve it's description. Answers with the first
* java doc style comment in the main sketch file, or an empty string if
* no such comment exists.
*/
public String getSketchDescription() {
return getDocString(sketch.getCode(0).getProgram());
}
// -----------------------------------------------------
// Export
/**
* Export the sketch to the default applet_js folder.
* @return success of the operation
*/
public boolean export() throws IOException {
File applet_js = new File(sketch.getFolder(), "applet_js");
return exportApplet_js(applet_js);
}
/**
* Export the sketch to the provided folder
* @return success of the operation
*/
public boolean exportApplet_js(File appletfolder) throws IOException {
return build(appletfolder);
}
}

View File

@@ -0,0 +1,247 @@
package processing.mode.javascript;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import processing.app.Base;
import processing.app.Editor;
import processing.app.EditorToolbar;
import processing.app.Formatter;
import processing.app.Mode;
import processing.mode.java.AutoFormat;
public class JavaScriptEditor extends Editor {
private JavaScriptMode jsMode;
protected JavaScriptEditor(Base base, String path, int[] location, Mode mode) {
super(base, path, location, mode);
jsMode = (JavaScriptMode) mode;
}
public EditorToolbar createToolbar() {
return new JavaScriptToolbar(this, base);
}
public Formatter createFormatter() {
return new AutoFormat();
}
// - - - - - - - - - - - - - - - - - -
// Menu methods
public JMenu buildFileMenu() {
JMenuItem exportItem = Base.newJMenuItem("export title", 'E');
exportItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
handleExport();
}
});
return buildFileMenu(new JMenuItem[] { exportItem });
}
public JMenu buildSketchMenu() {
return buildSketchMenu(new JMenuItem[] {});
}
public JMenu buildHelpMenu() {
JMenu menu = new JMenu("Help ");
JMenuItem item;
item = new JMenuItem("QuickStart for JS Devs");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Base.openURL("http://processingjs.org/reference/articles/jsQuickStart");
}
});
menu.add(item);
item = new JMenuItem("QuickStart for Processing Devs");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Base.openURL("http://processingjs.org/reference/articles/p5QuickStart");
}
});
menu.add(item);
/* TODO Implement an environment page
item = new JMenuItem("Environment");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
showReference("environment" + File.separator + "index.html");
}
});
menu.add(item);
*/
/* TODO Implement a troubleshooting page
item = new JMenuItem("Troubleshooting");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Base.openURL("http://wiki.processing.org/w/Troubleshooting");
}
});
menu.add(item);
*/
item = new JMenuItem("Reference");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
//TODO get offline reference archive corresponding to the release
// packaged with this mode see: P.js ticket 1146 "Offline Reference"
Base.openURL("http://processingjs.org/reference");
}
});
menu.add(item);
item = Base.newJMenuItemShift("Find in Reference", 'F');
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (textarea.isSelectionActive()) {
Base.openURL(
"http://www.google.com/search?q=" +
textarea.getSelectedText() +
"+site%3Ahttp%3A%2F%2Fprocessingjs.org%2Freference"
);
}
}
});
menu.add(item);
/* TODO FAQ
item = new JMenuItem("Frequently Asked Questions");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Base.openURL("http://wiki.processing.org/w/FAQ");
}
});
menu.add(item);
*/
item = new JMenuItem("Visit Processingjs.org");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Base.openURL("http://processingjs.org/");
}
});
menu.add(item);
// OSX has its own about menu
if (!Base.isMacOS()) {
menu.addSeparator();
item = new JMenuItem("About Processing");
item.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent e) {
base.handleAbout();
}
});
menu.add(item);
}
return menu;
}
// - - - - - - - - - - - - - - - - - -
public String getCommentPrefix() {
return "//";
}
// - - - - - - - - - - - - - - - - - -
/**
* Call the export method of the sketch and handle the gui stuff
*/
public void handleExport() {
if (handleExportCheckModified()) {
toolbar.activate(JavaScriptToolbar.EXPORT);
try {
boolean success = jsMode.handleExport(sketch);
if (success) {
File appletJSFolder = new File(sketch.getFolder(), "applet_js");
Base.openFolder(appletJSFolder);
statusNotice("Finished exporting.");
} else {
// error message already displayed by handleExport
}
} catch (Exception e) {
statusError(e);
}
toolbar.deactivate(JavaScriptToolbar.EXPORT);
}
}
public boolean handleExportCheckModified() {
if (sketch.isModified()) {
Object[] options = { "OK", "Cancel" };
int result = JOptionPane.showOptionDialog(this,
"Save changes before export?",
"Save",
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,
options,
options[0]);
if (result == JOptionPane.OK_OPTION) {
handleSaveRequest(true);
} else {
// why it's not CANCEL_OPTION is beyond me (at least on the mac)
// but f-- it.. let's get this shite done..
//} else if (result == JOptionPane.CANCEL_OPTION) {
statusNotice("Export canceled, changes must first be saved.");
//toolbar.clear();
return false;
}
}
return true;
}
public void handleSave() {
toolbar.activate(JavaScriptToolbar.SAVE);
super.handleSave();
toolbar.deactivate(JavaScriptToolbar.SAVE);
}
public boolean handleSaveAs() {
toolbar.activate(JavaScriptToolbar.SAVE);
boolean result = super.handleSaveAs();
toolbar.deactivate(JavaScriptToolbar.SAVE);
return result;
}
public void handleImportLibrary(String item) {
Base.showWarning("Processing.js doesn't support libraries",
"Libraries are not supported. Import statements are " +
"ignored, and code relying on them will break.",
null);
}
/** JavaScript mode has no runner. This method is empty. */
public void internalCloseRunner() { }
/** JavaScript mode does not run anything. This method is empty. */
public void deactivateRun() { }
}

View File

@@ -0,0 +1,132 @@
package processing.mode.javascript;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import processing.app.Base;
import processing.app.Editor;
import processing.app.Mode;
import processing.app.Sketch;
import processing.app.syntax.PdeKeywords;
import processing.core.PApplet;
/**
* JS Mode is very simple. Since P.js is dependent on a browser there is
* no runner, just an export so that users can debug in the browser of their
* choice.
*/
public class JavaScriptMode extends Mode {
// create a new editor with the mode
public Editor createEditor(Base base, String path, int[] location) {
return new JavaScriptEditor(base, path, location, this);
}
public JavaScriptMode(Base base, File folder) {
super(base, folder);
try {
loadKeywords();
} catch (IOException e) {
Base.showError("Problem loading keywords",
"Could not load keywords.txt, please re-install Processing.", e);
}
}
protected void loadKeywords() throws IOException {
File file = new File(folder, "keywords.txt");
BufferedReader reader = PApplet.createReader(file);
tokenMarker = new PdeKeywords();
keywordToReference = new HashMap<String, String>();
String line = null;
while ((line = reader.readLine()) != null) {
String[] pieces = PApplet.trim(PApplet.split(line, '\t'));
if (pieces.length >= 2) {
String keyword = pieces[0];
String coloring = pieces[1];
if (coloring.length() > 0) {
tokenMarker.addColoring(keyword, coloring);
}
if (pieces.length == 3) {
String htmlFilename = pieces[2];
if (htmlFilename.length() > 0) {
keywordToReference.put(keyword, htmlFilename);
}
}
}
}
}
// pretty printable name of the mode
public String getTitle() {
return "JavaScript";
}
// public EditorToolbar createToolbar(Editor editor) { }
// public Formatter createFormatter() { }
// public Editor createEditor(Base ibase, String path, int[] location) { }
// ------------------------------------------------
//TODO Add examples
protected File[] getExampleCategoryFolders() {
return new File[] {};
/*
return new File[] {
new File(examplesFolder, "Basics"),
new File(examplesFolder, "Topics"),
new File(examplesFolder, "3D"),
new File(examplesFolder, "Books")
};
*/
}
public String getDefaultExtension() {
return "pde";
}
// all file extensions it supports
public String[] getExtensions() {
return new String[] {"pde", "pjs"};
}
public String[] getIgnorable() {
return new String[] {
"applet_js" // not sure what color to paint this bike shed
};
}
// ------------------------------------------------
public boolean handleExport(Sketch sketch) throws IOException {
JavaScriptBuild build = new JavaScriptBuild(sketch);
return build.export();
}
//public boolean handleExportApplet(Sketch sketch) throws SketchException, IOException { }
//public boolean handleExportApplication(Sketch sketch) throws SketchException, IOException { }
}

View File

@@ -0,0 +1,75 @@
package processing.mode.javascript;
import java.awt.Image;
import java.awt.event.MouseEvent;
import javax.swing.JPopupMenu;
import processing.app.Base;
import processing.app.Editor;
import processing.app.EditorToolbar;
public class JavaScriptToolbar extends EditorToolbar {
// static protected final int RUN = 0;
// static protected final int STOP = 1;
static protected final int NEW = 0;
static protected final int OPEN = 1;
static protected final int SAVE = 2;
static protected final int EXPORT = 3;
static public String getTitle(int index, boolean shift) {
switch (index) {
case NEW: return !shift ? "New" : "New Editor Window";
case OPEN: return !shift ? "Open" : "Open in Another Window";
case SAVE: return "Save";
case EXPORT: return "Export for Web";
}
return null;
}
public JavaScriptToolbar(Editor editor, Base base) {
super(editor, base);
}
public void init() {
Image[][] images = loadImages();
for (int i = 0; i < 4; i++) {
addButton(getTitle(i, false), getTitle(i, true), images[i], i == NEW);
}
}
public void handlePressed(MouseEvent e, int index) {
boolean shift = e.isShiftDown();
JavaScriptEditor jsEditor = (JavaScriptEditor) editor;
switch (index) {
case OPEN:
JPopupMenu popup = editor.getMode().getToolbarMenu().getPopupMenu();
popup.show(this, e.getX(), e.getY());
break;
case NEW:
if (shift) {
base.handleNew();
} else {
base.handleNewReplace();
}
break;
case SAVE:
jsEditor.handleSaveRequest(false);
break;
case EXPORT:
jsEditor.handleExport();
break;
}
}
}