grooscript
@grooscript
http://grooscript.org
Jorge Franco
0.5
About me
Developer, I love it
Lazy in english at school
Living in Madrid
Working at Osoco
Grooscript developer
@jfrancoleza
jorge.franco.leza@gmail.com
The players
Web developer
Leave your
comfort zone
Move to
the client side
Doesn’t run!
on browsers
Do something!
No excuses
You can do a library,
resolve issues,
create a plugin,
give feedback,
help other projects,
…
!
You will learn a lot
The wire
Groovy to Javascript converter
Groovy 2 to Javascript ECMAScript 5
Open source project, Apache 2 license
Library with Groovy and GPars dependencies
No special Javascript objects or functions
Converted code requires grooscript.js to run
Different conversion options
Grails plugin, npm package, gradle plugin
What is grooscript?
File.groovy File.js
Conversion!
Options
grooscript.js
Convert Groovy files
Groovy code
def sayHello = { -> println "Hello ${it}!" }	
[‘Groovy', ‘JavaScript', 'GrooScript'].each sayHello	
!
assert [1, 2, 3].size() == 3	
class Amazing {}	
!
amazing = new Amazing()	
amazing.metaClass.who = []	
amazing.metaClass.add = { who << it}	
!
amazing.add 'Rafa Nadal'	
assert amazing.who.size() == 1
Javascript result
function Amazing() {	
var gSobject = gs.inherit(gs.baseClass,'Amazing');	
gSobject.clazz = { name: 'Amazing', simpleName: 'Amazing'};	
gSobject.clazz.superclass = { name: 'java.lang.Object', simpleName: 'Object'};	
if (arguments.length == 1) {gs.passMapToObject(arguments[0],gSobject);};	
	
return gSobject;	
};	
var sayHello = function(it) {	
return gs.println("Hello " + (it) + "!");	
};	
gs.mc(gs.list(["Groovy" , "JavaScript" , "GrooScript"]),"each",[sayHello]);	
gs.assert(gs.equals(gs.mc(gs.list([1 , 2 , 3]),"size",[]), 3), null);	
amazing = Amazing();	
gs.sp((amazing = gs.metaClass(amazing)),"who",gs.list([]));	
gs.sp((amazing = gs.metaClass(amazing)),"add",function(it) {	
return gs.mc(gs.fs('who', this),'leftShift', gs.list([it]));	
});	
gs.mc(amazing,"add",["Rafa Nadal"]);	
gs.assert(gs.equals(gs.mc(gs.gp(amazing,"who"),"size",[]), 1), null);
Class
Default constructor
Function
New
Javascript
Groovy
Java
MoreJavascriptfriendly
Types, inheritance, java 8, …
metaClass
Lists
Closures
Operators
Dsl’s
Expando
Maps
beans
methodMissing
Mixins
Categories
Traits
delegate
propertyMissing
Using converted code in javascript
Create classes
NameClass();
Default constructor with js object
NameClass({property: value, data: 1});
Call methods, basic parameters
myObject.method(params);
Access properties
myObject.property = 4;
Use functions as closures
list.each(function () { … });
Traits
Metaprogramming
Mixins, Categories
Groovy types
gs.map({});
gs.set();
gs.range(1, 5);
gs.date();
gs.regExp();
gs.stringBuffer();
…
Groovy list functions added to js lists
Added some functions to numbers and strings
(1).times(function () {});
“Groovy”.startsWith();
“Hello”.replaceAll();
Added curry to functions
Convert objects ‘to groovy’ or ‘to javascript’
gs.toGroovy(javaScriptList, NameClass);
gs.toJavascript(myGroovyObject);
Operators
Groovy truth
equals
getters and setters
Conversions
gs.toGroovy(obj, Type);
gs.toJavascript();
Limitations
Groovy / Java not fully supported
Working in groovy-core
No good support for inheritance (super)
No methods with same name
Basic support in metaClass
No packages, no classes with same name
No metainfo, expandoMetaClass, …
Only AST transformations applied in semantic phase
No complex Java / Groovy types (LinkedList,…)
… see documentation for more info on grooscript.org
Directly convert code
@Grab(‘org.grooscript:grooscript:0.5’)	
!
import org.grooscript.GrooScript	
!
//Convert a fragment code	
String jsResult = GrooScript.convert(“println ‘Hello!’”)	
!
//Convert some file or folder, generate .js files with same name	
GrooScript.convert(‘path/to/file.groovy’, ‘destination/folder’)	
GrooScript.convert(‘path/to/folder’, ‘destination/folder’)	
GrooScript.convert(listOfFilesAndOrFolders, ‘destination/folder’)
Conversion options
convertDependencies - boolean - default true
classPath - List<String> or String - default null - Ex: ‘src/groovy’
customization - Closure - default null
mainContextScope - List<String> - default null
initialText / finalText - String - default null
recursive - boolean - default false
//Set a conversion property	
GrooScript.setConversionProperty(‘convertDependencies’, false)	
GrooScript.setConversionProperty(‘customization’, {	
	 ast(TypeChecked)	
})
Convert dependencies option
Car.groovy
Wheel.groovy
Car.js
convert
== option
wheel
code
included
Main context scope
Customization
• Requires Groovy 2.1, the closure is passed to withConfig(conf)
• More info in http://docs.codehaus.org/display/GROOVY/Advanced+compiler+configuration
• Sometimes you don’t know where to find a variable
• When conversion is done, some variables come from other contexts
• grooscript.js sometimes tries to find missed variables with eval
• Node.js eval not working same way that browsers do
• You can define variables or function names with this option
• Ex. [‘$’, ‘myAwesomeFunction’, ‘myMissedVariable’]
Feature: Annotations
@GsNotConvert @GsNative
Feature: Daemon
>phantomjs myTest.js
Inject
grooscript.js
and jquery
Use Groovy
script abilities
I want to
be Groovy
@AST
http://phantomjs.org/
Please
code in
Groovy!
Mandatory Screen capture Before start test
More console info
Accept basic parameters
Works fine in GroovyTestCase
Not so well in Spock
Have to
define
Phantom.js
path
Feature: PhantomJsTest
Example
@GrabConfig(systemClassLoader = true)	
@Grab(‘org.grooscript:grooscript:0.5’)	
!
import org.grooscript.asts.PhantomJsTest	
!
//Need phantoms installed	
System.setProperty(‘PHANTOMJS_HOME’, ‘path/to/phantomjs’)	
!
@PhantomJsTest(url = ‘http://www.grails.org’)	
void grailsSiteChecks() {	
	 assert $(‘a’).size() > 50, “Not enough links ${$(‘a’).size()}”	
	 def title = $(“title”)	
	 assert title[0].text == ‘Grails - The search is over.’	
	 $(‘a’).each {	
	 	 println it	
	 }	
}	
!
//Run phantomjs test	
grailsSiteChecks()
package org.grooscript.builder	
!
class HtmlBuilder {	
!
String html	
!
static String build(@DelegatesTo(HtmlBuilder) Closure closure) {	
def mc = new ExpandoMetaClass(HtmlBuilder, false, true)	
mc.initialize()	
def builder = new HtmlBuilder()	
builder.metaClass = mc	
closure.delegate = builder	
closure()	
!
builder.html	
}	
!
def yield(String text) {	
html += text	
}	
!
def methodMissing(String name, args) { … }	
}	
Feature: html builder
def result = HtmlBuilder.build {	
body {	
ul(class: 'list', id: 'mainList') {	
2.times { number ->	
li number + 'Hello!'	
}	
}	
}	
}	
!
assert result == ‘<body><ul class='list'
id='mainList'><li>0Hello!</li><li>1Hello!</
li></ul></body>’
grooscript-builder.js
Feature: jquery tools
package org.grooscript.jquery	
!
interface GQuery {	
def bind(String selector, target, String nameProperty)	
def bind(String selector, target, String nameProperty, 	
	 	 Closure closure)	
boolean existsId(String id)	
boolean existsName(String name)	
boolean existsGroup(String name)	
void bindEvent(String id, String name, Closure func)	
void doRemoteCall(String url, String type, params, 	
	 	 Closure onSuccess, Closure onFailure)	
void doRemoteCall(String url, String type, params, 	
	 	 Closure onSuccess, Closure onFailure, objectResult)	
void onReady(Closure func)	
void html(String selector, String text)	
}
import org.grooscript.jquery	
!
GQuery gQuery = new GQueryImpl()
gQueryImpl.js
Feature: binder
grooscript-binder.js
package org.grooscript.jquery	
!
class Binder {	
!
GQuery gQuery = new GQueryImpl()	
!
def bindAllProperties(target, closure = null) {	
target.properties.each { name, value ->	
if (gQuery.existsId(name)) {	
gQuery.bind("#$name", target, name, closure)	
}	
if (gQuery.existsName(name)) {	
gQuery.bind("[name='$name']", target, name, closure)	
}	
if (gQuery.existsGroup(name)) {	
gQuery.bind("input:radio[name=${name}]", target, name, closure)	
}	
}	
}	
!
def bindAllMethods(target) {	
target.metaClass.methods.each { method ->	
if (method.name.endsWith('Click')) {	
def shortName = method.name.substring(0, method.name.length() - 5)	
if (gQuery.existsId(shortName)) {	
gQuery.bindEvent(shortName, 'click', target.&"${method.name}")	
}	
}	
}	
}	
!
def call(target, closure = null) {	
bindAllProperties(target, closure)	
bindAllMethods(target)	
}	
}
Bind an instance properties to
input elements, by Id or name
Bind on click events to an
instance methods
new Binder()(myInstance)
grooscript-binder.js
grooscript.js
grooscript.min.js
grooscript-builder.js
grooscript-all.js
gQueryImpl.js
inside the jar, servlet 3.0: META-INF/resources
http://grooscript.org/downloads.html
Websockets with Vert.x
https://github.com/chiquitinxx/demoGroovyMeteor
http://www.meteor.com
https://vimeo.com/59395085
https://github.com/chiquitinxx/grooscript-vertx-plugin
http://www.grails.org/plugin/grooscript-vertx
v 0.4
Grooscript Vert.x Plugin
Convert Groovy code to Javascript
Run conversion daemon
Websockets
Eventbus bridge
New port open
Events in the client
Auto reload pages
Both are optional
http://grooscript.org/pluginManual/
v 1.3.1
Requires Java 1.7
Differences?Renders
on server
Renders
on the client
Conversions are cached with cache plugin
Grooscript tags auto - import js files needed
Grails
port 8080
Vert.x
port 8085
Browser
gsp
eventBus
http
websockets
Config.groovy
BootStrap.groovy Chat sample
main.gsp
More eventsHtml Builder
Where
Listen events Render on load
Send event message
= println
Execute on event message
Don’t use ${} in grooscript tags
Strong dependency Resources plugin
Can put code in a .groovy file
Domain classes in the client*
*Experimental, it requires Groovy 2.1 (grails 2.3)
• validate, clientValidations **
• hasErrors
• count
• list * without params
• get
• save * without params
• delete
Domain classes connected with the server*
*Experimental, it requires Groovy 2.1 (grails 2.3)
• list
• get
• save
• delete
PhantomJs Tests*
*Not working in Grails 2.3, need improvements
New test phase phantomjs
Tests in test/phantomjs
More features
features, features…
next release - new plugin - remove vert.x
From resources plugin to Require.js or Asset pipeline plugin
Improve domain options
PhantomJs tests improvements
Move to websockets/sockJs/stomp with Spring 4 in grails 2.4
and many more…
Remote domain class to grails REST support
https://github.com/chiquitinxx/grooscript-gradle-plugin
v 0.3
Gradle plugin
Add in your build.gradle
http://grooscript.wordpress.com/2014/02/22/starting-with-grooscript/
buildscript {	
repositories {	
mavenCentral()	
}	
dependencies {	
classpath 'org.grooscript:grooscript-gradle-plugin:0.3'	
}	
}	
!
apply plugin: 'grooscript'	
!
//If you need to change any conversion option, can do this way, optional	
grooscript {	
source = [‘src/main/groovy/presenters']	
}
Create more conversion tasks: http://grooscript.wordpress.com/2014/01/31/61/
Converted files destination
Require.js setup file
Files to be converted
Demo springboot
http://projects.spring.io/spring-boot/
REST - Require.js - Grooscript Gradle Plugin - Websockets - Mongo
https://github.com/chiquitinxx/springboot-rest-demo
https://github.com/chiquitinxx/grooscript/tree/master/npm
Example using node.js colors npm module
Node.js is very fast!
http://grooscript.wordpress.com/2014/01/10/impressive-node-js-v8-speed/
https://github.com/chiquitinxx/colors
Final topics
Why would I use grooscript?
• Create small modules to use in your views
• You can continue developing in Groovy
• Can use dsl’s, typeCheck, AST’s,… in the browser
• You have all the java tools and IDE’s
• You can work with new Javascript tools from Groovy
• Don’t repeat code in two languages
• Single development environment
• Create your own architecture in Groovy
• Don’t learn another “to Javascript” tool
Community will decide
Future
Version 0.5, you can improve
I will continue improving grooscript
Never will support full Groovy / Java
I’m learning a lot, very fun
New grails plugin in summer
Focus next version: more and better java and groovy support
Questions?
Thank you!
All people reading this
680 motivation clicks
Jetbrains for IntelliJ IDEA v13 open source license
Special thanks to René, @glaforge @marioggar
@CedricChampeau @burtbeckwith

Grooscript gr8conf

  • 1.
  • 2.
    About me Developer, Ilove it Lazy in english at school Living in Madrid Working at Osoco Grooscript developer @jfrancoleza jorge.franco.leza@gmail.com
  • 3.
  • 4.
    Web developer Leave your comfortzone Move to the client side
  • 6.
  • 7.
    Do something! No excuses Youcan do a library, resolve issues, create a plugin, give feedback, help other projects, … ! You will learn a lot
  • 8.
  • 9.
    Groovy to Javascriptconverter Groovy 2 to Javascript ECMAScript 5 Open source project, Apache 2 license Library with Groovy and GPars dependencies No special Javascript objects or functions Converted code requires grooscript.js to run Different conversion options Grails plugin, npm package, gradle plugin What is grooscript?
  • 10.
  • 11.
    Groovy code def sayHello= { -> println "Hello ${it}!" } [‘Groovy', ‘JavaScript', 'GrooScript'].each sayHello ! assert [1, 2, 3].size() == 3 class Amazing {} ! amazing = new Amazing() amazing.metaClass.who = [] amazing.metaClass.add = { who << it} ! amazing.add 'Rafa Nadal' assert amazing.who.size() == 1
  • 12.
    Javascript result function Amazing(){ var gSobject = gs.inherit(gs.baseClass,'Amazing'); gSobject.clazz = { name: 'Amazing', simpleName: 'Amazing'}; gSobject.clazz.superclass = { name: 'java.lang.Object', simpleName: 'Object'}; if (arguments.length == 1) {gs.passMapToObject(arguments[0],gSobject);}; return gSobject; }; var sayHello = function(it) { return gs.println("Hello " + (it) + "!"); }; gs.mc(gs.list(["Groovy" , "JavaScript" , "GrooScript"]),"each",[sayHello]); gs.assert(gs.equals(gs.mc(gs.list([1 , 2 , 3]),"size",[]), 3), null); amazing = Amazing(); gs.sp((amazing = gs.metaClass(amazing)),"who",gs.list([])); gs.sp((amazing = gs.metaClass(amazing)),"add",function(it) { return gs.mc(gs.fs('who', this),'leftShift', gs.list([it])); }); gs.mc(amazing,"add",["Rafa Nadal"]); gs.assert(gs.equals(gs.mc(gs.gp(amazing,"who"),"size",[]), 1), null); Class Default constructor Function New
  • 13.
    Javascript Groovy Java MoreJavascriptfriendly Types, inheritance, java8, … metaClass Lists Closures Operators Dsl’s Expando Maps beans methodMissing Mixins Categories Traits delegate propertyMissing
  • 14.
    Using converted codein javascript Create classes NameClass(); Default constructor with js object NameClass({property: value, data: 1}); Call methods, basic parameters myObject.method(params); Access properties myObject.property = 4; Use functions as closures list.each(function () { … }); Traits Metaprogramming Mixins, Categories Groovy types gs.map({}); gs.set(); gs.range(1, 5); gs.date(); gs.regExp(); gs.stringBuffer(); … Groovy list functions added to js lists Added some functions to numbers and strings (1).times(function () {}); “Groovy”.startsWith(); “Hello”.replaceAll(); Added curry to functions Convert objects ‘to groovy’ or ‘to javascript’ gs.toGroovy(javaScriptList, NameClass); gs.toJavascript(myGroovyObject); Operators Groovy truth equals getters and setters Conversions gs.toGroovy(obj, Type); gs.toJavascript();
  • 15.
    Limitations Groovy / Javanot fully supported Working in groovy-core No good support for inheritance (super) No methods with same name Basic support in metaClass No packages, no classes with same name No metainfo, expandoMetaClass, … Only AST transformations applied in semantic phase No complex Java / Groovy types (LinkedList,…) … see documentation for more info on grooscript.org
  • 16.
    Directly convert code @Grab(‘org.grooscript:grooscript:0.5’) ! importorg.grooscript.GrooScript ! //Convert a fragment code String jsResult = GrooScript.convert(“println ‘Hello!’”) ! //Convert some file or folder, generate .js files with same name GrooScript.convert(‘path/to/file.groovy’, ‘destination/folder’) GrooScript.convert(‘path/to/folder’, ‘destination/folder’) GrooScript.convert(listOfFilesAndOrFolders, ‘destination/folder’)
  • 17.
    Conversion options convertDependencies -boolean - default true classPath - List<String> or String - default null - Ex: ‘src/groovy’ customization - Closure - default null mainContextScope - List<String> - default null initialText / finalText - String - default null recursive - boolean - default false //Set a conversion property GrooScript.setConversionProperty(‘convertDependencies’, false) GrooScript.setConversionProperty(‘customization’, { ast(TypeChecked) })
  • 18.
  • 19.
    Main context scope Customization •Requires Groovy 2.1, the closure is passed to withConfig(conf) • More info in http://docs.codehaus.org/display/GROOVY/Advanced+compiler+configuration • Sometimes you don’t know where to find a variable • When conversion is done, some variables come from other contexts • grooscript.js sometimes tries to find missed variables with eval • Node.js eval not working same way that browsers do • You can define variables or function names with this option • Ex. [‘$’, ‘myAwesomeFunction’, ‘myMissedVariable’]
  • 20.
  • 21.
  • 22.
    >phantomjs myTest.js Inject grooscript.js and jquery UseGroovy script abilities I want to be Groovy @AST http://phantomjs.org/ Please code in Groovy!
  • 23.
    Mandatory Screen captureBefore start test More console info Accept basic parameters Works fine in GroovyTestCase Not so well in Spock Have to define Phantom.js path Feature: PhantomJsTest
  • 24.
    Example @GrabConfig(systemClassLoader = true) @Grab(‘org.grooscript:grooscript:0.5’) ! importorg.grooscript.asts.PhantomJsTest ! //Need phantoms installed System.setProperty(‘PHANTOMJS_HOME’, ‘path/to/phantomjs’) ! @PhantomJsTest(url = ‘http://www.grails.org’) void grailsSiteChecks() { assert $(‘a’).size() > 50, “Not enough links ${$(‘a’).size()}” def title = $(“title”) assert title[0].text == ‘Grails - The search is over.’ $(‘a’).each { println it } } ! //Run phantomjs test grailsSiteChecks()
  • 25.
    package org.grooscript.builder ! class HtmlBuilder{ ! String html ! static String build(@DelegatesTo(HtmlBuilder) Closure closure) { def mc = new ExpandoMetaClass(HtmlBuilder, false, true) mc.initialize() def builder = new HtmlBuilder() builder.metaClass = mc closure.delegate = builder closure() ! builder.html } ! def yield(String text) { html += text } ! def methodMissing(String name, args) { … } } Feature: html builder def result = HtmlBuilder.build { body { ul(class: 'list', id: 'mainList') { 2.times { number -> li number + 'Hello!' } } } } ! assert result == ‘<body><ul class='list' id='mainList'><li>0Hello!</li><li>1Hello!</ li></ul></body>’ grooscript-builder.js
  • 26.
    Feature: jquery tools packageorg.grooscript.jquery ! interface GQuery { def bind(String selector, target, String nameProperty) def bind(String selector, target, String nameProperty, Closure closure) boolean existsId(String id) boolean existsName(String name) boolean existsGroup(String name) void bindEvent(String id, String name, Closure func) void doRemoteCall(String url, String type, params, Closure onSuccess, Closure onFailure) void doRemoteCall(String url, String type, params, Closure onSuccess, Closure onFailure, objectResult) void onReady(Closure func) void html(String selector, String text) } import org.grooscript.jquery ! GQuery gQuery = new GQueryImpl() gQueryImpl.js
  • 27.
    Feature: binder grooscript-binder.js package org.grooscript.jquery ! classBinder { ! GQuery gQuery = new GQueryImpl() ! def bindAllProperties(target, closure = null) { target.properties.each { name, value -> if (gQuery.existsId(name)) { gQuery.bind("#$name", target, name, closure) } if (gQuery.existsName(name)) { gQuery.bind("[name='$name']", target, name, closure) } if (gQuery.existsGroup(name)) { gQuery.bind("input:radio[name=${name}]", target, name, closure) } } } ! def bindAllMethods(target) { target.metaClass.methods.each { method -> if (method.name.endsWith('Click')) { def shortName = method.name.substring(0, method.name.length() - 5) if (gQuery.existsId(shortName)) { gQuery.bindEvent(shortName, 'click', target.&"${method.name}") } } } } ! def call(target, closure = null) { bindAllProperties(target, closure) bindAllMethods(target) } } Bind an instance properties to input elements, by Id or name Bind on click events to an instance methods new Binder()(myInstance)
  • 28.
  • 29.
  • 30.
  • 31.
    Grooscript Vert.x Plugin ConvertGroovy code to Javascript Run conversion daemon Websockets Eventbus bridge New port open Events in the client Auto reload pages Both are optional http://grooscript.org/pluginManual/ v 1.3.1 Requires Java 1.7
  • 32.
    Differences?Renders on server Renders on theclient Conversions are cached with cache plugin Grooscript tags auto - import js files needed
  • 33.
  • 34.
    More eventsHtml Builder Where Listenevents Render on load Send event message = println Execute on event message Don’t use ${} in grooscript tags Strong dependency Resources plugin Can put code in a .groovy file
  • 35.
    Domain classes inthe client* *Experimental, it requires Groovy 2.1 (grails 2.3) • validate, clientValidations ** • hasErrors • count • list * without params • get • save * without params • delete
  • 36.
    Domain classes connectedwith the server* *Experimental, it requires Groovy 2.1 (grails 2.3) • list • get • save • delete
  • 37.
    PhantomJs Tests* *Not workingin Grails 2.3, need improvements New test phase phantomjs Tests in test/phantomjs More features
  • 38.
    features, features… next release- new plugin - remove vert.x From resources plugin to Require.js or Asset pipeline plugin Improve domain options PhantomJs tests improvements Move to websockets/sockJs/stomp with Spring 4 in grails 2.4 and many more… Remote domain class to grails REST support
  • 39.
  • 40.
    Gradle plugin Add inyour build.gradle http://grooscript.wordpress.com/2014/02/22/starting-with-grooscript/ buildscript { repositories { mavenCentral() } dependencies { classpath 'org.grooscript:grooscript-gradle-plugin:0.3' } } ! apply plugin: 'grooscript' ! //If you need to change any conversion option, can do this way, optional grooscript { source = [‘src/main/groovy/presenters'] }
  • 41.
    Create more conversiontasks: http://grooscript.wordpress.com/2014/01/31/61/ Converted files destination Require.js setup file Files to be converted
  • 42.
    Demo springboot http://projects.spring.io/spring-boot/ REST -Require.js - Grooscript Gradle Plugin - Websockets - Mongo https://github.com/chiquitinxx/springboot-rest-demo
  • 43.
  • 44.
    Example using node.jscolors npm module Node.js is very fast! http://grooscript.wordpress.com/2014/01/10/impressive-node-js-v8-speed/ https://github.com/chiquitinxx/colors
  • 45.
  • 46.
    Why would Iuse grooscript? • Create small modules to use in your views • You can continue developing in Groovy • Can use dsl’s, typeCheck, AST’s,… in the browser • You have all the java tools and IDE’s • You can work with new Javascript tools from Groovy • Don’t repeat code in two languages • Single development environment • Create your own architecture in Groovy • Don’t learn another “to Javascript” tool
  • 47.
    Community will decide Future Version0.5, you can improve I will continue improving grooscript Never will support full Groovy / Java I’m learning a lot, very fun New grails plugin in summer Focus next version: more and better java and groovy support
  • 48.
  • 49.
    Thank you! All peoplereading this 680 motivation clicks Jetbrains for IntelliJ IDEA v13 open source license Special thanks to René, @glaforge @marioggar @CedricChampeau @burtbeckwith