├── Plugin ├── .gitignore ├── project │ ├── build.properties │ └── plugins.sbt ├── README.md ├── build.sbt └── src │ └── main │ └── scala │ └── com.scalakata │ └── ScalaKata.scala ├── .gitignore ├── Backend ├── src │ ├── test │ │ ├── resources │ │ │ ├── a │ │ │ │ └── 1.scala │ │ │ └── assets │ │ │ │ ├── b │ │ │ │ └── main.css │ │ │ │ └── index.html │ │ └── scala │ │ │ └── com.scalakata.backend │ │ │ └── RestSpec.scala │ └── main │ │ └── scala │ │ └── com.scalakata.backend │ │ ├── Server.scala │ │ ├── ScalaKataActor.scala │ │ └── Json.scala ├── project │ ├── build.properties │ └── plugins.sbt └── build.sbt ├── Eval ├── project │ ├── build.properties │ ├── plugins.sbt │ └── Build.scala ├── classPathtest │ └── src │ │ └── main │ │ └── scala │ │ └── com.example.test │ │ └── Testing.scala ├── macro │ └── src │ │ ├── test │ │ └── scala │ │ │ └── com.scalakata.eval │ │ │ └── InstrumentationSpec.scala │ │ └── main │ │ └── scala │ │ └── com.scalakata.eval │ │ ├── list │ │ ├── package.scala │ │ └── Annotation.scala └── compile │ └── src │ ├── main │ └── scala │ │ └── com.scalakata.eval │ │ ├── Security.scala │ │ ├── Model.scala │ │ ├── Eval.scala │ │ └── Compiler.scala │ └── test │ └── scala │ └── com.scalakata.eval │ └── CompilerSpec.scala ├── Frontend ├── project │ ├── build.properties │ └── plugins.sbt ├── publish.sh ├── .gitignore ├── web │ ├── assets │ │ └── favicon.ico │ ├── app.js │ ├── services │ │ ├── katas.js │ │ ├── scalaEval.js │ │ ├── wrap.js │ │ ├── webcam.js │ │ ├── autocomplete.js │ │ ├── errorsRenderer.js │ │ └── insightRenderer.js │ ├── index.html │ └── controllers │ │ └── code.js ├── styles │ ├── main.less │ ├── codemirror.less │ ├── theme.less │ ├── solarized.less │ └── layout.less ├── video │ ├── kinect.sh │ └── record.sh ├── README.md ├── plugins │ ├── install.js │ └── run.js ├── build.sbt ├── cert.pem ├── key.pem ├── bower.json ├── package.json ├── LICENSE └── gulpfile.js ├── Examples ├── README.md ├── To.png ├── From.png ├── Desugaring.png ├── Instructor.png └── TypesafeDocumentation.png ├── README.md ├── travis ├── bintray.template └── bintray.sh ├── tb.scala ├── publish.sh └── .travis.yml /Plugin/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Issues/ 3 | -------------------------------------------------------------------------------- /Backend/src/test/resources/a/1.scala: -------------------------------------------------------------------------------- 1 | scala 2 | -------------------------------------------------------------------------------- /Backend/src/test/resources/assets/b/main.css: -------------------------------------------------------------------------------- 1 | css 2 | -------------------------------------------------------------------------------- /Plugin/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /Backend/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 2 | -------------------------------------------------------------------------------- /Backend/src/test/resources/assets/index.html: -------------------------------------------------------------------------------- 1 | index 2 | -------------------------------------------------------------------------------- /Eval/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.5 2 | -------------------------------------------------------------------------------- /Frontend/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 2 | -------------------------------------------------------------------------------- /Frontend/publish.sh: -------------------------------------------------------------------------------- 1 | rm -rf out/ 2 | mkdir out 3 | sbt publish 4 | -------------------------------------------------------------------------------- /Examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | See https://github.com/ScalaKata 4 | -------------------------------------------------------------------------------- /Plugin/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("me.lessis" % "bintray-sbt" % "0.1.2") -------------------------------------------------------------------------------- /Frontend/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("me.lessis" % "bintray-sbt" % "0.1.2") -------------------------------------------------------------------------------- /Examples/To.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MasseGuillaume/ScalaKata/HEAD/Examples/To.png -------------------------------------------------------------------------------- /Examples/From.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MasseGuillaume/ScalaKata/HEAD/Examples/From.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [Moved to MasseGuillaume/ScalaKata2](https://github.com/MasseGuillaume/ScalaKata2) 2 | -------------------------------------------------------------------------------- /Examples/Desugaring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MasseGuillaume/ScalaKata/HEAD/Examples/Desugaring.png -------------------------------------------------------------------------------- /Examples/Instructor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MasseGuillaume/ScalaKata/HEAD/Examples/Instructor.png -------------------------------------------------------------------------------- /travis/bintray.template: -------------------------------------------------------------------------------- 1 | realm = Bintray API Realm 2 | host = api.bintray.com 3 | user = $BT_USER 4 | password = $BT_KEY -------------------------------------------------------------------------------- /Frontend/.gitignore: -------------------------------------------------------------------------------- 1 | web/SourceCodePro 2 | out/ 3 | node_modules/ 4 | bower_components/ 5 | packages/ 6 | dist/ 7 | tmp/ 8 | -------------------------------------------------------------------------------- /Frontend/web/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MasseGuillaume/ScalaKata/HEAD/Frontend/web/assets/favicon.ico -------------------------------------------------------------------------------- /Examples/TypesafeDocumentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MasseGuillaume/ScalaKata/HEAD/Examples/TypesafeDocumentation.png -------------------------------------------------------------------------------- /travis/bintray.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mkdir -p ~/.bintray 3 | eval "echo \"$(< ./travis/bintray.template)\"" > ~/.bintray/.credentials -------------------------------------------------------------------------------- /Frontend/styles/main.less: -------------------------------------------------------------------------------- 1 | @import 'solarized.less'; 2 | @import 'codemirror.less'; 3 | @import 'layout.less'; 4 | @import 'theme.less'; -------------------------------------------------------------------------------- /Backend/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("io.spray" % "sbt-revolver" % "0.7.2") 2 | 3 | addSbtPlugin("me.lessis" % "bintray-sbt" % "0.1.2") -------------------------------------------------------------------------------- /Eval/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.3.2") 2 | 3 | addSbtPlugin("me.lessis" % "bintray-sbt" % "0.1.2") -------------------------------------------------------------------------------- /Eval/classPathtest/src/main/scala/com.example.test/Testing.scala: -------------------------------------------------------------------------------- 1 | package com.example.test 2 | 3 | object Testing { 4 | val onetwothree = 123 5 | } -------------------------------------------------------------------------------- /Frontend/web/app.js: -------------------------------------------------------------------------------- 1 | window.app = angular.module('ScalaKata', ['ui.codemirror', 'ngRoute']); 2 | 3 | app.constant('LANGUAGE', 'scala'). 4 | constant('VERSION', '0.12.0'); 5 | -------------------------------------------------------------------------------- /Frontend/web/services/katas.js: -------------------------------------------------------------------------------- 1 | app.factory("katas",["$http", function($http) { 2 | return function(path){ 3 | return $http.get("/kata/scala" + path + ".scala"); 4 | }; 5 | }]); 6 | -------------------------------------------------------------------------------- /tb.scala: -------------------------------------------------------------------------------- 1 | import scala.reflect.runtime.{currentMirror ⇒ cm} 2 | import scala.reflect.runtime.universe._ 3 | import scala.tools.reflect.ToolBox 4 | val tb = cm.mkToolBox() 5 | 6 | tb.parse("1+1") 7 | -------------------------------------------------------------------------------- /Frontend/video/kinect.sh: -------------------------------------------------------------------------------- 1 | sudo modprobe v4l2loopback 2 | 3 | gst-launch -v v4l2src device=/dev/video2 ! \ 4 | ffmpegcolorspace ! \ 5 | video/x-raw-rgb ! \ 6 | ffmpegcolorspace ! \ 7 | video/x-raw-yuv,format=\(fourcc\)YUY2 ! \ 8 | v4l2sink device=/dev/video1 9 | -------------------------------------------------------------------------------- /Plugin/README.md: -------------------------------------------------------------------------------- 1 | ## Publish Docker Image 2 | 3 | seq(kataDockerSettings: _*) 4 | 5 | ``` 6 | sbt kata:docker // << error here 7 | cd target/docker 8 | sudo docker build -t="masseguillaume/scalakata:0.8.0" . 9 | sudo docker push masseguillaume/scalakata:0.8.0 10 | ``` 11 | -------------------------------------------------------------------------------- /Frontend/README.md: -------------------------------------------------------------------------------- 1 | # ScalaKata 2 | 3 | install node (http://nodejs.org/download/) 4 | install bower & gulp (```sudo npm install -g bower gulp```) 5 | 6 | install Adobe Font Development Kit for OpenType 7 | (http://download.macromedia.com/pub/developer/opentype/FDK-25-LINUX.zip) 8 | -------------------------------------------------------------------------------- /Frontend/plugins/install.js: -------------------------------------------------------------------------------- 1 | var run = require('./run.js'), 2 | es = require('event-stream'); 3 | 4 | exports.bower = function(){ 5 | return es.map(function(){ 6 | return run("bower", ["install"]); 7 | }); 8 | } 9 | exports.npm = function(){ 10 | return es.map(function(){ 11 | return run("npm", ["install"]); 12 | }); 13 | } -------------------------------------------------------------------------------- /Frontend/video/record.sh: -------------------------------------------------------------------------------- 1 | rm -f output.mp4 && 2 | play -n synth pl G2 pl B2 pl D3 pl G3 pl D4 pl G4 \ 3 | delay 0 .05 .1 .15 .2 .25 remix - fade 0 4 .1 norm -1 && \ 4 | ffmpeg \ 5 | -s 1366x768 -f x11grab -i :0.0 \ 6 | -f alsa -i pulse \ 7 | -vcodec libx264 -b 3000k \ 8 | -acodec aac -strict experimental \ 9 | -af lowpass=f=5000 \ 10 | -s hd720 -ab 320k -r 25 -g 25 -threads 0 output.mp4 && 11 | mplayer output.mp4 12 | -------------------------------------------------------------------------------- /Frontend/web/services/scalaEval.js: -------------------------------------------------------------------------------- 1 | app.factory("scalaEval",["$http", function($http) { 2 | return { 3 | "insight": function(code){ 4 | return $http.post("/eval", {"code": code}); 5 | }, 6 | "autocomplete": function(code, position){ 7 | return $http.post("/completion", {"code": code, "position": position}); 8 | }, 9 | "typeAt": function(code, position){ 10 | return $http.post("/typeAt", {"code": code, "position": position}); 11 | } 12 | }; 13 | }]); 14 | -------------------------------------------------------------------------------- /Eval/macro/src/test/scala/com.scalakata.eval/InstrumentationSpec.scala: -------------------------------------------------------------------------------- 1 | package com.scalakata.eval 2 | 3 | import scala.collection.immutable.Queue 4 | 5 | import org.specs2._ 6 | 7 | class InstrumentationSpecs extends Specification { def is = s2""" 8 | imports $imports 9 | """ 10 | def imports = { 11 | @ScalaKata object P { 12 | import scala.collection.mutable.Stack 13 | case class VV(a: Stack[Int]) 14 | VV(Stack(1)) 15 | } 16 | P.eval$() must not be empty 17 | } 18 | } -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | # I know this is so stupid 2 | 3 | cd Frontend && \ 4 | ./publish.sh && \ 5 | cd .. && \ 6 | cd Eval && \ 7 | sbt publish && \ 8 | cd .. && \ 9 | cd Backend && \ 10 | sbt publish && \ 11 | cd .. && \ 12 | cd Plugin && \ 13 | sbt publish 14 | 15 | # cd Frontend && \ 16 | # sbt publish && \ 17 | # cd .. && \ 18 | # cd Eval && \ 19 | # sbt publish && \ 20 | # cd .. && \ 21 | # cd Backend && \ 22 | # sbt publish && \ 23 | # cd .. && \ 24 | # cd Plugin && \ 25 | # sbt publish 26 | -------------------------------------------------------------------------------- /Frontend/plugins/run.js: -------------------------------------------------------------------------------- 1 | var spawn = require('child_process').spawn, 2 | gutil = require('gulp-util'); 3 | 4 | module.exports = function (command, args){ 5 | var child = spawn(command, args, {cwd: process.cwd()}); 6 | 7 | child.stdout.setEncoding('utf8'); 8 | child.stdout.on('data', function (data) { 9 | gutil.log(data); 10 | }); 11 | 12 | child.stderr.setEncoding('utf8'); 13 | child.stderr.on('data', function (data) { 14 | gutil.log(gutil.colors.red(data)); 15 | gutil.beep(); 16 | }); 17 | 18 | child.on('close', function(code) { 19 | gutil.log("Done with exit code", code); 20 | }); 21 | } -------------------------------------------------------------------------------- /Plugin/build.sbt: -------------------------------------------------------------------------------- 1 | import bintray.Keys._ 2 | 3 | sbtPlugin := true 4 | 5 | name := "plugin" 6 | 7 | organization := "com.scalakata" 8 | 9 | version := "0.12.0" 10 | 11 | addSbtPlugin("io.spray" % "sbt-revolver" % "0.7.2") 12 | 13 | addSbtPlugin("se.marcuslonnberg" % "sbt-docker" % "0.5.2") 14 | 15 | licenses := Seq("MIT" -> url("http://www.opensource.org/licenses/mit-license.html")) 16 | 17 | seq(bintraySettings:_*) 18 | 19 | repository in bintray := "sbt-plugins" 20 | 21 | bintrayOrganization in bintray := None 22 | 23 | scalaVersion := "2.10.4" 24 | 25 | scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") 26 | 27 | homepage := Some(url("http://scalakata.com")) -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | before_script: ./publish/bintray.sh 3 | script: sbt ++${TRAVIS_SCALA_VERSION} test publishTravis 4 | 5 | scala: 6 | - 2.11.6 7 | jdk: 8 | - openjdk7 9 | - oraclejdk7 10 | - oraclejdk8 11 | notifications: 12 | email: 13 | - masgui@gmail.com 14 | env: 15 | global: 16 | - secure: "HsBKxCc1SydicZr8GtI2oifOSvGdkVhPiIpkpbV1OKLvtNhbWLb+6fAJfQGF8y7FwUFHRw79tlcVqC9bazOEl6q+Nn86UuhS1Xn3ijtQ+wYSt+Xbvxk/Dmok90xUsAFuHircl386rQQn1eLbB00Ock7q7EZj6ZT1tdaZNgsxUaU=" 17 | - secure: "IRxUaifbCvhCZywU/Oo1AlXjaRhfATqX4+P3/SiuZl57ps7kjYfcXx7kcPWLTkPV5pkr/rJw9Aqgj1PKl83DdZ8AKP3FcIc51ul8AMk8sqm03fClBYwMKZ5wAcMNgJhnNcBKFrjU6Rsfni/mvCVKlOQRrfCpscSEmblKEZ/hbow=" -------------------------------------------------------------------------------- /Frontend/build.sbt: -------------------------------------------------------------------------------- 1 | name := "frontend" 2 | 3 | organization := "com.scalakata" 4 | 5 | version := "0.12.0" 6 | 7 | autoScalaLibrary := false 8 | 9 | scalaVersion := "2.11.6" 10 | 11 | resourceDirectory in Compile := { 12 | baseDirectory.value / "out" 13 | } 14 | 15 | resourceGenerators in Compile += Def.task { 16 | "gulp build" ! streams.value.log 17 | ((resourceDirectory in Compile).value ***).get 18 | /*Seq.empty[java.io.File]*/ 19 | }.taskValue 20 | 21 | publishArtifact in (Compile, packageDoc) := false 22 | 23 | publishArtifact in (Compile, packageSrc) := false 24 | 25 | crossPaths := false 26 | 27 | licenses := Seq("MIT" -> url("http://www.opensource.org/licenses/mit-license.html")) 28 | 29 | seq(bintraySettings:_*) 30 | -------------------------------------------------------------------------------- /Frontend/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICDTCCAXYCCQCrZvZIoAqNDzANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJj 3 | YTELMAkGA1UECAwCcWMxETAPBgNVBAcMCE1vbnRyZWFsMRwwGgYDVQQKDBNEZWZh 4 | dWx0IENvbXBhbnkgTHRkMB4XDTE0MDkwMTIxMDQyMFoXDTQyMDExNjIxMDQyMFow 5 | SzELMAkGA1UEBhMCY2ExCzAJBgNVBAgMAnFjMREwDwYDVQQHDAhNb250cmVhbDEc 6 | MBoGA1UECgwTRGVmYXVsdCBDb21wYW55IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOB 7 | jQAwgYkCgYEA5ElXk+PrM5MWQo5l+3TeL7RMQCBUsQapqiwQTwljtv9gkIwYBXkp 8 | GScBl2PlYdvzlQJNerz58whzrysa0rznxgHbtSHKJgF1PURfPtHLNrBZWrc86+9u 9 | e4SwIvsVQ8jsVDqtM/OdMSL9v37eqRo3KXF/z/pUs6/SL9We49M5LHcCAwEAATAN 10 | BgkqhkiG9w0BAQUFAAOBgQClvan5abSP1IP8og0zJqj6ssqJjIjpUQQUFhgBvWTU 11 | lJFwLN3ukb8ci7JGwyRfegf4Lz7GaPXFLDz3QB618he+Itp+da/NWTXGPIUSSzRy 12 | +QftA1ELCSVhgww2/mBfmxzUkdhdsCyD9qd9ARLjRmcXnsZ/vJgI//9HxoEaLDQv 13 | xg== 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /Eval/compile/src/main/scala/com.scalakata.eval/Security.scala: -------------------------------------------------------------------------------- 1 | package com.scalakata.eval 2 | 3 | import java.security._ 4 | import java.io._ 5 | 6 | object Security { 7 | val read1 = new java.io.FilePermission("../-", "read") 8 | val read2 = new java.io.FilePermission("../.", "read") 9 | val other = new java.net.NetPermission("specifyStreamHandler") 10 | 11 | class SecurityPolicy extends Policy { 12 | override def implies(domain: ProtectionDomain, permission: Permission) = { 13 | // not in eval 14 | Thread.currentThread().getStackTrace().find(_.getFileName == "(inline)").isEmpty || 15 | read1.implies(permission) || 16 | read2.implies(permission) || 17 | other.implies(permission) 18 | } 19 | } 20 | 21 | def start: Unit = { 22 | Policy.setPolicy(new SecurityPolicy) 23 | System.setSecurityManager(new SecurityManager) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Frontend/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXgIBAAKBgQDkSVeT4+szkxZCjmX7dN4vtExAIFSxBqmqLBBPCWO2/2CQjBgF 3 | eSkZJwGXY+Vh2/OVAk16vPnzCHOvKxrSvOfGAdu1IcomAXU9RF8+0cs2sFlatzzr 4 | 7257hLAi+xVDyOxUOq0z850xIv2/ft6pGjcpcX/P+lSzr9Iv1Z7j0zksdwIDAQAB 5 | AoGBALLay2SxAwtrR9tpWXlDPPi/F6Z+LhxsDe2njDeAMcGkH3HgcMmI/awZJWQI 6 | /iRHgXXTUgTBnkrM6mXpLaDCJAtBGe4bzPduarygz+o+VDanVwNs2ycORBqNoSnb 7 | 3CSxSWGcbCeAz4NmiDhN8qTqOse77xVkZMaA/uOKEV6muTZhAkEA/X6b9BkkdRj3 8 | kIYp/JMs85f34Xjvis0ymD3vXAg32lbrHOUXoXngAodhUwQtP4YnaS8IIdSWlBhN 9 | BW/H/pFtqQJBAOaK85jUnktdbHv0Y2M920Wlj8R976RxN8ON2vgOoRwDgxz5o8ee 10 | LO4HMNHdhVU2rUzvj62xEdJWouaTW/ei3R8CQDzU2DnwmqYbErW07Hh3HQ9rxYlv 11 | CxTk/yI3oqwUMMsNlqg9dblDVPVmZX2VgAJBgE2mgq4TpPrvBfSrCXRWk9kCQQDQ 12 | 5QD671ULZvigjH3t86OWxQCzA4W8FBZ6LDRH5kGXd5s7CvybKLj/aSaUUsu4vdVj 13 | zAH9WV+Tnor2sHl5tmnzAkEAhl0cHvqgKWNuhpQ+8yzmqpNsQDlbqZCFSvXrP06+ 14 | VVs+yWhOCF4/oqTZJhvL4UnxVotj46SGplcQ9RM4cFzPng== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /Frontend/web/services/wrap.js: -------------------------------------------------------------------------------- 1 | app.factory("wrap", function() { 2 | return function (code, insight){ 3 | var import_ = "import com.scalakata.eval._", 4 | macroBegin = "@ScalaKata object $Playground {", 5 | macroEnd = "}", 6 | presentationBegin = "object $Playground {", 7 | presentationEnd = "}", 8 | nl = "\n", 9 | full = insight ? 10 | [import_, macroBegin, code, macroEnd].join(nl) 11 | : [import_, presentationBegin, code, presentationEnd].join(nl) 12 | 13 | return { 14 | codeOffset: ([import_, presentationBegin].join(nl).length), 15 | fixRange: function(range) { 16 | return insight ? 17 | range - ([import_, macroBegin].join(nl).length) 18 | : range - ([import_, presentationBegin].join(nl).length) 19 | }, 20 | fixLine: function(line) { 21 | return line - 2 22 | }, 23 | full: full 24 | } 25 | }; 26 | }); 27 | -------------------------------------------------------------------------------- /Frontend/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ScalaKata", 3 | "version": "0.12.0", 4 | "homepage": "", 5 | "authors": [ 6 | "Guillaume Massé" 7 | ], 8 | "description": "ScalaKata", 9 | "dependencies": { 10 | "lodash": "~2.4.1", 11 | "angular": "~1.2.22", 12 | "angular-route": "~1.2.22", 13 | "jquery": "~2.1.0", 14 | "marked": "~0.3.2", 15 | "d3": "~3.4.12", 16 | "octicons": "~2.1.2", 17 | "fontawesome": "~4.1.0", 18 | "getusermedia": "git://github.com/MasseGuillaume/getUserMedia.js.git#1bd2c73b1dbd7b5cec3c1f337e8d9398d10483d7", 19 | "screenfull": "~1.2.1", 20 | "codemirror": "~4.12.0", 21 | "angular-ui-codemirror": "~0.1.6", 22 | "zeroclipboard": "~2.1.6" 23 | }, 24 | "keywords": [], 25 | "license": "MIT", 26 | "ignore": [ 27 | "**/.*", 28 | "node_modules", 29 | "bower_components", 30 | "app/bower_components", 31 | "test", 32 | "tests" 33 | ], 34 | "resolutions": { 35 | "angular": "1.2.28", 36 | "codemirror": "~4.12.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Backend/build.sbt: -------------------------------------------------------------------------------- 1 | name := "backend" 2 | 3 | organization := "com.scalakata" 4 | 5 | version := "0.12.0" 6 | 7 | scalaVersion := "2.11.6" 8 | 9 | Revolver.settings 10 | 11 | resolvers ++= Seq( 12 | "spray repo" at "http://repo.spray.io", 13 | "typesafe releases" at "http://repo.typesafe.com/typesafe/releases", 14 | "masseguillaume" at "http://dl.bintray.com/content/masseguillaume/maven" 15 | ) 16 | 17 | libraryDependencies ++= Seq( 18 | "com.scalakata" %% "eval" % version.value, 19 | "com.scalakata" % "frontend" % "0.10.0", 20 | "io.spray" %% "spray-can" % "1.3.1", 21 | "io.spray" %% "spray-routing" % "1.3.1", 22 | "io.spray" %% "spray-testkit" % "1.3.1" % "test", 23 | "org.specs2" %% s"specs2" % "2.3.12" % "test", 24 | "com.typesafe.akka" %% "akka-actor" % "2.3.3", 25 | "com.typesafe.play" %% "play-json" % "2.4.0-M1" 26 | ) 27 | 28 | licenses := Seq("MIT" -> url("http://www.opensource.org/licenses/mit-license.html")) 29 | 30 | seq(bintraySettings:_*) 31 | 32 | scalacOptions ++= Seq("-Yrangepos", "-unchecked", "-deprecation", "-feature") 33 | -------------------------------------------------------------------------------- /Frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ScalaKata", 3 | "version": "0.12.0", 4 | "dependencies": {}, 5 | "description": "ScalaKata", 6 | "repository": "https://github.com/MasseGuillaume/ScalaMirror", 7 | "devDependencies": { 8 | "gulp": "~3.6.0", 9 | "gulp-plumber": "^0.6.6", 10 | "gulp-concat": "~2.1.7", 11 | "express": "~3.5.0", 12 | "event-stream": "~3.0.18", 13 | "gulp-less": "~1.2.3", 14 | "connect-livereload": "~0.3.2", 15 | "gulp-cached": "~0.0.3", 16 | "gulp-livereload": "~2.1.1", 17 | "gulp-minify-css": "~0.3.0", 18 | "gulp-uglify": "~0.2.1", 19 | "gulp-util": "~2.2.14", 20 | "gulp-filter": "~1.0.0", 21 | "gulp-autoprefixer": "0.0.6", 22 | "gulp-rename": "~1.1.0", 23 | "gulp-spawn": "~0.3.0", 24 | "gulp-usemin": "~0.3.1", 25 | "gulp-uglify": "~0.3.1", 26 | "gulp-minify-html": "~0.1.4", 27 | "gulp-minify-css": "~0.3.5", 28 | "gulp-rev": "~0.4.1", 29 | "request": "~2.36.0", 30 | "tiny-lr": "0.1.1" 31 | }, 32 | "engines": { 33 | "node": "0.10.x" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Eval/compile/src/main/scala/com.scalakata.eval/Model.scala: -------------------------------------------------------------------------------- 1 | package com.scalakata.eval 2 | 3 | sealed trait Severity 4 | final case object Info extends Severity 5 | final case object Warning extends Severity 6 | final case object Error extends Severity 7 | 8 | case class CompilationInfo( 9 | message: String, 10 | start: Int, 11 | end: Int 12 | ) 13 | 14 | case class RuntimeError( 15 | message: String, 16 | line: Int 17 | ) 18 | 19 | case class EvalRequest( 20 | code: String 21 | ) 22 | 23 | case class EvalResponse( 24 | insight: OrderedRender, 25 | infos: Map[Severity, List[CompilationInfo]], 26 | timeout: Boolean, 27 | runtimeError: Option[RuntimeError] 28 | ) 29 | 30 | object EvalResponse { 31 | def empty = EvalResponse(Nil, Map.empty, false, None) 32 | } 33 | 34 | case class TypeAtRequest( 35 | code: String, 36 | position: Int 37 | ) 38 | 39 | case class TypeAtResponse( 40 | tpe: String 41 | ) 42 | 43 | case class CompletionRequest( 44 | code: String, 45 | position: Int 46 | ) 47 | 48 | case class CompletionResponse( 49 | name: String, 50 | signature: String 51 | ) 52 | -------------------------------------------------------------------------------- /Frontend/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Guillaume Massé 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Frontend/styles/codemirror.less: -------------------------------------------------------------------------------- 1 | .code .CodeMirror { 2 | background: transparent; 3 | } 4 | 5 | .CodeMirror-gutters { 6 | width: 30px; 7 | } 8 | 9 | .CodeMirror-linewidget { 10 | overflow: hidden; 11 | } 12 | 13 | .CodeMirror, .CodeMirror-scroll { 14 | height: 100%; 15 | } 16 | 17 | .CodeMirror-lines { 18 | padding: 16px 0; 19 | } 20 | 21 | .CodeMirror-code pre { 22 | padding: 0 16px; 23 | line-height: 21px; 24 | } 25 | 26 | .CodeMirror { 27 | font-size: 18px; 28 | } 29 | 30 | .CodeMirror-hints { 31 | resize: horizontal; 32 | .autocomplete { 33 | width: calc("80%"); 34 | } 35 | z-index: 10; 36 | } 37 | 38 | .CodeMirror-hint { 39 | max-width: none; 40 | resize: none; 41 | 42 | .autocomplete-result-name{ 43 | } 44 | .autocomplete-result-signature{ 45 | opacity:0.5; 46 | } 47 | } 48 | 49 | .CodeMirror-dialog.CodeMirror-dialog-top { 50 | border: none; 51 | margin-left: 56px; // gutter size 52 | line-height: 21px; 53 | 54 | padding-left: 2px; 55 | padding-right: 0; 56 | span { 57 | display: none; 58 | } 59 | input { 60 | font-size: 18px; 61 | padding: 0; 62 | font-family: "Source Code Pro Regular"; 63 | width: calc(~"100% - "150px) !important; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Eval/macro/src/main/scala/com.scalakata.eval/list: -------------------------------------------------------------------------------- 1 | q"" EmptyTree 2 | q"$value" Literal 3 | q"name" Ident 4 | q"$expr.$tname" Select 5 | q"$tpname.super[$tpname].$tname" Select 6 | q"$tpname.this" This 7 | q"$expr(...$exprss)" Apply 8 | q"$expr[..$tpts]" TypeApply 9 | 10 | q"$expr(..$exprs) = $expr" Tree 11 | 12 | q"throw $expr" Throw 13 | 14 | q"(..$exprs)" Tree 15 | q"{ ..$stats }" Block 16 | q"if ($expr) $expr else $expr" If 17 | q"$expr match { case ..$cases }" Match 18 | q"try $expr catch { case ..$cases } finally $expr" Try 19 | q"(..$params) ⇒ $expr" Function 20 | 21 | q"while ($expr) $expr" LabelDef 22 | q"do $expr while ($expr)" LabelDef 23 | q"for (..$enums) $expr" Tree 24 | q"for (..$enums) yield $expr" Tree 25 | q"new { ..$earlydefns } with ..$parents { $self ⇒ ..$stats }" Tree 26 | 27 | Definitions 28 | 29 | q"$mods val $pat = $expr" 30 | q"$mods var $tname: $tpt = $expr" 31 | 32 | q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" 33 | q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self ⇒ ..$stats }" 34 | q"$mods trait $tpname[..$tparams] extends { ..$earlydefns } with ..$parents { $self ⇒ ..$stats }" 35 | q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self ⇒ ..$body }" 36 | 37 | 38 | q"package $ref { ..$topstats }" !!! Not supported 39 | q"package object $tname extends { ..$earlydefns } with ..$parents { $self ⇒ ..$stats }" !!! Not suported 40 | -------------------------------------------------------------------------------- /Backend/src/main/scala/com.scalakata.backend/Server.scala: -------------------------------------------------------------------------------- 1 | package com.scalakata.backend 2 | 3 | import com.scalakata.eval._ 4 | 5 | import akka.actor.{ActorSystem, Props} 6 | import akka.io.IO 7 | import akka.util.Timeout 8 | import spray.can.Http 9 | 10 | import scala.concurrent.duration._ 11 | import com.typesafe.config.{ ConfigValueFactory, ConfigFactory, Config } 12 | 13 | 14 | object Boot { 15 | def main(args: Array[String]) = { 16 | val (readyPort :: artifacts :: host :: port :: 17 | production :: security :: timeoutS :: scalacOptions) = args.to[List] 18 | 19 | val timeout = Duration(timeoutS) 20 | 21 | val config: Config = ConfigFactory.parseString(s""" 22 | spray { 23 | can.server { 24 | idle-timeout = ${timeout.toSeconds + 5}s 25 | request-timeout = ${timeout.toSeconds + 2}s 26 | } 27 | } 28 | """) 29 | 30 | implicit val system = ActorSystem("scalakata-playground", config) 31 | 32 | val service = system.actorOf(Props( 33 | classOf[ScalaKataActor], artifacts, scalacOptions, security.toBoolean, timeout 34 | ), "scalakata-service") 35 | 36 | 37 | import akka.pattern.ask 38 | implicit val bindingTimeout = Timeout(5.seconds) 39 | import system.dispatcher 40 | (IO(Http) ? Http.Bind(service, host, port.toInt)) onSuccess { 41 | case _: Http.Bound ⇒ { 42 | if(!production.toBoolean) { 43 | val ready = new java.net.Socket(host, readyPort.toInt) 44 | ready.sendUrgentData(0) 45 | ready.close() 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Backend/src/test/scala/com.scalakata.backend/RestSpec.scala: -------------------------------------------------------------------------------- 1 | package com.scalakata 2 | package backend 3 | 4 | import org.specs2._ 5 | import spray.testkit.Specs2RouteTest 6 | import spray.http.StatusCodes._ 7 | import spray.http._ 8 | 9 | class RestSpec extends Specification with Specs2RouteTest with ScalaKata { def is = s2""" 10 | $assets must work 11 | $assetRoute must handle querystrings 12 | $resourceRoute must redirect resources to index 13 | $index must work 14 | $echo must allow sandboxed xss 15 | """ 16 | def actorRefFactory = system 17 | val artifacts: String = "" 18 | val code: String = "" 19 | val codePrelude: String = "" 20 | val scalacOptions: Seq[String] = Seq() 21 | val security = false 22 | 23 | def assets = { 24 | Get("/assets/b/main.css") ~> route ~> check { 25 | responseAs[String] must contain("css") 26 | } 27 | } 28 | 29 | def assetRoute = { 30 | Get("/assets/MathJax/MathJax.js?config=TeX-AMS-MML_HTMLorMML&delayStartupUntil=configured&dummy=.js") ~> route ~> check { 31 | status mustEqual OK 32 | } 33 | } 34 | 35 | def resourceRoute = { 36 | Get("/a/1.scala") ~> route ~> check { 37 | responseAs[String] must contain("index") 38 | } 39 | } 40 | 41 | def index = { 42 | Get("/") ~> route ~> check { 43 | responseAs[String] must contain("index") 44 | } 45 | } 46 | 47 | def echo = { 48 | Post("/echo", FormData(Seq("code"->"code"))) ~> route ~> check { 49 | headers must contain(HttpHeaders.RawHeader("X-XSS-Protection", "0")) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Frontend/web/services/webcam.js: -------------------------------------------------------------------------------- 1 | app.factory("webcam", ["$q", function($q) { 2 | // mapping: cameraId -> webcam | background 3 | return function(mapping){ 4 | var newMapping = $q.defer(); 5 | 6 | MediaStreamTrack.getSources(function(sources){ 7 | var camIds, 8 | requests, 9 | options = { 10 | audio: false, 11 | video: true, 12 | el: "_", 13 | extern: null, 14 | append: true, 15 | width: 0, 16 | height: 0, 17 | mode: "callback", 18 | onCapture: function () { 19 | window.webcam.save(); // ??? 20 | } 21 | }; 22 | 23 | camIds = 24 | _.chain(sources).filter(function(e){ 25 | return e.kind === 'video'; 26 | }).map(function(e){ 27 | return e.id; 28 | }).value(); 29 | 30 | if(!angular.isDefined(mapping)){ 31 | mapping = mapping || _.zipObject(camIds, ["webcam", "background"]); 32 | } 33 | newMapping.resolve(mapping); 34 | 35 | requests = _.map(mapping, function(element, camId){ 36 | var ops = angular.copy(options); 37 | ops.video = { 38 | mandatory: { 39 | sourceId: camId 40 | } 41 | }; 42 | ops.el = element; 43 | 44 | return ops; 45 | }); 46 | 47 | _.forEach(requests, function(op){ 48 | getUserMedia(op, function(stream){ 49 | var video = op.videoEl; 50 | 51 | var vendorURL = window.URL || window.webkitURL; 52 | video.src = vendorURL ? vendorURL.createObjectURL(stream) : stream; 53 | 54 | video.onerror = function () { 55 | stream.stop(); 56 | streamError(); 57 | }; 58 | 59 | }, function(){ 60 | console.log("error"); 61 | }); 62 | }); 63 | }); 64 | 65 | return newMapping.promise; 66 | }; 67 | }]); 68 | -------------------------------------------------------------------------------- /Eval/project/Build.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | import sbtbuildinfo.Plugin._ 4 | 5 | object Settings { 6 | lazy val default = 7 | Project.defaultSettings ++ 8 | bintray.Plugin.bintraySettings ++ 9 | Seq( 10 | organization := "com.scalakata", 11 | scalaVersion := "2.11.6", 12 | version := "0.12.0", 13 | licenses := Seq("MIT" -> url("http://www.opensource.org/licenses/mit-license.html")), 14 | scalacOptions ++= Seq("-Yrangepos", "-unchecked", "-deprecation", "-feature"), 15 | libraryDependencies ++= Seq( 16 | "org.scala-lang" % "scala-compiler" % scalaVersion.value, 17 | "org.scala-lang" % "scala-reflect" % scalaVersion.value, 18 | "org.specs2" %% "specs2-core" % "3.1" % "test" 19 | ), 20 | resolvers += Resolver.sonatypeRepo("releases"), 21 | addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0-M5" cross CrossVersion.full) 22 | ) 23 | } 24 | 25 | object EvalBuild extends Build { 26 | import Settings._ 27 | 28 | lazy val macro = Project( 29 | id = "macro", 30 | base = file("macro"), 31 | settings = default ++ Seq( 32 | name := "macro" 33 | ) 34 | ) 35 | 36 | lazy val classPathtest = Project( 37 | id = "classpathTest", 38 | base = file("classPathtest"), 39 | settings = default ++ Seq( 40 | /* dont */ publish := { }, 41 | /* dont */ publishLocal := { } 42 | ) 43 | ) 44 | 45 | lazy val compile = Project( 46 | id = "compile", 47 | base = file("compile"), 48 | settings = default ++ buildInfoSettings ++ Seq( 49 | name := "eval", 50 | sourceGenerators in Test <+= buildInfo, 51 | buildInfoKeys := Seq[BuildInfoKey]( 52 | BuildInfoKey.map((fullClasspath in classPathtest in Compile)){ case (k, v) ⇒ k -> v.map(_.data) }, 53 | BuildInfoKey.map((exportedProducts in Runtime in macro)){ case (k, v) ⇒ k -> v.map(_.data) }, 54 | (scalacOptions in Compile) 55 | ), 56 | buildInfoPackage := "com.scalakata.eval.sbt", 57 | parallelExecution in Test := false 58 | ) 59 | ) dependsOn(macro) 60 | } 61 | -------------------------------------------------------------------------------- /Frontend/styles/theme.less: -------------------------------------------------------------------------------- 1 | .CodeMirror { 2 | ::-webkit-scrollbar { 3 | width: auto; 4 | height: auto; 5 | } 6 | ::-webkit-scrollbar-thumb { 7 | border-radius: 5px; 8 | } 9 | 10 | ::-webkit-scrollbar-thumb { 11 | box-shadow: 0 0 1px grey inset; 12 | } 13 | } 14 | .cm-s-solarized { 15 | .solarized-pallette; 16 | 17 | &.cm-s-dark { 18 | .menu, .clip { 19 | background-color: @base02; 20 | color: @base01; 21 | i:hover, &.active { 22 | color: @base1; 23 | } 24 | } 25 | 26 | .CodeMirror-dialog { 27 | background-color: @base02; 28 | color: @base01; 29 | } 30 | 31 | ::-webkit-scrollbar-corner { 32 | background: @base03; 33 | } 34 | 35 | ::-webkit-scrollbar-thumb { 36 | background: @base02; 37 | box-shadow: 0 0 1px black inset; 38 | } 39 | 40 | .insight { 41 | background-color: @base02; 42 | 43 | &.block .insight { 44 | background: @base03; 45 | } 46 | 47 | hr { 48 | border-color: @base03; 49 | } 50 | } 51 | } 52 | 53 | &.cm-s-light { 54 | .menu, .clip { 55 | background-color: @base2; 56 | color: @base1; 57 | i:hover, &.active { 58 | color: @base01; 59 | } 60 | } 61 | 62 | ::-webkit-scrollbar-corner { 63 | background: @base3; 64 | } 65 | 66 | ::-webkit-scrollbar-thumb { 67 | box-shadow: 0 0 1px grey inset; 68 | } 69 | 70 | .insight { 71 | background-color: @base2; 72 | 73 | &.block .insight { 74 | background: @base3; 75 | } 76 | 77 | hr { 78 | border-color: @base3; 79 | } 80 | } 81 | } 82 | } 83 | .cm-s-mdn-like { 84 | ::-webkit-scrollbar-thumb { 85 | background: #f8f8f8; 86 | box-shadow: 0 0 1px grey inset; 87 | } 88 | 89 | .menu, .clip { 90 | border: 1px solid #ddd; 91 | background-color: #f8f8f8; 92 | color: rgba(0,83,159,0.65); 93 | i:hover, &.active { 94 | color: black; 95 | } 96 | } 97 | 98 | .insight { 99 | background-color: #f8f8f8; 100 | border: 1px solid #ddd; 101 | } 102 | } 103 | 104 | .ui-splitbar { 105 | background: rgba(125,125,125,0.2); 106 | } 107 | 108 | .menu, .clip { 109 | color: grey; 110 | i:hover, &.active { 111 | color: green; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Backend/src/main/scala/com.scalakata.backend/ScalaKataActor.scala: -------------------------------------------------------------------------------- 1 | package com.scalakata 2 | package backend 3 | 4 | import eval._ 5 | 6 | import akka.actor._ 7 | import spray.routing.HttpService 8 | import spray.http._ 9 | import spray.util._ 10 | 11 | import scala.concurrent.duration._ 12 | 13 | class ScalaKataActor( 14 | override val artifacts: String, 15 | override val scalacOptions: Seq[String], 16 | override val security: Boolean, 17 | override val timeout: Duration) extends Actor with ScalaKata { 18 | 19 | def actorRefFactory = context 20 | def receive = runRoute(route) 21 | } 22 | 23 | trait ScalaKata extends HttpService { 24 | val artifacts: String 25 | val scalacOptions: Seq[String] 26 | val security: Boolean 27 | val timeout: Duration 28 | 29 | implicit def executionContext = actorRefFactory.dispatcher 30 | 31 | import Request._ 32 | import Response._ 33 | 34 | lazy val compiler = new Compiler(artifacts, scalacOptions, security, timeout) 35 | 36 | val redirectCodebrew = hostName { hn ⇒ 37 | if(hn == "codebrew.io") redirect("www.scalakata.com", StatusCodes.PermanentRedirect) 38 | else getFromResource("assets/index.html") 39 | } 40 | 41 | val route = { 42 | path("eval") { 43 | post { 44 | entity(as[EvalRequest]) { request ⇒ 45 | val EvalRequest(code) = request 46 | complete(compiler.insight(code)) 47 | } 48 | } 49 | } ~ 50 | path("completion") { 51 | post { 52 | entity(as[CompletionRequest]) { request ⇒ 53 | val CompletionRequest(code, pos) = request 54 | complete(compiler.autocomplete(code, pos)) 55 | } 56 | } 57 | } ~ 58 | path("typeAt") { 59 | post { 60 | entity(as[TypeAtRequest]) { request ⇒ 61 | val TypeAtRequest(code, pos) = request 62 | complete(compiler.typeAt(code, pos, pos)) 63 | } 64 | } 65 | } ~ 66 | path("echo") { 67 | post { 68 | formFields('code){ code ⇒ 69 | respondWithHeader(HttpHeaders.RawHeader("X-XSS-Protection", "0")) { 70 | complete(HttpEntity(ContentType(MediaTypes.`text/html`, HttpCharsets.`UTF-8`), 71 | HttpData(code))) 72 | } 73 | } 74 | } 75 | } ~ 76 | pathSingleSlash { 77 | redirectCodebrew 78 | } ~ 79 | path("assets" / Rest) { path ⇒ 80 | getFromResource(s"assets/$path") 81 | } ~ 82 | path("kata" / "scala" / Rest) { path ⇒ 83 | getFromResource(s"scala/$path") 84 | } ~ 85 | path(Rest) { path ⇒ 86 | redirectCodebrew 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Frontend/web/services/autocomplete.js: -------------------------------------------------------------------------------- 1 | app.run(["scalaEval", function(scalaEval){ 2 | // remove select line 3 | delete CodeMirror.keyMap.sublime["Ctrl-L"]; 4 | 5 | function hint(cm, sf, cf, single){ 6 | var wrap = CodeMirror.hack.wrap( 7 | CodeMirror.hack.code.getDoc().getValue(), 8 | false 9 | ); 10 | 11 | sf( 12 | wrap.full, 13 | cm.getDoc().indexFromPos(cm.getCursor()) + 1 + wrap.codeOffset 14 | ).then(function(r){ 15 | var data = r.data; 16 | 17 | CodeMirror.showHint(cm, function(cm, options){ 18 | var i; 19 | var cur= cm.getCursor(); 20 | var curTo = {"ch" : cur.ch, "line" : cur.line}; 21 | var curFrom = {"ch" : cur.ch, "line" : cur.line}; 22 | 23 | var currentLine = cm.getDoc().getValue().split("\n")[cur.line]; 24 | 25 | function delimiter(c){ 26 | return /^[a-zA-Z0-9\_]$/.test(c); 27 | } 28 | 29 | for (i = cur.ch-1; i >= 0 && delimiter(currentLine[i]); i--){ 30 | curFrom.ch = i; 31 | } 32 | for (i = cur.ch; i < currentLine.length && delimiter(currentLine[i]); i++){ 33 | curTo.ch = i+1; 34 | } 35 | 36 | var term = currentLine.substr(curFrom.ch, curTo.ch - curFrom.ch); 37 | 38 | options.completeSingle = single; 39 | 40 | if(single){ 41 | return {from: curFrom, to: curTo, list: cf(data, term)}; 42 | } else { 43 | curFrom.ch = Math.Infinity; 44 | curTo.ch = Math.Infinity; 45 | return {from: curFrom, to: curTo, list: cf(data)}; 46 | } 47 | 48 | }); 49 | }); 50 | } 51 | 52 | CodeMirror.commands.typeAt = function(cm) { 53 | hint(cm, scalaEval.typeAt, function(data){ 54 | return [ 55 | { 56 | className: "typeAt", 57 | text: " // " + data.tpe, 58 | render: function(el, _, _1){ 59 | var elem = document.createElement("pre"); 60 | elem.innerText = data.tpe; 61 | el.appendChild(elem); 62 | } 63 | } 64 | ]; 65 | }, false); 66 | } 67 | CodeMirror.commands.autocomplete = function(cm) { 68 | hint(cm, scalaEval.autocomplete, function(data, term){ 69 | return data.filter(function(c){ 70 | return c.name.toLowerCase().indexOf(term.toLowerCase()) != -1; 71 | }).map(function(c){ 72 | return { 73 | className: "autocomplete", 74 | text: c.name, 75 | completion: c, 76 | alignWithWord: true, 77 | render: function(el, _, _1){ 78 | el.innerHTML = "" + c.name + " " + c.signature +""; 79 | } 80 | } 81 | }); 82 | }, true); 83 | }; 84 | CodeMirror.commands.autocompleteDot = function (cm){ 85 | cm.replaceSelection("."); 86 | cm.execCommand("autocomplete"); 87 | }; 88 | }]); 89 | -------------------------------------------------------------------------------- /Frontend/web/services/errorsRenderer.js: -------------------------------------------------------------------------------- 1 | app.factory('errorsRenderer', function() { 2 | var insightWidget = []; 3 | var errorMessages = []; 4 | var errorUnderlines = []; 5 | 6 | function errorUnderline(cmCode, wrap, severity, value) { 7 | var start; 8 | function render(from, to){ 9 | return cmCode.markText(from, to, {className: severity} ); 10 | } 11 | if(angular.isDefined(value.start) && angular.isDefined(value.end) && value.start != value.end) { 12 | return render( 13 | wrap.fixRange(value.start), 14 | wrap.fixRange(value.end) 15 | ) 16 | } else { 17 | var l = angular.isDefined(value.line) ? 18 | wrap.fixLine(value.line) 19 | : cmCode.getDoc().posFromIndex(wrap.fixRange(value.start)).line; 20 | 21 | return render( 22 | {line: l, ch: 0}, 23 | {line: l, ch: Infinity} 24 | ); 25 | } 26 | } 27 | 28 | function errorMessage(cmCode, wrap, severity, value){ 29 | var offset; 30 | function render(line){ 31 | var msg = document.createElement("div"), 32 | icon = msg.appendChild(document.createElement("i")); 33 | 34 | icon.className = "fa "; 35 | if(severity == "error") { 36 | icon.className += "fa-times-circle"; 37 | } else if(severity == "warning") { 38 | icon.className += "fa-exclamation-triangle"; 39 | } else if(severity == "info") { 40 | icon.className += "fa-info-circle"; 41 | } 42 | msg.appendChild(document.createTextNode(value.message)); 43 | msg.className = "error-message"; 44 | 45 | return cmCode.addLineWidget(line, msg); 46 | } 47 | 48 | if(angular.isDefined(value.start)) { 49 | offset = value.start; 50 | 51 | console.log(wrap.fixRange(offset).line); 52 | 53 | if(value.start == -1) offset = Infinity; 54 | return render(cmCode.getDoc().posFromIndex(wrap.fixRange(offset)).line); 55 | 56 | } else { 57 | if (value.line !== -1) { 58 | return render( 59 | render(wrap.fixLine(value.line) -1) 60 | ); 61 | } else { 62 | return render(Infinity); 63 | } 64 | } 65 | } 66 | 67 | function clearFun(){ 68 | // clear line errors 69 | errorMessages.forEach(function (value){ 70 | value.clear(); 71 | }); 72 | errorMessages = []; 73 | 74 | errorUnderlines.forEach(function (value){ 75 | value.clear(); 76 | }); 77 | errorUnderlines = []; 78 | } 79 | 80 | return { 81 | clear: clearFun, 82 | render: function(cmCode, wrap, infos, runtimeError){ 83 | clearFun(); 84 | ["error", "warning", "info"].forEach(function(severity){ 85 | if (infos[severity]){ 86 | infos[severity].forEach(function(value) { 87 | errorMessages.push(errorMessage(cmCode, wrap, severity, value)); 88 | errorUnderlines.push(errorUnderline(cmCode, wrap, severity, value)); 89 | }); 90 | } 91 | }); 92 | if(angular.isDefined(runtimeError)) { 93 | var value = runtimeError; 94 | var severity = "runtime-error"; 95 | 96 | errorMessages.push(errorMessage(cmCode, cmPrelude, wrap, severity, value)); 97 | errorUnderlines.push(errorUnderline(cmCode, cmPrelude, wrap, severity, value)); 98 | } 99 | } 100 | } 101 | }); 102 | -------------------------------------------------------------------------------- /Eval/macro/src/main/scala/com.scalakata.eval/package.scala: -------------------------------------------------------------------------------- 1 | package com.scalakata 2 | 3 | import scala.language.experimental.macros 4 | import collection.immutable.Queue 5 | 6 | package object eval { 7 | type Range = (Int, Int) 8 | type OrderedRender = List[(Range, List[Render])] 9 | 10 | sealed trait Render 11 | 12 | sealed trait Expression extends Render 13 | case class EString(v: String) extends Expression 14 | case class Other(repr: String) extends Expression 15 | 16 | case class Block(childs: OrderedRender ) extends Render 17 | case class Steps(simplifications: List[Expression]) extends Render 18 | 19 | case class Markdown(a: String) extends Render { 20 | override def toString = a 21 | def stripMargin = Markdown(a.stripMargin) 22 | } 23 | 24 | case class Markdown2(a: String) extends Render { 25 | override def toString = a 26 | def stripMargin = Markdown(a.stripMargin) 27 | } 28 | 29 | case class Html(a: String, height: Int = 500) extends Render { 30 | override def toString = a 31 | def stripMargin = Html(a.stripMargin) 32 | } 33 | 34 | case class Html2(a: String) extends Render { 35 | override def toString = a 36 | def stripMargin = Html2(a.stripMargin) 37 | } 38 | 39 | def isNotUnit(a: Any) = { 40 | a match { 41 | case _: Unit ⇒ false 42 | case _ ⇒ true 43 | } 44 | } 45 | def render[A >: Null](a: A): Render = { 46 | a match { 47 | case null ⇒ Other("null") 48 | case ar: Array[_] ⇒ Other(ar.deep.toString) 49 | case v: String ⇒ EString(v) 50 | case _: scala.xml.Elem ⇒ Html(a.toString) 51 | case md: Markdown ⇒ md 52 | case md2: Markdown2 ⇒ md2 53 | case h: Html ⇒ h 54 | case h2: Html2 ⇒ h2 55 | case other ⇒ Other(other.toString) 56 | } 57 | } 58 | 59 | def htmlFile(clazz: Class[_], filename: String, size: Int = 500): Html = 60 | Option(clazz.getClassLoader.getResourceAsStream(filename)).map{ res ⇒ 61 | Html(io.Source.fromInputStream(res).mkString, size) 62 | }.getOrElse(Html(s"

