Factor out common template code. Send stdout back to web page.
authorcommit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 22 Apr 2014 21:13:45 +0000 (21:13 +0000)
committercommit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 22 Apr 2014 21:13:45 +0000 (21:13 +0000)
BUG=skia:
R=mtklein@google.com

Author: jcgregorio@google.com

Review URL: https://codereview.chromium.org/246823004

git-svn-id: http://skia.googlecode.com/svn/trunk@14317 2bbb7eff-a529-9590-31e7-b0007b416f81

experimental/webtry/TODO.md
experimental/webtry/css/webtry.css
experimental/webtry/js/run.js
experimental/webtry/templates/content.html [new file with mode: 0644]
experimental/webtry/templates/iframe.html
experimental/webtry/templates/index.html
experimental/webtry/templates/template.cpp
experimental/webtry/templates/workspace.html
experimental/webtry/webtry.go

index 049f476ec092a64c402ebdc31ef5b4d18f531d8e..b42149a5d9015d1a963f4d69e491cd0e82ac0307 100644 (file)
@@ -1,6 +1,5 @@
 Public facing feature requests
 ------------------------------
- - return printf output
  - ability to render against multiple targets (gpu/cpu)
  - versioning (which version of skia was this run against)
  - magnifying glass (both client side and server side)
@@ -15,4 +14,3 @@ Implementation details
  - Add font support in the c++ template.
  - Add inline links to doxygen.
  - Add monitoring and probing (nagios).
- - sanitize the file name in the output.
index dcc25402925bbc50a76d67d971895a6db80fe47a..70a2c5e38e543fb37dcc046bc089a38f432ffee6 100644 (file)
@@ -35,13 +35,12 @@ template {
   display: none;
 }
 
