Repository: bokeh/bokeh

Issue:
Add support for *.tsx source files
TSX is TypeScript's take on JSX, which are XML extensions to JavaScript (used mostly, but exclusively, with React). We can take advantage of the nice syntax, hook our own code for generating HTML/DOM, and use this as a more robust replacement for Eco templates. Eco templates are neither well defined (it's a mixture of HTML and CoffeeScript-ish kind of language) and won't work well together with TypeScript (Eco sets new attributes on instances of built-in `String`).



Discussion:

Resolving patch:
diff --git a/bokeh/util/compiler.py b/bokeh/util/compiler.py
--- a/bokeh/util/compiler.py
+++ b/bokeh/util/compiler.py
@@ -160,12 +160,6 @@ class JavaScript(Inline):
     def lang(self):
         return "javascript"
 
-class Eco(Inline):
-
-    @property
-    def lang(self):
-        return "eco"
-
 class Less(Inline):
 
     @property
@@ -183,16 +177,14 @@ def __init__(self, path):
     def lang(self):
         if self.file.endswith(".coffee"):
             return "coffeescript"
-        if self.file.endswith(".ts"):
+        if self.file.endswith((".ts", ".tsx")):
             return "typescript"
         if self.file.endswith(".js"):
             return "javascript"
-        if self.file.endswith(".eco"):
-            return "eco"
         if self.file.endswith((".css", ".less")):
             return "less"
 
-exts = (".coffee", ".ts", ".js", ".eco", ".css", ".less")
+exts = (".coffee", ".ts", ".tsx", ".js", ".css", ".less")
 
 class CustomModel(object):
     def __init__(self, cls):
diff --git a/examples/models/widgets.py b/examples/models/widgets.py
new file mode 100644
--- /dev/null
+++ b/examples/models/widgets.py
@@ -0,0 +1,102 @@
+from __future__ import print_function
+
+from datetime import date
+
+from bokeh.document import Document
+from bokeh.embed import file_html
+from bokeh.resources import INLINE
+from bokeh.util.browser import view
+from bokeh.models.layouts import Column, Row, WidgetBox
+from bokeh.models.widgets import (
+    Button, Toggle, Dropdown,
+    CheckboxGroup, RadioGroup,
+    CheckboxButtonGroup, RadioButtonGroup,
+    Icon,
+    TextInput, AutocompleteInput,
+    Select, MultiSelect,
+    Slider, RangeSlider, DateRangeSlider,
+    DatePicker,
+    Paragraph, Div, PreText,
+    Panel, Tabs,
+)
+from bokeh.plotting import figure
+from bokeh.sampledata.iris import flowers
+
+button = Button(label="Button (disabled) - still has click event", icon=Icon(icon_name="check"), button_type="primary", disabled=True)
+
+toggle = Toggle(label="Toggle button", button_type="success")
+
+menu = [("Item 1", "item_1_value"), ("Item 2", "item_2_value"), ("Item 3", "item_3_value")]
+dropdown = Dropdown(label="Dropdown button", button_type="warning", menu=menu, default_value="item_1_value")
+
+split_menu = [("Item 1", "item_1_value"), ("Item 2", "item_2_value"), None, ("Item 3", "item_3_value")]
+split = Dropdown(label="Split button", button_type="danger", menu=split_menu)
+
+checkbox_group = CheckboxGroup(labels=["Option 1", "Option 2", "Option 3"], active=[0, 1])
+
+radio_group = RadioGroup(labels=["Option 1", "Option 2", "Option 3"], active=0)
+
+checkbox_button_group = CheckboxButtonGroup(labels=["Option 1", "Option 2", "Option 3"], active=[0, 1])
+
+radio_button_group = RadioButtonGroup(labels=["Option 1", "Option 2", "Option 3"], active=0)
+
+icon = Icon(icon_name="check")
+
+text_input = TextInput(placeholder="Enter value ...")
+
+autocomplete_input = AutocompleteInput()
+
+select = Select(options=["Option 1", "Option 2", "Option 3"])
+
+multi_select = MultiSelect(options=["Option 1", "Option 2", "Option 3"])
+
+slider = Slider(value=10, start=0, end=100)
+
+range_slider = RangeSlider()
+
+#date_range_slider = DateRangeSlider(value=(date(2016, 1, 1), date(2016, 12, 31)))
+
+date_picker = DatePicker()
+
+paragraph = Paragraph(text="some text")
+
+div = Div(text="some <b>text</b>")
+
+pre_text = PreText(text="some text")
+
+def mk_tab(color):
+    plot = figure(width=300, height=300)
+    plot.scatter(flowers["petal_length"], flowers["petal_width"], color=color, fill_alpha=0.2, size=12)
+    return Panel(title="Tab 1: %s" % color.capitalize(), child=plot)
+
+tabs = Tabs(tabs=[mk_tab("red"), mk_tab("green"), mk_tab("blue")])
+
+widgets = Row(children=[
+    WidgetBox(children=[
+        button, toggle, dropdown, split,
+        checkbox_group, radio_group,
+        checkbox_button_group, radio_button_group,
+    ]),
+    WidgetBox(children=[
+        icon,
+        text_input, autocomplete_input,
+        select, multi_select,
+        slider, range_slider, #date_range_slider,
+        date_picker,
+        paragraph, div, pre_text,
+    ]),
+    WidgetBox(children=[
+        tabs,
+    ], width=400),
+])
+
+doc = Document()
+doc.add_root(widgets)
+
+if __name__ == "__main__":
+    doc.validate()
+    filename = "widgets.html"
+    with open(filename, "w") as f:
+        f.write(file_html(doc, INLINE, "Widgets"))
+    print("Wrote %s" % filename)
+    view(filename)