$filename not found

", size)) 63 | 64 | implicit class MarkdownHelper(val sc: StringContext) extends AnyVal { 65 | def markdown(args: Any*): Markdown = { 66 | Markdown(sc.s(args: _*)) 67 | } 68 | def md(args: Any*) = markdown(args: _*) 69 | def markdownR(args: Any*): Markdown = { 70 | Markdown(sc.raw(args: _*)) 71 | } 72 | def mdR(args: Any*) = markdownR(args: _*) 73 | } 74 | implicit class MarkdownHelper2(val sc: StringContext) extends AnyVal { 75 | def markdown2(args: Any*): Markdown2 = { 76 | Markdown2(sc.s(args: _*)) 77 | } 78 | def md2(args: Any*) = markdown2(args: _*) 79 | def markdownR2(args: Any*): Markdown2 = { 80 | Markdown2(sc.raw(args: _*)) 81 | } 82 | def mdR2(args: Any*) = markdownR2(args: _*) 83 | } 84 | implicit class HtmlHelper(val sc: StringContext) extends AnyVal { 85 | def html(args: Any*): Html = { 86 | Html(sc.s(args: _*)) 87 | } 88 | def htmlR(args: Any*): Html = { 89 | Html(sc.raw(args: _*)) 90 | } 91 | } 92 | implicit class HtmlHelper2(val sc: StringContext) extends AnyVal { 93 | def html2(args: Any*): Html2 = { 94 | Html2(sc.s(args: _*)) 95 | } 96 | def htmlR2(args: Any*): Html2 = { 97 | Html2(sc.raw(args: _*)) 98 | } 99 | } 100 | 101 | def desugar[T](code: T): Unit = ??? // via annotation macro 102 | 103 | def desugar2[T](code: T): Unit = macro ScalaKataMacro.desugar2_impl[T] 104 | 105 | def trace: Any ⇒ Unit = macro ScalaKataMacro.trace_implf 106 | def print: Any ⇒ Unit = macro ScalaKataMacro.trace_implf 107 | def println: Any ⇒ Unit = macro ScalaKataMacro.trace_implf 108 | def oprintln: Any ⇒ Unit = (a) ⇒ scala.Predef.println(a) 109 | } 110 | -------------------------------------------------------------------------------- /Frontend/styles/solarized.less: -------------------------------------------------------------------------------- 1 | /* 2 | Solarized theme for code-mirror: http://ethanschoonover.com/solarized 3 | Solarized color pallette: http://ethanschoonover.com/solarized/img/solarized-palette.png 4 | less version: gui 5 | */ 6 | 7 | .solarized-pallette { 8 | @base00: #657b83; 9 | @base01: #586e75; 10 | @base02: #073642; 11 | @base03: #002b36; 12 | 13 | @base0: #839496; 14 | @base1: #93a1a1; 15 | @base2: #eee8d5; 16 | @base3: #fdf6e3; 17 | 18 | @yellow: #b58900; 19 | @orange: #cb4b16; 20 | @red: #dc322f; 21 | @magenta: #d33682; 22 | @violet: #6c71c4; 23 | @blue: #268bd2; 24 | @cyan: #2aa198; 25 | @green: #859900; 26 | } 27 | 28 | .cm-s-solarized { 29 | 30 | .solarized-pallette; 31 | 32 | color-profile: sRGB; 33 | rendering-intent: auto; 34 | 35 | .CodeMirror-widget { text-shadow: none; } 36 | 37 | .cm-keyword { color: @orange } 38 | .cm-atom { color: @magenta; } 39 | .cm-number { color: @magenta; } 40 | .cm-def { color: @cyan; } 41 | .cm-variable { color: @blue; } 42 | .cm-variable-2 { color: @yellow; } 43 | .cm-variable-3 { color: @violet; } 44 | .cm-property { color: @cyan; } 45 | .cm-operator {color: @violet;} 46 | .cm-comment { color: @base01 } 47 | .cm-string { color: @green; } 48 | .cm-string-2 { color: @yellow; } 49 | .cm-meta { color: @green; } 50 | .cm-error, .cm-invalidchar { 51 | color: @base01; 52 | border-bottom: 1px dotted @red; 53 | } 54 | .cm-qualifier { color: @yellow; } 55 | .cm-builtin { color: @magenta; } 56 | .cm-bracket { color: @orange; } 57 | .CodeMirror-matchingbracket { color: @green; } 58 | .CodeMirror-nonmatchingbracket { color: @red; } 59 | .cm-tag { color: @base1; } 60 | .cm-attribute { color: @cyan; } 61 | .cm-header { color: @base01; } 62 | .cm-quote { color: @base1; } 63 | .cm-hr { 64 | color: transparent; 65 | border-top: 1px solid @base01; 66 | display: block; 67 | } 68 | .cm-link { color: @base1; cursor: pointer; } 69 | .cm-special { color: @violet; } 70 | .cm-em { 71 | color: #999; 72 | text-decoration: underline; 73 | text-decoration-style: dotted; 74 | } 75 | .cm-strong { color: #eee; } 76 | .cm-tab:before { color: @base01; } 77 | 78 | .cm-matchhighlight { 79 | border-bottom-style: solid; 80 | border-bottom-width: 2px; 81 | } 82 | 83 | .CodeMirror-gutters { 84 | background-color: @base02; 85 | border-right-color: transparent; 86 | } 87 | 88 | a:visited { 89 | color: @violet; 90 | } 91 | 92 | &.cm-s-dark { 93 | color: @base0; 94 | background-color: @base03; 95 | .cm-matchhighlight { 96 | border-bottom-color: @base02; 97 | } 98 | .CodeMirror-gutters { 99 | background-color: @base02; 100 | } 101 | .CodeMirror-gutter-filler { 102 | background-color: @base02; 103 | } 104 | .CodeMirror-selected, .cm-searching { 105 | background: rgba(255, 255, 255, 0.10); 106 | } 107 | .CodeMirror-activeline-background { 108 | background-color: @base02; 109 | } 110 | .CodeMirror-scrollbar-filler { 111 | background-color: @base03; 112 | } 113 | } 114 | 115 | &.cm-s-light { 116 | background-color: @base3; 117 | color: @base00; 118 | 119 | .cm-matchhighlight { 120 | border-bottom-color: @base2; 121 | } 122 | 123 | .CodeMirror-gutters { 124 | background-color: @base2; 125 | } 126 | 127 | .CodeMirror-gutter-filler { 128 | background-color: @base2; 129 | } 130 | 131 | .CodeMirror-selected, .cm-searching { 132 | background: rgba(0, 0, 0, 0.10); 133 | } 134 | 135 | .CodeMirror-activeline-background { 136 | background-color: @base2; 137 | } 138 | 139 | .CodeMirror-scrollbar-filler { 140 | background-color: @base3; 141 | } 142 | } 143 | 144 | .CodeMirror-gutters { 145 | padding: 0 15px 0 10px; 146 | } 147 | 148 | .CodeMirror-linenumber { 149 | color: @base00; 150 | } 151 | 152 | .CodeMirror-gutter .CodeMirror-gutter-text { 153 | color: @base01; 154 | } 155 | 156 | .CodeMirror-lines .CodeMirror-cursor { 157 | border-left: 1px solid @base01; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Backend/src/main/scala/com.scalakata.backend/Json.scala: -------------------------------------------------------------------------------- 1 | package com.scalakata 2 | package backend 3 | 4 | import eval._ 5 | 6 | import play.api.libs.json._ 7 | import play.api.libs.json.Json._ 8 | import play.api.libs.functional.syntax._ 9 | 10 | import spray.http._ 11 | import spray.http.ContentTypes._ 12 | import spray.http.ContentTypeRange._ 13 | 14 | import spray.httpx.marshalling.Marshaller 15 | import spray.httpx.unmarshalling.Unmarshaller 16 | 17 | case class Code(prelude: String, code: String) 18 | 19 | object Request { 20 | val json = ContentTypeRange(MediaTypes.`application/json`, HttpCharsets.`UTF-8`) 21 | 22 | implicit private val evalrequest = Json.reads[EvalRequest] 23 | implicit val EvalRequestUnmarshaller = 24 | Unmarshaller.delegate[String, EvalRequest](json) { 25 | data ⇒ fromJson[EvalRequest](Json.parse(data)).get 26 | } 27 | 28 | implicit private val completionrequest = Json.reads[CompletionRequest] 29 | implicit val CompletionRequestUnmarshaller = 30 | Unmarshaller.delegate[String, CompletionRequest](json) { 31 | data ⇒ fromJson[CompletionRequest](Json.parse(data)).get 32 | } 33 | 34 | implicit private val typeatrequest = Json.reads[TypeAtRequest] 35 | implicit val TypeAtRequestUnmarshaller = 36 | Unmarshaller.delegate[String, TypeAtRequest](json) { 37 | data ⇒ fromJson[TypeAtRequest](Json.parse(data)).get 38 | } 39 | } 40 | 41 | object Response { 42 | implicit private val code = Json.writes[Code] 43 | 44 | implicit private val orderedRender = new Writes[OrderedRender]{ 45 | def writes(s: OrderedRender) = { 46 | val res:Seq[JsValue] = 47 | s.map { case ((rs, re), renders) ⇒ 48 | JsArray(Seq( 49 | JsArray(Seq(JsNumber(rs), JsNumber(re))), 50 | JsArray(renders.map(rendertype.writes)) 51 | )) 52 | } 53 | JsArray(res) 54 | } 55 | } 56 | 57 | 58 | implicit private val expression = new Writes[List[Expression]] { 59 | def writes(xs: List[Expression]) = JsArray(xs.map(rendertype.writes)) 60 | } 61 | 62 | implicit private val rendertype: Writes[Render] = new Writes[Render] { 63 | def writes(s: Render) = { 64 | def wrap(tpe: String, value: String) = wrap2(tpe, JsString(value)) 65 | def wrap2(tpe: String, value: JsValue) = 66 | JsObject(Seq("type" -> JsString(tpe), "value" -> value)) 67 | 68 | implicit val tuples = new Writes[(String, Int)] { 69 | def writes(t: (String, Int)) = JsArray(Seq(JsString(t._1), JsNumber(t._2))) 70 | } 71 | val res = 72 | s match { 73 | case Html(v, h) ⇒ wrap2("html", toJson((v, h))) 74 | case Html2(v) ⇒ wrap2("html2", toJson(v)) 75 | case Markdown(v) ⇒ wrap("markdown", v) 76 | case Markdown2(v) ⇒ wrap("markdown2", v) 77 | case EString(v) ⇒ wrap("string", v) 78 | case Other(v) ⇒ wrap("other", v) 79 | case Block(cs) ⇒ wrap2("block", toJson(cs)) 80 | case Steps(ss) ⇒ wrap2("steps", toJson(ss)) 81 | } 82 | toJson(res) 83 | } 84 | } 85 | 86 | implicit private val compilationinfo = Json.writes[CompilationInfo] 87 | implicit private val runtimeerror = Json.writes[RuntimeError] 88 | implicit private val compilationinfomap = new Writes[Map[Severity,List[CompilationInfo]]] { 89 | def writes(s: Map[Severity,List[CompilationInfo]]) = { 90 | val a = s.map{ case (s, cis) ⇒ 91 | val sev = s match { 92 | case Error ⇒ "error" 93 | case Warning ⇒ "warning" 94 | case Info ⇒ "info" 95 | } 96 | sev -> toJson(cis) 97 | } 98 | toJson(a) 99 | } 100 | } 101 | 102 | implicit private val evalresponse = Json.writes[EvalResponse] 103 | implicit val EvalResponseMarshaller = 104 | Marshaller.of[EvalResponse](`application/json`) { (eval, contentType, ctx) ⇒ 105 | ctx.marshalTo(HttpEntity(contentType, toJson(eval).toString)) 106 | } 107 | 108 | implicit private val completionresponse = Json.writes[CompletionResponse] 109 | implicit val CompletionResponseMarshaller = 110 | Marshaller.of[List[CompletionResponse]](`application/json`) { (eval, contentType, ctx) ⇒ 111 | ctx.marshalTo(HttpEntity(contentType, toJson(eval).toString)) 112 | } 113 | 114 | implicit private val typeatresponse = Json.writes[TypeAtResponse] 115 | implicit val typeatresponseMarshaller = 116 | Marshaller.of[Option[TypeAtResponse]](`application/json`) { (eval, contentType, ctx) ⇒ 117 | ctx.marshalTo(HttpEntity(contentType, toJson(eval).toString)) 118 | } 119 | 120 | implicit val CodeMarshaller = 121 | Marshaller.of[Code](`application/json`) { (eval, contentType, ctx) ⇒ 122 | ctx.marshalTo(HttpEntity(contentType, toJson(eval).toString)) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Eval/compile/src/test/scala/com.scalakata.eval/CompilerSpec.scala: -------------------------------------------------------------------------------- 1 | package com.scalakata.eval 2 | 3 | import sbt.BuildInfo 4 | 5 | import java.io.File 6 | 7 | import org.specs2._ 8 | 9 | class CommpilerSpecs extends Specification { def is = s2""" 10 | typeAt $typeAt 11 | autocomplete symbols $autocompleteSymbols 12 | autocomplete types $autocompleteTypes 13 | """ 14 | /* 15 | 16 | 17 | nopackage $nopackage 18 | compilation infos $infos 19 | runtimeErrors $runtimeErrors 20 | works $works 21 | support packages $packages 22 | compile classpath $compileClasspath 23 | compileClasspath $compileClasspath 24 | linkage $linkage 25 | doubledef $doubledef 26 | */ 27 | 28 | def wrap(code: String) = 29 | s"""|object Playground { 30 | | $code 31 | |}""".stripMargin 32 | 33 | def typeAt = { 34 | val c = compiler 35 | val code = wrap("List(1)") 36 | oprintln(code.slice(28, 29)) 37 | c.typeAt(code, 28, 28) ==== 38 | Some(TypeAtResponse("List[Int]")) 39 | } 40 | 41 | def autocompleteSymbols = { 42 | val c = compiler 43 | c.autocomplete(" ", 0) must contain( 44 | CompletionResponse( 45 | name = "assert", 46 | signature = "(assertion: Boolean): Unit" 47 | ) 48 | ) 49 | } 50 | 51 | def autocompleteTypes = { 52 | val c = compiler 53 | val code = wrap("List(1).") 54 | oprintln(code.slice(65, 67)) // [65, 67[ 55 | c.autocomplete(code, 29) must contain( 56 | CompletionResponse("map","[B](f: A => B): scala.collection.TraversableOnce[B]") 57 | ) 58 | } 59 | 60 | 61 | def doubledef = { 62 | val c = compiler 63 | val code = """|trait K[T] { def f = A } 64 | |case object A extends K[Int]""".stripMargin 65 | 66 | val result = c.insight(wrap(code)) 67 | result.runtimeError must be empty 68 | } 69 | 70 | 71 | 72 | 73 | 74 | val artifacts = 75 | (BuildInfo.fullClasspath ++ BuildInfo.runtime_exportedProducts). 76 | map(_.getAbsoluteFile). 77 | mkString(File.pathSeparator) 78 | 79 | import scala.concurrent.duration._ 80 | 81 | val scalacOptions = sbt.BuildInfo.scalacOptions.to[Seq] 82 | def compiler = new Compiler(artifacts, scalacOptions, security = false, timeout = 20.seconds) 83 | 84 | def linkage = { 85 | val c = compiler 86 | val inner = """|(new BC).f 87 | |(new CB).f""".stripMargin 88 | 89 | val code = s"""|trait A { def f: String } 90 | |trait B extends A { override def f = "B" } 91 | |trait C extends A { override def f = "C" } 92 | |class BC extends A with B with C 93 | |class CB extends A with C with B 94 | |${wrap(inner)}""".stripMargin 95 | val result = c.insight(code) 96 | result.runtimeError must be empty 97 | } 98 | 99 | def nopackage = { 100 | val c = compiler 101 | val result = c.insight(wrap("1+1")) 102 | result.insight must not be empty 103 | } 104 | 105 | def infos = { 106 | val c = compiler 107 | val result = c.insight( 108 | """|object Extras { 109 | | type V = Toto 110 | | class Meter(val v: Int) extends Anyval { 111 | | def +(o: Meter) = new Meter(v + o.v) 112 | | } 113 | |}""".stripMargin) 114 | result.infos must not be empty 115 | } 116 | 117 | def works = { 118 | val c = compiler 119 | val result = c.insight(wrap("1+1")) 120 | result.insight must not be empty 121 | } 122 | 123 | def packages = { 124 | val c = compiler 125 | val code = 126 | """|val a = List(1,2) 127 | |val b = 2""".stripMargin 128 | 129 | val result = c.insight( 130 | s"""|package intro 131 | |class TEEEEEEEEEEEEEEST() 132 | |${wrap(code)}""".stripMargin 133 | ) 134 | 135 | result.insight must not be empty 136 | /*result ==== EvalResponse.empty.copy(insight = 137 | List(Instrumentation("List(1, 2)", RT_Other, 62, 75), Instrumentation("2", RT_Other, 80, 85)) 138 | )*/ 139 | } 140 | 141 | 142 | def runtimeErrors = { 143 | val c = compiler 144 | val code = 145 | """|0 146 | |1/0""".stripMargin 147 | c.insight(wrap(code)) ==== EvalResponse.empty.copy( 148 | runtimeError = Some(RuntimeError("java.lang.ArithmeticException: / by zero", 2)) 149 | ) 150 | } 151 | 152 | def compileClasspath = { 153 | val c = compiler 154 | c.insight(wrap("com.example.test.Testing.onetwothree")) ==== EvalResponse.empty.copy( 155 | insight = List(((83, 94), List(Other("123")))) 156 | ) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /Frontend/styles/layout.less: -------------------------------------------------------------------------------- 1 | // trick to hide textareas durring loading 2 | .code textarea { 3 | opacity: 0; 4 | } 5 | 6 | body { 7 | height: 100%; 8 | margin: 0; 9 | overflow: hidden; 10 | } 11 | 12 | .fullscreen(@rules) { 13 | :fullscreen { @rules(); } 14 | :-ms-fullscreen { @rules(); } 15 | :-moz-full-screen { @rules(); } 16 | :-webkit-full-screen { @rules(); } 17 | } 18 | 19 | .zen(@full, @p) { 20 | padding: @p; 21 | height: calc(~"100% - "(2 *@p)); 22 | & when(@full) { 23 | width: calc(~"100% - "(2 *@p)); 24 | } 25 | } 26 | 27 | 28 | @screen-sm-min: 768px; 29 | @screen-md-min: 992px; 30 | @screen-lg-min: 1200px; 31 | @screen-xs-max: (@screen-sm-min - 1); 32 | @screen-sm-max: (@screen-md-min - 1); 33 | @screen-md-max: (@screen-lg-min - 1); 34 | 35 | // small 36 | @media (max-width: @screen-xs-max) { 37 | .insight { 38 | max-width: @screen-xs-max; 39 | } 40 | } 41 | 42 | // medium 43 | @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { 44 | .insight { 45 | max-width: @screen-sm-max; 46 | } 47 | } 48 | 49 | // large 50 | @media (min-width: @screen-md-min) and (max-width: @screen-md-max) { 51 | .insight { 52 | max-width: @screen-md-max; 53 | } 54 | } 55 | 56 | // wide 57 | @media (min-width: @screen-lg-min) { 58 | .insight { 59 | max-width: @screen-lg-min - 200px; 60 | } 61 | .code { 62 | .zen(false, 20px); 63 | } 64 | } 65 | 66 | .fullscreen({ 67 | .menu { 68 | display: none; 69 | } 70 | .code { 71 | .zen(true, 100px); 72 | } 73 | }); 74 | 75 | @media print { 76 | .menu { 77 | display: none; 78 | } 79 | } 80 | 81 | .menu { 82 | position: fixed; 83 | top: 0; 84 | right: 0; 85 | margin: 16px; 86 | border-radius: 5px; 87 | z-index: 20; 88 | 89 | &.unsaved { 90 | .save{ display: block; } 91 | .update, .fork{ display: none; } 92 | } 93 | &.saved { 94 | .save{ display: none; } 95 | .update, .fork{ display: block } 96 | } 97 | 98 | .run, .clear, .loading { display: none; } 99 | &.idle .run { display: block; } 100 | &.running .loading { display: block; } 101 | &.viewing .clear { display: block; } 102 | 103 | i { 104 | display: block; 105 | font-size: 32px; 106 | margin: 10px; 107 | cursor: pointer; 108 | } 109 | } 110 | 111 | .code { 112 | .error { 113 | display: inline-block; 114 | border-bottom: 1px dashed red; 115 | } 116 | .error-message { 117 | line-height: 22px; 118 | color: red; 119 | padding-left: 16px; 120 | i { 121 | margin-right: 5px; 122 | margin-left: 5px; 123 | } 124 | } 125 | 126 | .macroAnnotation { 127 | opacity: 0.3; 128 | } 129 | 130 | pre.insight.inline { 131 | display: inline-block; 132 | } 133 | .insight { 134 | // so we can see the cursor 135 | margin-left: 2px; 136 | 137 | border-radius: 2px; 138 | white-space: normal; 139 | position: relative; 140 | margin-top: 5px; 141 | overflow: auto; 142 | 143 | &.markdown { 144 | h1 { margin: 30px 0; } 145 | p { 146 | margin: 15px; 147 | white-space: pre-line; 148 | } 149 | } 150 | 151 | &.html { 152 | display: block; 153 | width: 100%; 154 | resize: vertical; 155 | border: none; 156 | } 157 | 158 | &.block { 159 | &.CodeMirror { 160 | height: auto; 161 | overflow: hidden; 162 | position: relative; 163 | } 164 | .CodeMirror-cursors { 165 | display: none; 166 | } 167 | .CodeMirror-scroll { 168 | height: auto; 169 | overflow-y: auto; 170 | overflow-x: auto; 171 | } 172 | .insight { 173 | margin-left: 40px; 174 | } 175 | .clip { 176 | position: absolute; 177 | top: 0; 178 | right: 0; 179 | margin: 20px; 180 | font-size: 32px; 181 | cursor: pointer; 182 | z-index: 2; 183 | } 184 | } 185 | 186 | 187 | &.code { 188 | font-style: italic; 189 | opacity: 0.8; 190 | } 191 | &.inline { 192 | height: auto; 193 | border-box: none; 194 | margin-left: 16px; 195 | padding: 10px; 196 | } 197 | 198 | &.fold { 199 | padding: 10px; 200 | margin-top: -22px; 201 | } 202 | 203 | hr { 204 | border-style: solid; 205 | } 206 | 207 | pre { 208 | white-space: pre-wrap; 209 | } 210 | 211 | z-index: 2; 212 | } 213 | } 214 | 215 | .ui-splitbar{ 216 | height: 4px !important; 217 | z-index: 11; 218 | } 219 | 220 | video { 221 | pointer-events: none; 222 | opacity: 0.2; 223 | z-index: 3; 224 | } 225 | 226 | #background video { 227 | position: fixed; 228 | top: 0; 229 | left: 0; 230 | width: 100%; 231 | height: auto; 232 | } 233 | 234 | #webcam video { 235 | position: fixed; 236 | bottom: 0; 237 | right: 0; 238 | margin: 20px; 239 | width: 256px; 240 | height: auto; 241 | } 242 | 243 | 244 | 245 | 246 | // 247 | -------------------------------------------------------------------------------- /Eval/compile/src/main/scala/com.scalakata.eval/Eval.scala: -------------------------------------------------------------------------------- 1 | package com.scalakata.eval 2 | 3 | import scala.tools.nsc.{Global, Settings} 4 | import scala.tools.nsc.reporters.StoreReporter 5 | import scala.tools.nsc.io.{VirtualDirectory, AbstractFile} 6 | import scala.reflect.internal.util.{NoPosition, BatchSourceFile, AbstractFileClassLoader} 7 | 8 | import scala.language.reflectiveCalls 9 | 10 | import java.io.File 11 | import java.net.{URL, URLClassLoader, URLEncoder} 12 | 13 | class Eval(settings: Settings, security: Boolean) { 14 | 15 | if(security) { Security.start } 16 | 17 | private val reporter = new StoreReporter() 18 | 19 | private val artifactLoader = { 20 | val loaderFiles = 21 | settings.classpath.value.split(File.pathSeparator).map(a ⇒ { 22 | 23 | val node = new java.io.File(a) 24 | val endSlashed = 25 | if(node.isDirectory) node.toString + "/" 26 | else node.toString 27 | 28 | val t = 29 | if(sys.props("os.name") == "Window") URLEncoder.encode(endSlashed, "UTF-8") 30 | else endSlashed 31 | new java.net.URI(s"file://$t").toURL 32 | }) 33 | new URLClassLoader(loaderFiles, this.getClass.getClassLoader) 34 | } 35 | 36 | private val target = new VirtualDirectory("(memory)", None) 37 | private var classLoader: AbstractFileClassLoader = _ 38 | 39 | settings.outputDirs.setSingleOutput(target) 40 | settings.Ymacroexpand.value = settings.MacroExpand.Normal 41 | 42 | private val compiler = new Global(settings, reporter) 43 | 44 | def apply(code: String): EvalResponse = { 45 | compile(code) 46 | 47 | val infos = check() 48 | 49 | if(!infos.contains(Error)) { 50 | // Look for static class with eval$ method that return 51 | // an instrumentation 52 | def findEval: Option[OrderedRender] = { 53 | def removeExt(of: String) = { 54 | of.slice(0, of.lastIndexOf(".class")) 55 | } 56 | 57 | def removeMem(of: String) = { 58 | of.slice("(memory)/".length, of.length) 59 | } 60 | 61 | def recurseFolders(file: AbstractFile): Set[AbstractFile] = { 62 | file.iterator.to[Set].flatMap{ fs ⇒ 63 | if(fs.isDirectory) 64 | fs.to[Set] ++ 65 | fs.filter(_.isDirectory).flatMap(recurseFolders).to[Set] 66 | else Set(fs) 67 | } 68 | } 69 | 70 | val instrClass = 71 | recurseFolders(target). 72 | map(_.path). 73 | map(((removeExt _) compose (removeMem _))). 74 | map(_.replace('/', '.')). 75 | filterNot(_.endsWith("$class")). 76 | find { n ⇒ 77 | classLoader.loadClass(n).getMethods.exists(m ⇒ 78 | m.getName == "eval$" && 79 | m.getReturnType == classOf[OrderedRender] 80 | ) 81 | } 82 | 83 | import scala.reflect.runtime.{universe ⇒ ru} 84 | val m = ru.runtimeMirror(classLoader) 85 | 86 | instrClass.map{ c ⇒ 87 | m.reflectModule(m.staticModule(c)). 88 | instance.asInstanceOf[{def eval$(): OrderedRender}].eval$() 89 | } 90 | } 91 | 92 | EvalResponse.empty.copy( 93 | insight = findEval.getOrElse(Nil), 94 | infos = infos 95 | ) 96 | } else { 97 | EvalResponse.empty.copy(infos = infos) 98 | } 99 | } 100 | 101 | private def check(): Map[Severity, List[CompilationInfo]] = { 102 | val infos = 103 | reporter.infos.map { info ⇒ 104 | val (start, end) = info.pos match { 105 | case NoPosition ⇒ (-1, -1) 106 | case _ ⇒ (info.pos.start, info.pos.end) 107 | } 108 | ( 109 | info.severity, 110 | start, 111 | end, 112 | info.msg 113 | ) 114 | }.to[List] 115 | .filterNot{ case (sev, _, _, msg) ⇒ 116 | // annoying 117 | sev == reporter.WARNING && 118 | msg == ("a pure expression does nothing in statement " + 119 | "position; you may be omitting necessary parentheses") 120 | }.groupBy(_._1) 121 | .mapValues{_.map{case (_,start, end, message) ⇒ (start, end, message)}} 122 | 123 | def convert(infos: Map[reporter.Severity, List[(Int, Int, String)]]): Map[Severity, List[CompilationInfo]] = { 124 | infos.map{ case (k,vs) ⇒ 125 | val sev = k match { 126 | case reporter.ERROR ⇒ Error 127 | case reporter.WARNING ⇒ Warning 128 | case reporter.INFO ⇒ Info 129 | } 130 | val info = vs map {case (start, end, message) ⇒ 131 | CompilationInfo(message, start, end) 132 | } 133 | (sev, info) 134 | } 135 | } 136 | convert(infos) 137 | } 138 | 139 | private def reset(): Unit = { 140 | target.clear() 141 | reporter.reset() 142 | classLoader = new AbstractFileClassLoader(target, artifactLoader) 143 | } 144 | 145 | private def compile(code: String): Unit = { 146 | reset() 147 | val run = new compiler.Run 148 | val sourceFiles = List(new BatchSourceFile("(inline)", code)) 149 | 150 | run.compileSources(sourceFiles) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Eval/macro/src/main/scala/com.scalakata.eval/Annotation.scala: -------------------------------------------------------------------------------- 1 | package com.scalakata.eval 2 | 3 | import scala.reflect.macros.whitebox.Context 4 | 5 | import scala.language.experimental.macros 6 | import scala.annotation.StaticAnnotation 7 | 8 | import collection.immutable.Queue 9 | import collection.mutable.{Map ⇒ MMap} 10 | 11 | case class Record[K: Ordering, V](inner: MMap[K, Queue[V]] = MMap.empty[K, Queue[V]]) { 12 | def update(k: K, nv: V): Unit = { 13 | val t = 14 | inner.get(k) match { 15 | case Some(v) ⇒ v :+ nv 16 | case None ⇒ Queue(nv) 17 | } 18 | inner(k) = t 19 | } 20 | def ordered: List[(K, List[V])] = { 21 | inner.toMap.mapValues(_.to[List]).to[List].sortBy(_._1) 22 | } 23 | } 24 | 25 | class Helper[C <: Context](val c: C) { 26 | import c.universe._ 27 | 28 | def lift(p: c.universe.Position): (Int, Int) = { 29 | (p.start, p.end) 30 | } 31 | implicit def liftq = Liftable[c.universe.Position] { p ⇒ 32 | q"(${p.point}, ${p.end})" 33 | } 34 | 35 | def topInst(tree: Tree, depth: Int = 0)(instr: TermName): Tree = tree match { 36 | case t @ q"desugar{ ..$body }" ⇒ block(lift(t.pos), body, depth, true)(instr) 37 | case ident: Ident ⇒ inst(ident)(instr) // a 38 | case apply: Apply ⇒ inst(apply)(instr) // f(..) 39 | 40 | case bl @ Block(childs, last) if (depth == 0) ⇒ block(lift(bl.pos), childs ::: List(last), depth)(instr) 41 | case select: Select ⇒ inst(select)(instr) 42 | case mat: Match ⇒ inst(mat)(instr) 43 | case tr: Try ⇒ inst(tr)(instr) 44 | case c: ClassDef if (depth == 0) ⇒ q"" 45 | case m: ModuleDef if (depth == 0) ⇒ q"" 46 | case otherwise ⇒ otherwise 47 | } 48 | 49 | def block(position: (Int, Int), childs: List[Tree], depth: Int, desug: Boolean = false)(instr: TermName): Tree = { 50 | val t = TermName("t$") 51 | val oldinstr = TermName("oldinstr$") 52 | 53 | def des(tree: Tree): Tree = { 54 | val pre = s"""|```scala 55 | |${showCode(tree)} 56 | |```""".stripMargin 57 | q"${instr}(${tree.pos}) = com.scalakata.eval.Markdown2($pre)" 58 | } 59 | 60 | val childs2 = childs.flatMap{ s ⇒ 61 | val ins = topInst(s, depth + 1)(instr) 62 | if(desug) List(des(s), ins) 63 | else List(ins) 64 | } 65 | 66 | q"""{ 67 | val $oldinstr = $instr 68 | $instr = Record[Range, Render]() 69 | ..$childs2 70 | ${oldinstr}($position) = Block(${instr}.ordered) 71 | $instr = $oldinstr 72 | () 73 | }""" 74 | } 75 | 76 | def inst2(expr: Tree, posTree: Tree)(instr: TermName) : Tree = { 77 | val t = TermName("t$") 78 | q"""{ 79 | val $t = $expr 80 | if(isNotUnit($t)) ${instr}(${posTree.pos}) = render($t) 81 | $t 82 | }""" 83 | } 84 | def inst(expr: Tree)(instr: TermName): Tree = inst2(expr, expr)(instr) 85 | 86 | def trace(instr: TermName) = { 87 | val t = TermName("t$") 88 | c.Expr[Any ⇒ Unit](q"""{ 89 | (v: Any) ⇒ { 90 | ${instr}(${c.enclosingPosition}) = render(v) 91 | () 92 | } 93 | }""") 94 | } 95 | } 96 | 97 | object ScalaKataMacro { 98 | val instrName = "instr$" 99 | 100 | def trace_implf(c: Context): c.Expr[Any ⇒ Unit] = { 101 | val instr = c.universe.TermName(instrName) 102 | new Helper[c.type](c).trace(instr) 103 | } 104 | 105 | def desugar2_impl[T](c: Context)(code: c.Expr[T]): c.Expr[Unit] = { 106 | import c.universe._ 107 | val instr = TermName(instrName) 108 | 109 | val q"{ ..$body }" = code.tree 110 | 111 | val pos = 112 | if(body.isEmpty) (code.tree.pos.start, code.tree.pos.end) 113 | else (body.head.pos.start, body.last.pos.end) 114 | 115 | c.Expr[Unit]( 116 | new Helper[c.type](c).block(pos, body, 0, true)(instr) 117 | ) 118 | } 119 | 120 | def instrumentation(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { 121 | import c.universe._ 122 | 123 | // we modify a global mutable set of range position and representation 124 | // we can swap this set if we are inside a block 125 | // so we have easy access to it in the trace macro 126 | def instrument(body: Seq[Tree], name: c.TermName, extend: List[Tree]) = { 127 | val instr = TermName(instrName) 128 | 129 | val classAndObjects = body.collect { 130 | case c: ClassDef ⇒ c 131 | case m: ModuleDef ⇒ m 132 | case td: TypeDef ⇒ td 133 | case ips: Import ⇒ ips 134 | } 135 | 136 | val helper = new Helper[c.type](c) 137 | q""" 138 | object $name extends ..$extend { 139 | ..$classAndObjects 140 | private var $instr = Record[Range, Render]() 141 | def ${TermName("eval$")}(): OrderedRender = { 142 | ..${body.map(b ⇒ helper.topInst(b)(instr))} 143 | ${instr}.ordered 144 | } 145 | } 146 | """ 147 | } 148 | 149 | c.Expr[Any]{ 150 | annottees.map(_.tree).toList match { 151 | case q"object $name extends ..$extend { ..$body }" :: Nil ⇒ 152 | instrument(body, name, extend) 153 | } 154 | } 155 | } 156 | } 157 | 158 | class ScalaKata extends StaticAnnotation { 159 | def macroTransform(annottees: Any*) = macro ScalaKataMacro.instrumentation 160 | } 161 | -------------------------------------------------------------------------------- /Frontend/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Scala Kata 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 | 36 | 37 |
38 |
39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /Frontend/gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | plumber = require('gulp-plumber'), 3 | gulpUtil = require('gulp-util'), 4 | concat = require('gulp-concat'), 5 | express = require('express'), 6 | https = require('https'), 7 | http = require('http'), 8 | fs = require('fs'), 9 | install = require('./plugins/install.js'), 10 | run = require('./plugins/run.js'), 11 | less = require('gulp-less'), 12 | livereload = require('connect-livereload'), 13 | spawn = require("gulp-spawn"), 14 | refresh = require('gulp-livereload'), 15 | gulpFilter = require('gulp-filter'), 16 | rename = require('gulp-rename'), 17 | request = require('request'), 18 | usemin = require('gulp-usemin'), 19 | uglify = require('gulp-uglify'), 20 | minifyHtml = require('gulp-minify-html'), 21 | minifyCss = require('gulp-minify-css'), 22 | gutil = require('gulp-util'), 23 | rev = require('gulp-rev'); 24 | 25 | var useHttps = false, 26 | i = 0, 27 | livereloadport = 35729 + i, 28 | serverport = 5443 + i, 29 | apiport = 7331 + i, 30 | certs = { 31 | key: fs.readFileSync('key.pem'), 32 | cert: fs.readFileSync('cert.pem'), 33 | port: livereloadport 34 | }, 35 | lrserver = useHttps ? 36 | require('tiny-lr')(certs) : 37 | require('tiny-lr')(); 38 | 39 | 40 | 41 | function serveF(assets){ 42 | var server = express(); 43 | 44 | if(useHttps) { 45 | request.defaults({ 46 | strictSSL: false, // allow us to use our self-signed cert for testing 47 | rejectUnauthorized: false 48 | }); 49 | } 50 | 51 | server.use(livereload({port: livereloadport})); 52 | 53 | assets.forEach(function(a){ 54 | server.use(express.static(a)); 55 | }); 56 | 57 | // catch all to api 58 | server.use(function(req, res) { 59 | gutil.log(req.originalUrl); 60 | gutil.log(req.url); 61 | 62 | var isApi = [ 63 | "eval", 64 | "completion", 65 | "typeAt", 66 | "echo" 67 | ].some(function(v){ 68 | return req.originalUrl == "/" + v 69 | }) || req.originalUrl.indexOf(".scala") !== -1; 70 | 71 | if(isApi) { 72 | req.pipe(request("http://localhost:" + apiport + req.originalUrl)).pipe(res); 73 | } else { 74 | if(useHttps) { 75 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; 76 | } 77 | req.pipe(request("http://localhost:" + serverport)).pipe(res); 78 | } 79 | }); 80 | 81 | if(useHttps) https.createServer(certs, server).listen(serverport); 82 | else http.createServer(server).listen(serverport); 83 | 84 | console.log(serverport, useHttps); 85 | 86 | lrserver.listen(livereloadport); 87 | } 88 | 89 | gulp.task('styles', function() { 90 | return gulp.src('styles/main.less') 91 | .pipe(plumber()) 92 | .pipe(less()) 93 | .pipe(gulp.dest('tmp/styles')) 94 | .pipe(refresh(lrserver)); 95 | }); 96 | 97 | gulp.task('html', function(){ 98 | gulp.src('web/**/*.html') 99 | .pipe(refresh(lrserver)); 100 | }); 101 | 102 | // gulp.task('browser', function(){ 103 | // var protocol = useHttps ? 104 | // "https" : 105 | // "http"; 106 | 107 | // run("open", [protocol + "://localhost:" + serverport]); 108 | // }); 109 | 110 | // to develop codemirror 111 | // gulp.task('js2', function(){ 112 | // gulp.src('bower_components/codemirror/**/*.js') 113 | // .pipe(refresh(lrserver)); 114 | // }); 115 | 116 | gulp.task('js', function(){ 117 | gulp.src('web/**/*.js') 118 | .pipe(refresh(lrserver)); 119 | }); 120 | 121 | gulp.task('bower', function(){ 122 | gulp.src('bower.json') 123 | .pipe(install.bower()) 124 | .pipe(refresh(lrserver)); 125 | }); 126 | 127 | gulp.task('install', ['bower', 'npm']); 128 | 129 | gulp.task('npm', function(){ 130 | gulp.src('package.json') 131 | .pipe(install.npm()) 132 | .pipe(refresh(lrserver)); 133 | }); 134 | 135 | gulp.task('default', function() { 136 | // 'install' 137 | gulp.start('styles', 'serve', 'watch'); 138 | }); 139 | 140 | gulp.task('serve', function(){ 141 | serveF(['web', 'bower_components', 'tmp']); 142 | }); 143 | 144 | gulp.task('watch', function() { 145 | // gulp.watch('bower_components/codemirror/**/*.js', ['js2']); 146 | gulp.watch('styles/**/*.less', ['styles']); 147 | gulp.watch('web/**/*.html', ['html']); 148 | gulp.watch('web/**/*.js', ['js']); 149 | gulp.watch('bower.json', ['bower']); 150 | gulp.watch('package.json', ['npm']); 151 | }); 152 | 153 | gulp.task('build', ['styles', 'usemin', 'font', 'fav', 'zeroclipboard']); 154 | gulp.task('buildServe', ['build', 'serveDist']); 155 | 156 | gulp.task('serveDist', function(){ 157 | serveF(['out']); 158 | }); 159 | 160 | gulp.task('font', function(){ 161 | gulp.src('bower_components/fontawesome/fonts/fontawesome-webfont.woff') 162 | .pipe(gulp.dest('out/assets/fonts/')); 163 | 164 | gulp.src('bower_components/octicons/octicons/octicons.woff') 165 | .pipe(gulp.dest('out/assets/styles/')); 166 | }); 167 | 168 | gulp.task('zeroclipboard', function(){ 169 | gulp.src('bower_components/zeroclipboard/dist/ZeroClipboard.swf') 170 | .pipe(gulp.dest('out/assets/scripts')); 171 | }); 172 | 173 | gulp.task('fav', function(){ 174 | gulp.src('web/assets/favicon.ico') 175 | .pipe(gulp.dest('out/assets/')); 176 | }) 177 | 178 | gulp.task('usemin', ['styles'], function() { 179 | var index = gulpFilter(['**/index.html']); 180 | 181 | gulp.src('web/index.html') 182 | .pipe(usemin()) 183 | .pipe(gulp.dest('out/')) 184 | .pipe(index) 185 | .pipe(gulp.dest('out/assets/')) 186 | 187 | }); 188 | -------------------------------------------------------------------------------- /Frontend/web/controllers/code.js: -------------------------------------------------------------------------------- 1 | CodeMirror.hack = {}; 2 | app.controller('code',["$scope", "$timeout", "LANGUAGE", "VERSION", "scalaEval", "katas", "insightRenderer", "errorsRenderer", "wrap", "webcam", 3 | function code( $scope , $timeout , LANGUAGE , VERSION , scalaEval , katas, insightRenderer , errorsRenderer , wrap , webcam){ 4 | 5 | var cmCode, 6 | state = {}, 7 | ctrl = CodeMirror.keyMap["default"] == CodeMirror.keyMap.pcDefault ? "Ctrl-" : "Cmd-"; 8 | 9 | state.configEditing = false; 10 | 11 | $scope.state = 'idle'; 12 | 13 | if(angular.isDefined(window.localStorage['codemirror_' + VERSION])) { 14 | $scope.cmOptions = JSON.parse(window.localStorage['codemirror_' + VERSION]); 15 | } else { 16 | 17 | var keys = {} 18 | keys[ctrl + "Space"] = "autocomplete"; 19 | keys['.'] = "autocompleteDot"; 20 | keys[ctrl + "Enter"] = "run"; 21 | keys[ctrl + ","] = "config"; 22 | keys[ctrl + "."] = "typeAt"; 23 | keys["F11"] = "fullscreen"; 24 | 25 | $scope.cmOptions = { 26 | "_to config codemirror see_": "http://codemirror.net/doc/manual.html#config", 27 | extraKeys: keys, 28 | coverGutterNextToScrollbar: true, 29 | firstLineNumber: 0, 30 | lineNumbers: false, 31 | lineWrapping: true, 32 | tabSize: 2, 33 | theme: 'solarized light', 34 | "_supported_themes": [ "solarized dark", "solarized light", "mdn-like"], 35 | smartIndent: false, 36 | multiLineStrings: true, 37 | matchTags: {bothTags: true}, 38 | autoCloseBrackets: true, 39 | styleActiveLine: false, 40 | scrollPastEnd: true, 41 | keyMap: "sublime", 42 | mode: 'text/x-' + LANGUAGE, 43 | highlightSelectionMatches: { showToken: false }, 44 | video: false 45 | } 46 | } 47 | 48 | $scope.theme = function(){ 49 | return _.map($scope.cmOptions.theme.split(" "), function(v){ 50 | return "cm-s-" + v; 51 | }).join(" "); 52 | } 53 | 54 | function clear(){ 55 | $timeout(function(){ 56 | $scope.state = 'idle'; 57 | }); 58 | insightRenderer.clear(); 59 | errorsRenderer.clear(); 60 | } 61 | $scope.clear = clear; 62 | 63 | function setMode(edit){ 64 | if(edit) { 65 | state.code = $scope.code; 66 | clear(); 67 | $timeout(function(){ 68 | $scope.cmOptions.mode = 'application/json'; 69 | $scope.code = JSON.stringify($scope.cmOptions, null, '\t'); 70 | }); 71 | } else { 72 | $scope.cmOptions.onLoad = function(cm_) { 73 | cm_.refresh(); 74 | 75 | cmCode = cm_; 76 | CodeMirror.hack.code = cm_; 77 | cmCode.focus(); 78 | cmCode.on('changes', function(){ 79 | clear(); 80 | }); 81 | cmCode.on('dblclick', function(){ 82 | clear(); 83 | }); 84 | }; 85 | 86 | $scope.cmOptions.mode = 'text/x-' + LANGUAGE; 87 | 88 | window.localStorage['codemirror_' + VERSION] = JSON.stringify($scope.cmOptions); 89 | 90 | this.videoSet = this.videoSet || false; 91 | if($scope.cmOptions.video && !this.videoSet) { 92 | this.videoSet = true; 93 | webcam($scope.cmOptions.videoMapping).then(function(newMapping){ 94 | $scope.cmOptions.videoMapping = newMapping; 95 | window.localStorage['codemirror_' + VERSION] = JSON.stringify($scope.cmOptions); 96 | }) 97 | } 98 | $scope.code = state.code; 99 | $timeout(function(){ 100 | $scope.code = state.code; 101 | }); 102 | } 103 | } 104 | function setResource(){ 105 | function load(path){ 106 | setMode(false, false); 107 | katas(path).then(function(r){ 108 | state.code = r.data; 109 | setMode(false, false); 110 | window.history.replaceState({"code": r.data}, null, path); 111 | }); 112 | } 113 | if(window.location.pathname !== "/") { 114 | load(window.location.pathname); 115 | } else { 116 | if(angular.isDefined(window.localStorage['code_' + VERSION])){ 117 | state.code = window.localStorage['code_' + VERSION]; 118 | window.history.replaceState({"code": state.code}, null, "/"); 119 | } else { 120 | state.code = ""; 121 | window.history.replaceState({"code": state.code}, null, "/"); 122 | } 123 | setMode(false, false); 124 | } 125 | } 126 | setResource(); 127 | 128 | $scope.toogleEdit = function(){ 129 | state.configEditing = !state.configEditing; 130 | setMode(state.configEditing, false); 131 | }; 132 | 133 | CodeMirror.hack.wrap = wrap; 134 | 135 | window.onpopstate = function(event) { 136 | if(event.state) { 137 | $scope.code = event.state.code; 138 | $scope.$digest(); 139 | run(); 140 | } 141 | }; 142 | 143 | function run(){ 144 | if(state.configEditing) return; 145 | if(!angular.isDefined($scope.code)) return; 146 | 147 | $scope.state = 'running'; 148 | 149 | var w = wrap($scope.code, true); 150 | scalaEval.insight(w.full).then(function(r){ 151 | var data = r.data; 152 | var code = $scope.code.split("\n"); 153 | 154 | $scope.state = 'viewing'; 155 | 156 | insightRenderer.render(cmCode, w, $scope.cmOptions, data.insight, setResource, $scope.code); 157 | errorsRenderer.render(cmCode, w, data.infos, data.runtimeError, code); 158 | }); 159 | } 160 | 161 | CodeMirror.commands.run = run; 162 | CodeMirror.commands.save = run; 163 | CodeMirror.commands.config = $scope.toogleEdit; 164 | 165 | CodeMirror.commands.fullscreen = function(){ 166 | if(screenfull.enabled) { 167 | screenfull.toggle(); 168 | } 169 | } 170 | 171 | $scope.$watch('code', function(){ 172 | if(state.configEditing) { 173 | try { 174 | $scope.cmOptions = JSON.parse($scope.code); 175 | } catch(e){} 176 | } else { 177 | clear(); 178 | window.localStorage['code_' + VERSION] = $scope.code; 179 | } 180 | }); 181 | 182 | $scope.run = run; 183 | }]); 184 | -------------------------------------------------------------------------------- /Eval/compile/src/main/scala/com.scalakata.eval/Compiler.scala: -------------------------------------------------------------------------------- 1 | package com.scalakata.eval 2 | 3 | import java.io.File 4 | import java.math.BigInteger 5 | import java.security.MessageDigest 6 | import java.util.Random 7 | import java.util.concurrent.{TimeoutException, Callable, FutureTask, TimeUnit} 8 | 9 | import scala.util.control.NonFatal 10 | import scala.concurrent.duration._ 11 | 12 | import scala.tools.nsc.interactive.Global 13 | import scala.tools.nsc.Settings 14 | import scala.tools.nsc.reporters.StoreReporter 15 | import scala.tools.nsc.io.VirtualDirectory 16 | import scala.reflect.internal.util._ 17 | import scala.tools.nsc.interactive.Response 18 | 19 | import scala.concurrent.duration._ 20 | 21 | class Compiler(artifacts: String, scalacOptions: Seq[String], security: Boolean, timeout: Duration) { 22 | 23 | def insight(code: String): EvalResponse = { 24 | if (code.isEmpty) EvalResponse.empty 25 | else { 26 | try { 27 | withTimeout{eval(code)}(timeout).getOrElse( 28 | EvalResponse.empty.copy(timeout = true) 29 | ) 30 | } catch { 31 | case NonFatal(e) ⇒ { 32 | e.printStackTrace 33 | val pos = 34 | if(e.getCause != null) { 35 | e.getCause.getStackTrace(). 36 | find(_.getFileName == "(inline)"). 37 | map(_.getLineNumber). 38 | getOrElse(-1) // not virtual, this is a runtime error in ScalaKata code 39 | } else { 40 | e.getStackTrace()(0).getLineNumber 41 | } 42 | 43 | EvalResponse.empty.copy(runtimeError = 44 | Some(RuntimeError(e.getCause.toString, pos)) 45 | ) 46 | } 47 | } 48 | } 49 | } 50 | 51 | private def reload(code: String): BatchSourceFile = { 52 | val file = new BatchSourceFile("default", code) 53 | withResponse[Unit](r ⇒ compiler.askReload(List(file), r)).get 54 | file 55 | } 56 | 57 | private def withResponse[A](op: Response[A] ⇒ Any): Response[A] = { 58 | val response = new Response[A] 59 | op(response) 60 | response 61 | } 62 | 63 | def autocomplete(code: String, p: Int): List[CompletionResponse] = { 64 | def completion(f: (compiler.Position, compiler.Response[List[compiler.Member]]) ⇒ Unit, 65 | pos: compiler.Position): 66 | List[CompletionResponse] = { 67 | 68 | withResponse[List[compiler.Member]](r ⇒ f(pos, r)).get match { 69 | case Left(members) ⇒ compiler.ask(() ⇒ { 70 | members.map(member ⇒ 71 | CompletionResponse( 72 | name = member.sym.decodedName, 73 | signature = member.sym.signatureString 74 | ) 75 | ) 76 | }) 77 | case Right(e) ⇒ 78 | e.printStackTrace 79 | Nil 80 | } 81 | } 82 | def typeCompletion(pos: compiler.Position) = { 83 | completion(compiler.askTypeCompletion _, pos) 84 | } 85 | 86 | def scopeCompletion(pos: compiler.Position) = { 87 | completion(compiler.askScopeCompletion _, pos) 88 | } 89 | 90 | // inspired by scala-ide 91 | // https://github.com/scala-ide/scala-ide/blob/4.0.0-m3-luna/org.scala-ide.sdt.core/src/org/scalaide/core/completion/ScalaCompletions.scala#L170 92 | askTypeAt(code, p, p) { (tree, pos) ⇒ tree match { 93 | case compiler.New(name) ⇒ typeCompletion(name.pos) 94 | case compiler.Select(qualifier, _) if qualifier.pos.isDefined && qualifier.pos.isRange ⇒ 95 | typeCompletion(qualifier.pos) 96 | case compiler.Import(expr, _) ⇒ typeCompletion(expr.pos) 97 | case compiler.Apply(fun, _) ⇒ 98 | fun match { 99 | case compiler.Select(qualifier: compiler.New, _) ⇒ typeCompletion(qualifier.pos) 100 | case compiler.Select(qualifier, _) if qualifier.pos.isDefined && qualifier.pos.isRange ⇒ 101 | typeCompletion(qualifier.pos) 102 | case _ ⇒ scopeCompletion(fun.pos) 103 | } 104 | case _ ⇒ scopeCompletion(pos) 105 | }}{ 106 | pos ⇒ Some(scopeCompletion(pos)) 107 | }.getOrElse(Nil) 108 | } 109 | 110 | def typeAt(code: String, start: Int, end: Int): Option[TypeAtResponse] = { 111 | askTypeAt(code, start, end){(tree, _) ⇒ { 112 | // inspired by ensime 113 | val res = 114 | tree match { 115 | case compiler.Select(qual, name) ⇒ qual 116 | case t: compiler.ImplDef if t.impl != null ⇒ t.impl 117 | case t: compiler.ValOrDefDef if t.tpt != null ⇒ t.tpt 118 | case t: compiler.ValOrDefDef if t.rhs != null ⇒ t.rhs 119 | case t ⇒ t 120 | } 121 | scala.Predef.println(tree) 122 | TypeAtResponse(res.tpe.toString) 123 | }}{Function.const(None)} 124 | } 125 | 126 | private def askTypeAt[A] 127 | (code: String, start: Int, end: Int) 128 | (f: (compiler.Tree, compiler.Position) ⇒ A) 129 | (fb: compiler.Position ⇒ Option[A]): Option[A] = { 130 | 131 | if(code.isEmpty) None 132 | else { 133 | val file = reload(code) 134 | val rpos = compiler.rangePos(file, start, start, end) 135 | 136 | val response = withResponse[compiler.Tree](r ⇒ 137 | compiler.askTypeAt(rpos, r) 138 | ) 139 | 140 | response.get match { 141 | case Left(tree) ⇒ Some(f(tree, rpos)) 142 | case Right(e) ⇒ e.printStackTrace; fb(rpos) 143 | } 144 | } 145 | } 146 | 147 | private val jvmId = java.lang.Math.abs(new Random().nextInt()) 148 | 149 | private val reporter = new StoreReporter() 150 | private val settings = new Settings() 151 | 152 | settings.processArguments(scalacOptions.to[List], true) 153 | settings.bootclasspath.value = artifacts 154 | settings.classpath.value = artifacts 155 | settings.Yrangepos.value = true 156 | 157 | private lazy val compiler = new Global(settings, reporter) 158 | private lazy val eval = new Eval(settings.copy, security) 159 | 160 | private def withTimeout[T](f: ⇒ T)(timeout: Duration): Option[T]= { 161 | val task = new FutureTask(new Callable[T]() { 162 | def call = f 163 | }) 164 | val thread = new Thread( task ) 165 | try { 166 | thread.start() 167 | Some(task.get(timeout.toMillis, TimeUnit.MILLISECONDS)) 168 | } catch { 169 | case e: TimeoutException ⇒ None 170 | } finally { 171 | if( thread.isAlive ){ 172 | thread.interrupt() 173 | thread.stop() 174 | } 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /Frontend/web/services/insightRenderer.js: -------------------------------------------------------------------------------- 1 | app.factory('insightRenderer', ["$timeout", function($timeout) { 2 | var widgets = []; 3 | 4 | function apply(cmCode, wrap, cmOptions, insight, cmOriginal, blockOffset, updateF, prelude, code){ 5 | var nl = "\n", elem, start, end, clearF, joined; 6 | 7 | start = cmCode.getDoc().posFromIndex(wrap.fixRange(insight[0][0])); 8 | start.line -= (blockOffset + 1); 9 | 10 | end = cmCode.getDoc().posFromIndex(wrap.fixRange(insight[0][1])); 11 | end.line -= (blockOffset + 1); 12 | 13 | function captureClick(el){ 14 | $("a", el).map(function(i, e){ 15 | var href = $(e).attr("href"); 16 | if(!angular.isDefined(href)) return; 17 | 18 | function domain(url) { 19 | return url.replace('http://','').replace('https://','').split('/')[0]; 20 | }; 21 | if(domain(href) === domain(window.location.origin) || 22 | (href[0] === '/' && href[1] !== '/')) { 23 | $(e).on('click', function(ev){ 24 | var path = href.replace(window.location.origin, ""), 25 | state = {"prelude": prelude, "code": code}; 26 | ev.preventDefault(); 27 | window.history.pushState(state, null, path); 28 | updateF(path); 29 | }) 30 | } else { 31 | $(e).attr('target', '_blank'); 32 | } 33 | }) 34 | } 35 | 36 | function joined(sep){ 37 | return _.map(insight[1], function(v){ return v.value; }).join(sep); 38 | } 39 | 40 | function addClass(two, e){ 41 | angular.element(e) 42 | .addClass("insight") 43 | .addClass(two); 44 | } 45 | 46 | function fold(e){ 47 | addClass("fold", e); 48 | var range = cmCode.markText({ch: 0, line: start.line}, end, { 49 | replacedWith: e 50 | }); 51 | clearF = function(){ 52 | range.clear(); 53 | }; 54 | } 55 | 56 | function inline(e){ 57 | addClass("inline", e); 58 | var widget = cmCode.addLineWidget(end.line, e); 59 | clearF = function(){ widget.clear() }; 60 | } 61 | 62 | function html(){ 63 | var time = new Date().getTime(), 64 | code = insight[1][0].value[0], 65 | div = $('
'), 66 | form = $('
'), 67 | iframe = $(''); 68 | 69 | $("") 70 | .attr("name", "code") 71 | .attr("value", code) 72 | .appendTo(form); 73 | 74 | iframe.css("height", insight[1][0].value[1]); 75 | 76 | elem = iframe.contents(); 77 | fold(iframe[0]); 78 | $timeout(function(){ 79 | form.submit(); 80 | iframe.load(function(){ 81 | captureClick(iframe.contents()); 82 | }); 83 | }, 500); 84 | } 85 | 86 | function html2(){ 87 | elem = document.createElement("div"); 88 | elem.innerHTML = joined(""); 89 | } 90 | 91 | function markdown(){ 92 | elem = document.createElement("div"); 93 | elem.innerHTML = marked.parse(joined(nl), { 94 | ghf: true, 95 | highlight: function (code, lang) { 96 | var elem = document.createElement("div"), 97 | option = angular.copy(cmOptions), 98 | langs = { 99 | "scala": "text/x-scala", 100 | "json": "application/json" 101 | }, 102 | mode = langs[lang] || ""; 103 | 104 | 105 | CodeMirror.runMode(code, mode, elem, option); 106 | return elem.innerHTML; 107 | } 108 | }); 109 | 110 | elem.className = "markdown"; 111 | captureClick(elem); 112 | } 113 | 114 | switch (insight[1][0].type) { 115 | case "html": 116 | html(); 117 | break; 118 | 119 | case "html2": 120 | html2(); 121 | fold(elem); 122 | break; 123 | 124 | case "markdown": 125 | markdown(); 126 | fold(elem); 127 | break; 128 | 129 | case "markdown2": 130 | markdown(); 131 | inline(elem); 132 | break; 133 | 134 | case "block": 135 | var $element = angular.element("
"), 136 | ta = document.createElement("textarea"), 137 | clip = angular.element(""), 138 | cmOptions2 = angular.copy(cmOptions), 139 | content = cmCode.getRange(start, end); 140 | ta.textContent = content; 141 | $element.append(ta); 142 | cmOptions2.readOnly = true; 143 | cmOptions2.lineNumbers = false; 144 | cmOptions2.scrollPastEnd = false; 145 | var cm = CodeMirror.fromTextArea(ta, cmOptions2); 146 | 147 | var client = new ZeroClipboard(clip); 148 | 149 | client.on("ready", function (event){ 150 | client.on("copy", function (event){ 151 | event.clipboardData.setData("text/plain", content); 152 | clip.addClass("active"); 153 | $timeout(function(){ 154 | clip.removeClass("active"); 155 | }, 400); 156 | }); 157 | }); 158 | 159 | elem = cm.display.wrapper; 160 | angular.element(elem).addClass("block").append(clip); 161 | $timeout(function(){ 162 | cm.refresh(); 163 | }) 164 | 165 | _.forEach(insight[1][0].value, function(it){ 166 | apply(cm, wrap, cmOptions, it, cmOriginal, start.line); 167 | }); 168 | 169 | fold(elem); 170 | 171 | break; 172 | case "string": 173 | elem = document.createElement("pre"); 174 | elem.innerText = joined(nl); 175 | inline(elem); 176 | break; 177 | case "other": 178 | elem = document.createElement("pre"); 179 | elem.className = "code"; 180 | CodeMirror.runMode(joined(nl), cmOptions, elem); 181 | inline(elem); 182 | break; 183 | } 184 | 185 | return clearF; 186 | } 187 | function clearFun(){ 188 | widgets.forEach(function(w){ 189 | w(); 190 | }); 191 | widgets = []; 192 | } 193 | return { 194 | clear: clearFun, 195 | render: function(cmCode, wrap, cmOptions, insights, updateF, prelude, code){ 196 | clearFun(); 197 | widgets = _.map(insights, function(insight){ 198 | return apply(cmCode, wrap, cmOptions, insight, cmCode, 0, updateF, prelude, code); 199 | }); 200 | $timeout(function(){ 201 | cmCode.refresh(); 202 | }); 203 | // focus on cursor 204 | // cmCode.focus(); 205 | // cmCode.scrollIntoView(cmCode.getCursor()); 206 | // cmCode.setCursor(cmCode.getCursor(), null, { focus: true}); 207 | } 208 | } 209 | }]); 210 | -------------------------------------------------------------------------------- /Plugin/src/main/scala/com.scalakata/ScalaKata.scala: -------------------------------------------------------------------------------- 1 | package com.scalakata 2 | 3 | import java.awt.Desktop 4 | 5 | import sbt._ 6 | import Def.Initialize 7 | import Keys._ 8 | import Attributed.data 9 | 10 | import java.net.URL 11 | import java.io.File 12 | 13 | import spray.revolver.Actions 14 | import spray.revolver.RevolverPlugin.Revolver 15 | 16 | import sbtdocker._ 17 | import sbtdocker.Plugin._ 18 | import sbtdocker.Plugin.DockerKeys._ 19 | 20 | import scala.concurrent.duration._ 21 | 22 | object Scalakata extends Plugin { 23 | 24 | case class StartArgs( 25 | readyPort: Int, 26 | classPath: Seq[File], 27 | host: String, 28 | port: Int, 29 | production: Boolean, 30 | security: Boolean, 31 | timeout: Duration, 32 | scalacOptions: Seq[String] 33 | ) { 34 | def toArgs = Seq( 35 | readyPort.toString, 36 | classPath. 37 | map(_.getAbsoluteFile). 38 | mkString(File.pathSeparator), 39 | host, 40 | port.toString, 41 | production.toString, 42 | security.toString, 43 | timeout.toString 44 | ) ++ scalacOptions 45 | } 46 | 47 | lazy val Kata = config("kata") extend(Runtime) 48 | lazy val Backend = config("backend") 49 | 50 | lazy val openBrowser = TaskKey[Unit]("open-browser", "task to open browser to kata url") 51 | lazy val readyPort = SettingKey[Int]("ready-port", "port to send ready command") 52 | lazy val kataUri = SettingKey[URI]("kata-uri", "uri to scala kata") 53 | lazy val startArgs = TaskKey[StartArgs]("start-args", 54 | "The arguments to be passed to the applications main method when being started") 55 | lazy val startArgs2 = TaskKey[Seq[String]]("start-args2", 56 | "The arguments to be passed to the applications main method when being started") 57 | 58 | lazy val securityManager = SettingKey[Boolean]("security-manager", "turn on jvm security manager") 59 | lazy val production = SettingKey[Boolean]("production", "deployed version") 60 | lazy val timeout = SettingKey[Duration]("timeout", "maximum time to wait for evaluation response") 61 | 62 | lazy val test = Project( 63 | id = "test", 64 | base = file("."), 65 | settings = kataSettings 66 | ) 67 | lazy val scalaKataVersion = "0.12.0" 68 | val start = "kstart" 69 | 70 | lazy val kataAutoStart = 71 | onLoad in Global := { 72 | ((s: State) ⇒ { start :: s }) compose (onLoad in Global).value 73 | } 74 | 75 | lazy val kataSettings = 76 | addCommandAlias(start, ";backend:reStart ;backend:openBrowser ;kwatch") ++ 77 | addCommandAlias("kwatch", "~ ;backend:copyResources ;kata:compile ;kata:copyResources") ++ 78 | addCommandAlias("kstop", "backend:reStop") ++ 79 | addCommandAlias("krestart", ";backend:reStop ;backend:reStart") ++ 80 | inConfig(Backend)( 81 | Classpaths.ivyBaseSettings ++ 82 | Classpaths.jvmBaseSettings ++ 83 | Defaults.compileBase ++ 84 | Defaults.configTasks ++ 85 | Defaults.configSettings ++ 86 | Revolver.settings ++ 87 | Seq( 88 | production := false, 89 | securityManager := false, 90 | mainClass in Revolver.reStart := Some("com.scalakata.backend.Boot"), 91 | startArgs2 in Revolver.reStart := (startArgs in Revolver.reStart).value.toArgs, 92 | fullClasspath in Revolver.reStart <<= fullClasspath, 93 | Revolver.reStart <<= InputTask(Actions.startArgsParser) { args ⇒ 94 | ( 95 | streams, 96 | Revolver.reLogTag, 97 | thisProjectRef, 98 | Revolver.reForkOptions, 99 | mainClass in Revolver.reStart, 100 | fullClasspath in Revolver.reStart, 101 | startArgs2 in Revolver.reStart, 102 | args 103 | ).map(Actions.restartApp) 104 | .dependsOn(products in Compile) 105 | }, 106 | kataUri := new URI("http://localhost:7331"), 107 | readyPort := 8081, 108 | openBrowser := { 109 | val socket = new java.net.ServerSocket(readyPort.value) 110 | socket.accept() 111 | socket.close() 112 | 113 | sys.props("os.name").toLowerCase match { 114 | case x if x contains "mac" ⇒ s"open ${kataUri.value.toString}".! 115 | case _ ⇒ Desktop.getDesktop.browse(kataUri.value) 116 | } 117 | 118 | () 119 | }, 120 | libraryDependencies ++= Seq( 121 | "com.scalakata" % s"backend_${scalaBinaryVersion.value}" % scalaKataVersion, 122 | "com.scalakata" % s"eval_${scalaBinaryVersion.value}" % scalaKataVersion, 123 | "com.scalakata" % "frontend" % scalaKataVersion 124 | ) 125 | ) 126 | ) ++ 127 | inConfig(Kata)( 128 | Classpaths.ivyBaseSettings ++ 129 | Classpaths.jvmBaseSettings ++ 130 | Defaults.compileBase ++ 131 | Defaults.configTasks ++ 132 | Defaults.configSettings ++ 133 | Seq( 134 | scalaVersion := "2.11.6", 135 | unmanagedResourceDirectories += sourceDirectory.value, 136 | scalacOptions ++= Seq("-Yrangepos", "-unchecked", "-deprecation", "-feature"), 137 | libraryDependencies ++= Seq( 138 | "com.scalakata" % s"macro_${scalaBinaryVersion.value}" % scalaKataVersion, 139 | "org.scala-lang" % "scala-compiler" % scalaVersion.value, 140 | 141 | compilerPlugin("org.scalamacros" % "paradise" % "2.1.0-M5" cross CrossVersion.full) 142 | // compilerPlugin("org.scalamacros" % s"paradise_${scalaVersion.value}" % "2.1.0-M3") 143 | ) 144 | ) 145 | ) ++ 146 | Seq( 147 | // the backend can serve .scala files 148 | unmanagedResourceDirectories in Backend += (sourceDirectory in Kata).value, 149 | // you have full acces to your project in the kata sandbox 150 | dependencyClasspath in Kata ++= (fullClasspath in Compile).value ++ (fullClasspath in Test).value, 151 | scalaVersion in Backend := (scalaVersion in Kata).value, 152 | timeout in Backend := 20.seconds, 153 | startArgs in (Backend, Revolver.reStart) := StartArgs( 154 | (readyPort in Backend).value, 155 | (fullClasspath in Kata).value. 156 | map(_.data). 157 | map(_.getAbsoluteFile), 158 | (kataUri in Backend).value.getHost, 159 | (kataUri in Backend).value.getPort, 160 | (production in Backend).value, 161 | (securityManager in Backend).value, 162 | (timeout in Backend).value, 163 | (scalacOptions in Kata).value 164 | ), 165 | resolvers ++= Seq( 166 | "spray repo" at "http://repo.spray.io", 167 | "typesafe releases" at "http://repo.typesafe.com/typesafe/releases", 168 | "masseguillaume" at "http://dl.bintray.com/content/masseguillaume/maven", 169 | Resolver.sonatypeRepo("releases") 170 | ) 171 | ) 172 | 173 | lazy val kataDockerSettings = kataSettings ++ inConfig(Backend)(Seq( 174 | production := true, 175 | securityManager := true, 176 | openBrowser := { } 177 | )) ++ inConfig(Kata)(dockerSettings) ++ Seq( 178 | kataUri in Backend := new URI("http://0.0.0.0:7331"), 179 | imageName in (Kata, docker) := { 180 | ImageName( 181 | namespace = None, 182 | repository = name.value, 183 | tag = Some("v" + version.value) 184 | ) 185 | }, 186 | dockerfile in (Kata, docker) := { 187 | val Some(main) = (mainClass in (Backend, Revolver.reStart)).value 188 | 189 | val app = "/app" 190 | val libs = s"$app/libs" 191 | val katas = s"$app/katas" 192 | val plugins = s"$app/plugins" 193 | 194 | val classpath = s"$libs/*:$katas/*" 195 | 196 | new Dockerfile { 197 | from("dockerfile/java:oracle-java8") 198 | 199 | val args = { 200 | val t = (startArgs in (Backend, Revolver.reStart)).value 201 | val kataClasspath = 202 | (packageBin in Compile).value +: 203 | (packageBin in Kata).value +: 204 | (managedClasspath in Kata).value. 205 | map(_.data). 206 | map(_.getAbsoluteFile) 207 | t.copy( 208 | // update compiler plugin path 209 | scalacOptions = t.scalacOptions.map{ v ⇒ 210 | val pluginArg = "-Xplugin:" 211 | if(v.startsWith(pluginArg)) { 212 | val plugin = file(v.slice(pluginArg.length, v.length)) 213 | val target = file(plugins) / plugin.name 214 | stageFile(plugin, target) 215 | pluginArg + target.getAbsolutePath 216 | } else v 217 | }, 218 | // update frontend classpath 219 | classPath = kataClasspath.map { v ⇒ 220 | val target = file(katas) / v.name 221 | stageFile(v, target) 222 | target 223 | } 224 | ) 225 | } 226 | 227 | // backend classpath 228 | (managedClasspath in Backend).value.files.foreach{ dep ⇒ 229 | val target = file(libs) / dep.name 230 | stageFile(dep, target) 231 | } 232 | add(libs, libs) 233 | 234 | // frontend classpath 235 | add(katas, katas) 236 | add(plugins, plugins) 237 | 238 | // exposes 239 | expose(args.port) 240 | entryPoint(( 241 | Seq("java", "-Xmx1G", "-Xms256M", "-cp", classpath, main) ++ args.toArgs 242 | ):_*) 243 | } 244 | } 245 | ) 246 | } 247 | --------------------------------------------------------------------------------