-pre, code {
-    padding: 0;
+code {
     color: green;
 }
 
-#output {
-    color: #333;
+pre, code {
+    padding: 0;
 }
 
 .tries {
index c69e2bcc22c7494ce671aeced6c78f5fec53f241..d44e81817ba4583a95993fd3da93aa85df705211 100644 (file)
@@ -43,6 +43,7 @@
     var embedButton = document.getElementById('embedButton');
     var code = document.getElementById('code');
     var output = document.getElementById('output');
+    var stdout = document.getElementById('stdout');
     var img = document.getElementById('img');
     var tryHistory = document.getElementById('tryHistory');
     var parser = new DOMParser();
       alert('Something bad happened: ' + e);
     }
 
+    function clearOutput() {
+      output.innerText = "";
+      if (stdout) {
+        stdout.innerText = "";
+      }
+      embed.style.display='none';
+    }
+
     /**
      * Called when an image in the workspace history is clicked.
      */
     function historyClick() {
       beginWait();
+      clearOutput();
       var req = new XMLHttpRequest();
       req.addEventListener('load', historyComplete);
       req.addEventListener('error', xhrError);
       console.log(e.target.response);
       body = JSON.parse(e.target.response);
       output.innerText = body.message;
+      if (stdout) {
+        stdout.innerText = body.stdout;
+      }
       if (body.hasOwnProperty('img')) {
         img.src = 'data:image/png;base64,' + body.img;
       } else {
 
     function onSubmitCode() {
       beginWait();
+      clearOutput();
       var req = new XMLHttpRequest();
       req.addEventListener('load', codeComplete);
       req.addEventListener('error', xhrError);
diff --git a/experimental/webtry/templates/content.html b/experimental/webtry/templates/content.html
new file mode 100644 (file)
index 0000000..77e68e3
--- /dev/null
@@ -0,0 +1,25 @@
+
+<section id=content>
+  <pre><code>#include "SkCanvas.h"
+
+void draw(SkCanvas* canvas) {
+  <textarea spellcheck=false name='code' id='code' rows='15' cols='80'>{{.Code}}</textarea>
+}
+</code></pre>
+
+  <input type='button' value='Run' id='run'>
+  <a href='{{if .Hash}}/c/{{.Hash}}{{end}}' target=_blank id=permalink>Share</a>
+
+  <input type='button' value='Embed' id='embedButton' disabled/>
+  <input type="text" value="" id="embed" readonly style="display:none;">
+
+  <p>
+  <img id='img' src='{{if .Hash}}/i/{{.Hash}}.png{{end}}'/>
+  </p>
+
+  <h2>Errors</h2>
+  <pre><code id='output'></code></pre>
+  <h2>Runtime output</h2>
+  <pre id='stdout'></pre>
+
+</section>
index 01e529acbccd41043029af5af11978d668ca81c8..d7b165db2f36f0d6754c0a10e8a9bd1f6c39f197 100644 (file)
@@ -6,17 +6,7 @@
     <link rel='stylesheet' href='/css/' type='text/css' media='screen'>
 </head>
 <body class=iframe>
-  <section id=content>
-  <pre><code><textarea spellcheck=false name='code' id='code' rows='8' cols='80'>{{.UserCode}}</textarea></code></pre>
-
-  <input type='button' value='Run' id='run'>
-  <a href='/c/{{.Hash}}' target=_blank id=permalink>Share</a>
-
-  <p><img id='img' src=''/></p>
-
-  <pre><code id='output'></code></pre>
-
-  </section>
+  {{template "content.html" .}}
   <script type='text/javascript' charset='utf-8'>
       // Not running in a workspace.
       var workspaceName = '';
index a29bfd1b839655f471193bb33f9d682b5f5d9b72..bab6c005457dc314e99968db5232c06ed81ec37e 100644 (file)
@@ -7,26 +7,8 @@
 </head>
 <body>
   {{template "titlebar.html"}}
-  <section id=content>
-  <pre><code>#include "SkCanvas.h"
+  {{template "content.html" .}}
 
-void draw(SkCanvas* canvas) {
-  <textarea spellcheck=false name='code' id='code' rows='15' cols='80'>{{.UserCode}}</textarea>
-}
-</code></pre>
-
-  <input type='button' value='Run' id='run'>
-
-  <input type='button' value='Embed' id='embedButton' disabled/>
-  <input type="text" value="" id="embed" readonly style="display:none;">
-
-  <p>
-  <img id='img' src='{{if .Hash}}/i/{{.Hash}}.png{{end}}'/>
-  </p>
-
-  <pre><code id='output'></code></pre>
-
-  </section>
   <script type='text/javascript' charset='utf-8'>
       // Not running in a workspace.
       var workspaceName = "";
index d256cdc6a71ad6359e30ae518966f0251b6e28d6..bef525edc5d4d3b026636a4f3dc9e782f5e3ed9a 100644 (file)
 #include "SkXfermodeImageFilter.h"
 
 void draw(SkCanvas* canvas) {
-{{.UserCode}}
+{{.Code}}
 }
index a802ea719ebb79e05f4bc94e226cc6db7e8713d0..18b858f7e859b46eb6acce8aec5fc5aec057f4af 100644 (file)
     </div>
   </template>
   {{template "titlebar.html"}}
-  <section id=content>
 {{if .Name}}
-  <pre><code>#include "SkCanvas.h"
-
-void draw(SkCanvas* canvas) {
-  <textarea spellcheck=false name='code' id='code' rows='15' cols='80'>{{.Code}}</textarea>
-}
-</code></pre>
-
-  <input type='button' value='Run' id='run'>
-  <a href='{{if .Hash}}/c/{{.Hash}}{{end}}' target=_blank id=permalink>Share</a>
+  {{template "content.html" .}}
 
-  <p>Image appears here:</p>
-  <img id='img' src='{{if .Hash}}/i/{{.Hash}}.png{{end}}'/>
-
-  <pre><code id='output'></code></pre>
+  <section id=tryHistory>
   </section>
+
   <script type="text/javascript" charset="utf-8">
     var history = {{.Tries}};
   </script>
-  <section id=tryHistory>
-  </section>
-
   <script type='text/javascript' charset='utf-8'>
       // Set the workspace name so run.js also updates the history.
       var workspaceName = "{{.Name}}";
   </script>
   <script src="/js/" type="text/javascript" charset="utf-8"></script>
 {{else}}
-  <h1>Create</h1>
-  Create a new workspace:
-  <form action="." method="POST" accept-charset="utf-8">
-    <p><input type="submit" value="Create"></p>
-  </form>
-{{end}}
+  <section id=content>
+    <h1>Create</h1>
+    Create a new workspace:
+    <form action="." method="POST" accept-charset="utf-8">
+      <p><input type="submit" value="Create"></p>
+    </form>
   </section>
+{{end}}
 </body>
 </html>
index 73e9fda993d6697dd404572827499e0828ccb56d..8e02a751a32f6786f00cb669a837e02d01f9602a 100644 (file)
@@ -135,12 +135,14 @@ func init() {
        indexTemplate, err = htemplate.ParseFiles(
                filepath.Join(cwd, "templates/index.html"),
                filepath.Join(cwd, "templates/titlebar.html"),
+               filepath.Join(cwd, "templates/content.html"),
        )
        if err != nil {
                panic(err)
        }
        iframeTemplate, err = htemplate.ParseFiles(
                filepath.Join(cwd, "templates/iframe.html"),
+               filepath.Join(cwd, "templates/content.html"),
        )
        if err != nil {
                panic(err)
@@ -155,6 +157,7 @@ func init() {
        workspaceTemplate, err = htemplate.ParseFiles(
                filepath.Join(cwd, "templates/workspace.html"),
                filepath.Join(cwd, "templates/titlebar.html"),
+               filepath.Join(cwd, "templates/content.html"),
        )
        if err != nil {
                panic(err)
@@ -220,8 +223,8 @@ func init() {
 
 // userCode is used in template expansion.
 type userCode struct {
-       UserCode string
-       Hash     string
+       Code string
+       Hash string
 }
 
 // expandToFile expands the template and writes the result to the file.
@@ -231,7 +234,7 @@ func expandToFile(filename string, code string, t *template.Template) error {
                return err
        }
        defer f.Close()
-       return t.Execute(f, userCode{UserCode: code})
+       return t.Execute(f, userCode{Code: code})
 }
 
 // expandCode expands the template into a file and calculate the MD5 hash.
@@ -249,6 +252,7 @@ func expandCode(code string) (string, error) {
 // response is serialized to JSON as a response to POSTs.
 type response struct {
        Message string `json:"message"`
+       StdOut  string `json:"stdout"`
        Img     string `json:"img"`
        Hash    string `json:"hash"`
 }
@@ -298,8 +302,15 @@ func doCmd(commandLine string, moveToDebug bool) (string, error) {
 
 // reportError formats an HTTP error response and also logs the detailed error message.
 func reportError(w http.ResponseWriter, r *http.Request, err error, message string) {
+       log.Printf("Error: %s\n%s", message, err.Error())
+       http.Error(w, message, 500)
+}
+
+// reportTryError formats an HTTP error response in JSON and also logs the detailed error message.
+func reportTryError(w http.ResponseWriter, r *http.Request, err error, message, hash string) {
        m := response{
                Message: message,
+               Hash:    hash,
        }
        log.Printf("Error: %s\n%s", message, err.Error())
        resp, err := json.Marshal(m)
@@ -499,7 +510,7 @@ func iframeHandler(w http.ResponseWriter, r *http.Request) {
                return
        }
        // Expand the template.
-       if err := iframeTemplate.Execute(w, userCode{UserCode: code, Hash: hash}); err != nil {
+       if err := iframeTemplate.Execute(w, userCode{Code: code, Hash: hash}); err != nil {
                log.Printf("ERROR: Failed to expand template: %q\n", err)
        }
 }
@@ -540,6 +551,12 @@ func tryInfoHandler(w http.ResponseWriter, r *http.Request) {
        w.Write(resp)
 }
 
+func cleanCompileOutput(s, hash string) string {
+       old := "../../../cache/" + hash + ".cpp:"
+       log.Printf("INFO: replacing %q\n", old)
+       return strings.Replace(s, old, "usercode.cpp:", -1)
+}
+
 // mainHandler handles the GET and POST of the main page.
 func mainHandler(w http.ResponseWriter, r *http.Request) {
        log.Printf("Main Handler: %q\n", r.URL.Path)
@@ -560,7 +577,7 @@ func mainHandler(w http.ResponseWriter, r *http.Request) {
                        }
                }
                // Expand the template.
-               if err := indexTemplate.Execute(w, userCode{UserCode: code, Hash: hash}); err != nil {
+               if err := indexTemplate.Execute(w, userCode{Code: code, Hash: hash}); err != nil {
                        log.Printf("ERROR: Failed to expand template: %q\n", err)
                }
        } else if r.Method == "POST" {
@@ -568,38 +585,40 @@ func mainHandler(w http.ResponseWriter, r *http.Request) {
                buf := bytes.NewBuffer(make([]byte, 0, MAX_TRY_SIZE))
                n, err := buf.ReadFrom(r.Body)
                if err != nil {
-                       reportError(w, r, err, "Failed to read a request body.")
+                       reportTryError(w, r, err, "Failed to read a request body.", "")
                        return
                }
                if n == MAX_TRY_SIZE {
                        err := fmt.Errorf("Code length equal to, or exceeded, %d", MAX_TRY_SIZE)
-                       reportError(w, r, err, "Code too large.")
+                       reportTryError(w, r, err, "Code too large.", "")
                        return
                }
                request := TryRequest{}
                if err := json.Unmarshal(buf.Bytes(), &request); err != nil {
-                       reportError(w, r, err, "Coulnd't decode JSON.")
+                       reportTryError(w, r, err, "Coulnd't decode JSON.", "")
                        return
                }
                if hasPreProcessor(request.Code) {
                        err := fmt.Errorf("Found preprocessor macro in code.")
-                       reportError(w, r, err, "Preprocessor macros aren't allowed.")
+                       reportTryError(w, r, err, "Preprocessor macros aren't allowed.", "")
                        return
                }
                hash, err := expandCode(LineNumbers(request.Code))
                if err != nil {
-                       reportError(w, r, err, "Failed to write the code to compile.")
+                       reportTryError(w, r, err, "Failed to write the code to compile.", hash)
                        return
                }
                writeToDatabase(hash, request.Code, request.Name)
                message, err := doCmd(fmt.Sprintf(RESULT_COMPILE, hash, hash), true)
                if err != nil {
-                       reportError(w, r, err, "Failed to compile the code:\n"+message)
+                       message = cleanCompileOutput(message, hash)
+                       reportTryError(w, r, err, message, hash)
                        return
                }
                linkMessage, err := doCmd(fmt.Sprintf(LINK, hash, hash), true)
                if err != nil {
-                       reportError(w, r, err, "Failed to link the code:\n"+linkMessage)
+                       linkMessage = cleanCompileOutput(linkMessage, hash)
+                       reportTryError(w, r, err, linkMessage, hash)
                        return
                }
                message += linkMessage
@@ -609,7 +628,7 @@ func mainHandler(w http.ResponseWriter, r *http.Request) {
                } else {
                        abs, err := filepath.Abs("../../../inout")
                        if err != nil {
-                               reportError(w, r, err, "Failed to find executable directory.")
+                               reportTryError(w, r, err, "Failed to find executable directory.", hash)
                                return
                        }
                        cmd = abs + "/" + cmd
@@ -617,23 +636,24 @@ func mainHandler(w http.ResponseWriter, r *http.Request) {
 
                execMessage, err := doCmd(cmd, false)
                if err != nil {
-                       reportError(w, r, err, "Failed to run the code:\n"+execMessage)
+                       reportTryError(w, r, err, "Failed to run the code:\n"+execMessage, hash)
                        return
                }
                png, err := ioutil.ReadFile("../../../inout/" + hash + ".png")
                if err != nil {
-                       reportError(w, r, err, "Failed to open the generated PNG.")
+                       reportTryError(w, r, err, "Failed to open the generated PNG.", hash)
                        return
                }
 
                m := response{
                        Message: message,
+                       StdOut:  execMessage,
                        Img:     base64.StdEncoding.EncodeToString([]byte(png)),
                        Hash:    hash,
                }
                resp, err := json.Marshal(m)
                if err != nil {
-                       reportError(w, r, err, "Failed to serialize a response.")
+                       reportTryError(w, r, err, "Failed to serialize a response.", hash)
                        return
                }
                w.Write(resp)