Test patch:
diff --git a/bokeh/util/tests/test_compiler.py b/bokeh/util/tests/test_compiler.py
--- a/bokeh/util/tests/test_compiler.py
+++ b/bokeh/util/tests/test_compiler.py
@@ -51,76 +51,6 @@ def test_nodejs_compile_javascript():
             message="';' expected.",
             text="some.js:1:26:';' expected."))
 
-def test_nodejs_compile_eco():
-    assert nodejs_compile("""<div><%= @value %></div>""", "eco", "some.eco") == \
-        dict(code="""\
-"use strict";
-function default_1(__obj) {
-    if (!__obj)
-        __obj = {};
-    var __out = [];
-    var __capture = function (callback) {
-        var out = __out, result;
-        __out = [];
-        callback.call(this);
-        result = __out.join('');
-        __out = out;
-        return __safe(result);
-    };
-    var __sanitize = function (value) {
-        if (value && value.ecoSafe) {
-            return value;
-        }
-        else if (typeof value !== 'undefined' && value != null) {
-            return __escape(value);
-        }
-        else {
-            return '';
-        }
-    };
-    var __safe = function (value) {
-        if (value && value.ecoSafe) {
-            return value;
-        }
-        else {
-            if (!(typeof value !== 'undefined' && value != null))
-                value = '';
-            var result = new String(value);
-            result.ecoSafe = true;
-            return result;
-        }
-    };
-    var __escape = function (value) {
-        return ('' + value)
-            .replace(/&/g, '&amp;')
-            .replace(/</g, '&lt;')
-            .replace(/>/g, '&gt;')
-            .replace(/"/g, '&quot;');
-    };
-    (function () {
-        (function () {
-            __out.push('<div>');
-            __out.push(__sanitize(this.value));
-            __out.push('</div>');
-        }).call(this);
-    }).call(__obj);
-    return __out.join('');
-}
-Object.defineProperty(exports, "__esModule", { value: true });
-exports.default = default_1;
-;
-""", deps=[])
-
-    assert nodejs_compile("""<div><%= @@value %></div>""", "eco", "some.eco") == \
-        dict(error=dict(
-            line=2,
-            column=24,
-            message=u'unexpected @',
-            text=u'some.eco:2:24:unexpected @',
-            extract=u'__out.push __sanitize @@value',
-            annotated='some.eco:2:24:unexpected @\n  __out.push __sanitize @@value\n                         ^'))
-
-
 def test_nodejs_compile_less():
     assert nodejs_compile(""".bk-some-style { color: mix(#ff0000, #0000ff, 50%); }""", "less", "some.less") == \
         dict(code=""".bk-some-style{color:#800080}""")