diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e8fd5f6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: ci +on: push +# push: +# tags: +# - '*' +jobs: + ci: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + - name: RSpec + run: bundle exec rspec spec/ + - name: Release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + files: | + dist/dev-unmanual-${{ github.ref }}.pdf% \ No newline at end of file diff --git a/.gitignore b/.gitignore index 39f02f3..42eff0f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ pkg/* .bundle Gemfile.lock .ruby-gemset -.ruby-version \ No newline at end of file +vendor/ diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..460b6fd --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.7.5 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 14ac5c0..0000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: ruby -rvm: - - 2.1.0 - - 2.0.0 - - 1.9.3 -before_install: - - gem update bundler diff --git a/Changelog.md b/Changelog.md index 9282784..cd94277 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,4 +1,17 @@ +# 0.1.0 + +* Smolbars rename +* Replace therubyracer with mini_racer + * No more invoking Ruby from JS + * We build templates through string concatentation. Do not pass in untrusted handlebars templates. + * All data passed to JS environment is serialized with JSON + * Remove support for partial_missing, it's deprecated since handlebars 4.3.0 + * Remove precompiling (doesn't seem useful in the ruby setting) + * Remove setting data from ruby (use eval and do it yourself) + * Remove support for SafeString (use eval and do it yourself) + # 0.8.0 + * bumped handlebars-source version to 4.0.5 # 0.2.3 diff --git a/Gemfile b/Gemfile index c9e384a..ec5b991 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,3 @@ source "https://rubygems.org" -# Specify your gem's dependencies in handlebars.gemspec +# Specify your gem's dependencies in smolbars.gemspec gemspec diff --git a/README.mdown b/README.mdown index 1ac5db5..1491953 100644 --- a/README.mdown +++ b/README.mdown @@ -1,123 +1,87 @@ -## Handlebars.rb +## Smolbars -[![Gem Version](https://badge.fury.io/rb/handlebars.png)](http://badge.fury.io/rb/handlebars) +[![Gem Version](https://badge.fury.io/rb/smolbars.png)](http://badge.fury.io/rb/handlebars) [![Build Status](https://travis-ci.org/cowboyd/handlebars.rb.png?branch=master)](https://travis-ci.org/cowboyd/handlebars.rb) [![Dependency Status](https://gemnasium.com/cowboyd/handlebars.rb.png)](https://gemnasium.com/cowboyd/handlebars.rb) -This uses [therubyracer][1] to bind to the _actual_ JavaScript implementation of -[Handlebars.js][2] so that you can use it from ruby. +This uses [mini_racer][1] to bind to the _actual_ JavaScript implementation of +[Handlebars.js][2] so that you can use it from ruby. This is a fork of [handlebars.rb][3] to +change out the deprecated [therubyracer][4] JS integration. 99% the same idea as the better-named +[minibars][5]. + +Please be mindful of how this library works: it brings in the full libv8 JS VM to your ruby environment. Each +`Context` is a full blown JS machine (memory management, JIT, etc). This fork does not support attaching ruby +functions to the JS VM. + +Note on security: do not compile untrusted Handlebars templates. We compile Handlebars template by building ad-hoc +javascript statements, a bad actor could perform an SQL-injection like attack using the v8 environment for bad things. ## Usage ### Simple stuff - require 'handlebars' - handlebars = Handlebars::Context.new - template = handlebars.compile("{{say}} {{what}}") + require 'smolbars' + smolbars = Smolbars::Context.new + template = smolbars.compile("{{say}} {{what}}") template.call(:say => "Hey", :what => "Yuh!") #=> "Hey Yuh!" -### functions as properties - - template.call(:say => "Hey ", :what => lambda {|this| ("yo" * 2) + "!"}) #=> "Hey yoyo!" - -### Block Helpers: - -Just like JavaScript, you can write block helpers with an `{{else}}` section. To print -out a section twice if a condition is met: - - # V8 maps the first argument sent to a block to "this". All subsequent arguments are as - # described in the Handlebars documentation. - handlebars.register_helper(:twice) do |context, condition, block| - if condition - "#{block.fn(context)}#{block.fn(context)}" - else - block.inverse(context) - end - end - template = handlebars.compile("{{#twice foo}}Hurray!{{else}}Boo!{{/twice}}") - template.call(foo: true) #=> Hurray!Hurray! - template.call(foo: false) #=> Boo! - -### Private variables: - -Just like JavaScript, block helpers can inject private variables into their child templates. -These can be accessed in a template using the `@` prefix: - - handlebars.register_helper(:list) do |this, context, block| - "" - end - template = handlebars.compile("{{#list array}}{{@index}}. {{title}}{{/list}}") - template.call(array: [{title: "Memento"}, {title: "Inception"}]) - #=> "" +### Helpers -### Hash arguments: +You must write helpers with JavaScript. The JavaScript code should include calls to the Handlebars class registration +function. -When using hash arguments, beware of one gotcha - V8 defines the #hash method for every -object. Therefore, to access the hash object of the options argument Handlebars sends to your -block, you must use the `[]` method: + require 'smolbars' + helper = %Q{ + Handlebars.registerHelper("nthTimes", function(n, options){ + var buffer = ""; - handlebars.register_helper :list do |this, context, options| - attrs = options[:hash].map{|k,v| "#{k}=\"#{v}\""}.join(' ') - "" - end - template = handlebars.compile(%({{#list nav id="nav-bar" class="top"}}{{title}}{{/list}})) - template.call({nav: [{url: 'www.google.com', title: 'Google'}]}) - #=> + for(var i = 0; i < n; i++) { + buffer += options.fn(); + } -### Safe Strings - -By default, handlebars will escape strings that are returned by your block helpers. To -mark a string as safe: - - template = handlebars.compile("{{safe}}") - template.call(:safe => proc {Handlebars::SafeString.new("
Totally Safe!
")})
+		  return buffer;
+		});
+	}
+	smolbars = Smolbars::Context.new
+	smolbars.eval(helper)
+	template = smolbars.compile('{{#nthTimes 2}}yep {{/nthTimes}}hurrah!')
+	template.call # 'yep yep hurrah!'
 
 ### Partials
 
-You can directly register partials
-
-    handlebars.register_partial("whoami", "I am {{who}}")
-    handlebars.compile("{{>whoami}}").call(:who => 'Legend') #=> I am Legend
-
-Partials can also be dynamically looked up by defining a partial_missing behavior:
+You must write partials with JavaScript. The JavaScript code should include calls to the Handlebars class registration
+function.
 
-    handlebars.partial_missing do |name|
-      "unable to find >#{name}"
-    end
-    handlebars.compile("{{>missing}}").call #=> unable to find >missing
-
-Missing partials can also be returned as a function:
-
-    count = 0
-    handlebars.partial_missing do |name|
-      lambda do |this, context, options|
-        count += 1
-        "#{count} miss(es) when trying to look up a partial"
-      end
-    end
-    t = handlebars.compile("{{>missing}}")
-    t.call #=> 1 miss(es) when trying to look up a partial
-    t.call #=> 2 miss(es) when tyring to look up a partial
+	require 'smolbars'
+	partial = %Q{
+		Handlebars.registerPartial("legend", "I am {{ who }}");
+	}
+	smolbars = Smolbars::Context.new
+	smolbars.eval(partial)
+	template = smolbars.compile('{{> legend}}')
+	template.call # 'I am Legend!'
 
 ### Security
 
 In general, you should not trust user-provided templates: a template can call any method
-(with no arguments) or access any property on any object in the `Handlebars::Context`.
+(with no arguments) or access any property on any object in the `Smolbars::Context`.
 
 If you'd like to render user-provided templates, you'd want to make sure you do so in a
 sanitized Context, e.g. no filesystem access, read-only or no database access, etc.
 
+You can try setting the timeout on a Smolbars::Context through kwargs that are passed to the
+underlying JS instance
+
+    Smolbars::Context.new(timeout: 500)
+
 ## Test
 
     rspec spec/
 
 
-[1]: http://github.com/cowboyd/therubyracer "The Ruby Racer"
-[2]: http://github.com/wycats/handlebars.js "Handlebars JavaScript templating library"
+[1]: https://github.com/rubyjs/mini_racer "mini_racer"
+[2]: https://github.com/wycats/handlebars.js "Handlebars JavaScript templating library"
+[3]: https://github.com/cowboyd/handlebars.rb "Handlebars Ruby library"
+[4]: https://github.com/cowboyd/therubyracer "The Ruby Racer"
+[5]: https://github.com/combinaut/minibars "Minibars"
\ No newline at end of file
diff --git a/handlebars.gemspec b/handlebars.gemspec
index a5db306..3249f74 100644
--- a/handlebars.gemspec
+++ b/handlebars.gemspec
@@ -1,19 +1,19 @@
-require "./lib/handlebars/version"
+require "./lib/smolbars/version"
 
 Gem::Specification.new do |s|
-  s.name        = "handlebars"
-  s.version     = Handlebars::VERSION
-  s.authors     = ["Charles Lowell"]
+  s.name        = "smolbars"
+  s.version     = Smolbars::VERSION
+  s.authors     = ["Charles Lowell", "Xavier Lange"]
   s.email       = ["cowboyd@thefrontside.net"]
-  s.homepage    = "https://github.com/cowboyd/handlebars.rb"
-  s.summary     = "Ruby bindings for the handlebars.js templating library"
-  s.description = "Uses the actual JavaScript implementation of Handlebars, but supports using Ruby objects as template contexts and Ruby procs as view functions and named helpers"
+  s.homepage    = "https://github.com/cowboyd/smolbars.rb"
+  s.summary     = "Ruby bindings for the smolbars.js templating library"
+  s.description = "Uses the actual JavaScript implementation of Handlebars"
   s.license     = "MIT"
 
   s.files         = `git ls-files lib README.mdown`.split("\n")
 
-  s.add_dependency "therubyracer", "~> 0.12.1"
-  s.add_dependency "handlebars-source", "~> 4.0.5"
+  s.add_dependency "mini_racer"
+  s.add_dependency "handlebars-source"
   s.add_development_dependency "rake"
   s.add_development_dependency "rspec", "~> 2.0"
 end
diff --git a/lib/handlebars.rb b/lib/handlebars.rb
deleted file mode 100644
index 6f97daf..0000000
--- a/lib/handlebars.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module Handlebars
-end
-
-require 'handlebars/context'
-require 'handlebars/template'
-require 'handlebars/partials'
-require 'handlebars/safe_string'
diff --git a/lib/handlebars/context.rb b/lib/handlebars/context.rb
deleted file mode 100644
index 520e5fb..0000000
--- a/lib/handlebars/context.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require 'handlebars/source'
-require 'v8'
-
-module Handlebars
-  class Context
-    def initialize
-      @js = V8::Context.new
-      @js['global'] = {} # there may be a more appropriate object to be used here @MHW
-      @js.load(Handlebars::Source.bundled_path)
-
-      @partials = handlebars.partials = Handlebars::Partials.new
-    end
-
-    def compile(*args)
-      ::Handlebars::Template.new(self, handlebars.compile(*args))
-    end
-
-    def load_helpers(helpers_pattern)
-      Dir[helpers_pattern].each{ |path| load_helper(path) }
-    end
-
-    def load_helper(path)
-      @js.load(path)
-    end
-
-    def precompile(*args)
-      handlebars.precompile(*args)
-    end
-
-    def register_helper(name, &fn)
-      handlebars.registerHelper(name, fn)
-    end
-
-    def register_partial(name, content)
-      handlebars.registerPartial(name, content)
-    end
-
-    def create_frame(data)
-      handlebars.createFrame(data)
-    end
-
-    def partial_missing(&fn)
-      @partials.partial_missing = fn
-    end
-
-    def handlebars
-      @js.eval('Handlebars')
-    end
-
-    def []=(key, value)
-      data[key] = value
-    end
-
-    def [](key)
-      data[key]
-    end
-
-    class << self
-      attr_accessor :current
-    end
-
-    private
-
-    def data
-      handlebars[:_rubydata] ||= handlebars.create()
-    end
-  end
-end
diff --git a/lib/handlebars/partials.rb b/lib/handlebars/partials.rb
deleted file mode 100644
index 300051d..0000000
--- a/lib/handlebars/partials.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-module Handlebars
-  class Partials
-    attr_accessor :partial_missing
-
-    def initialize
-      @partials = {}
-    end
-    
-    def []=(name, value)
-      @partials[name.to_s] = value
-    end
-    
-    def [](name)
-      if @partials.has_key?(name.to_s)
-        return @partials[name.to_s]
-      elsif @partial_missing
-        return @partial_missing[name]
-      else
-        yield
-      end
-    end
-  end
-end
\ No newline at end of file
diff --git a/lib/handlebars/safe_string.rb b/lib/handlebars/safe_string.rb
deleted file mode 100644
index 32c2f31..0000000
--- a/lib/handlebars/safe_string.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module Handlebars
-  class SafeString
-    def self.new(string)
-      if context = Context.current
-        context.handlebars['SafeString'].new(string)
-      else
-        fail "Cannot instantiate Handlebars.SafeString outside a running template Evaluation"
-      end
-    end
-  end
-end
\ No newline at end of file
diff --git a/lib/handlebars/template.rb b/lib/handlebars/template.rb
deleted file mode 100644
index f68a5dc..0000000
--- a/lib/handlebars/template.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module Handlebars
-  class Template
-    def initialize(context, fn)
-      @context, @fn = context, fn
-    end
-    
-    def call(*args)
-      current = Handlebars::Context.current
-      Handlebars::Context.current = @context
-      @fn.call(*args)
-    ensure
-      Handlebars::Context.current = current
-    end
-  end
-end
\ No newline at end of file
diff --git a/lib/handlebars/version.rb b/lib/handlebars/version.rb
deleted file mode 100644
index e056f24..0000000
--- a/lib/handlebars/version.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-module Handlebars
-  VERSION = "0.8.0"
-end
diff --git a/lib/smolbars.rb b/lib/smolbars.rb
new file mode 100644
index 0000000..e8a9616
--- /dev/null
+++ b/lib/smolbars.rb
@@ -0,0 +1,2 @@
+require 'smolbars/context'
+require 'smolbars/template'
diff --git a/lib/smolbars/context.rb b/lib/smolbars/context.rb
new file mode 100644
index 0000000..2c98dff
--- /dev/null
+++ b/lib/smolbars/context.rb
@@ -0,0 +1,43 @@
+require 'handlebars/source'
+require 'mini_racer'
+require 'securerandom'
+
+module Smolbars
+  class Context
+    def initialize(**kwargs)
+      @@snapshot ||= MiniRacer::Snapshot.new(File.read(Handlebars::Source.bundled_path))
+      @js = MiniRacer::Context.new(kwargs.merge(snapshot: @@snapshot))
+    end
+
+    # Note that this is a hacky JS expression builder. We cannot pass JS AST in to mini_racer so we have to
+    # hope the template passed in does not form invalid Ruby. So don't use templates with backtick characters without
+    # manually escaping them
+    def compile(template)
+      if template.include?("`")
+        raise RuntimeError.new("template cannot contain a backtick character '`'")
+      end
+      handle = fn_handle
+      invocation = %Q{var #{handle} = Handlebars.compile(`#{template}`);}
+      @js.eval(invocation)
+      ::Smolbars::Template.new(self, handle)
+    end
+
+    def eval(*args)
+      @js.eval(*args)
+    end
+
+    def load_pattern(pattern)
+      Dir[pattern].each{ |path| load(path) }
+    end
+
+    def load(path)
+      @js.load(path)
+    end
+
+    private
+
+    def fn_handle
+      "js_fn_#{SecureRandom.hex}"
+    end
+  end
+end
diff --git a/lib/smolbars/template.rb b/lib/smolbars/template.rb
new file mode 100644
index 0000000..f9a9f87
--- /dev/null
+++ b/lib/smolbars/template.rb
@@ -0,0 +1,17 @@
+module Smolbars
+  class Template
+    def initialize(context, fn)
+      @context, @fn = context, fn
+    end
+
+    def call(*args, **kwargs)
+      if args.length == 0
+        invocation = "%s(%s)" % [@fn, kwargs.to_json]
+      else
+        raise "unsupported"
+        invocation = "%s(%s)" % [@fn, args.to_json]
+      end
+      @context.eval(invocation)
+    end
+  end
+end
\ No newline at end of file
diff --git a/lib/smolbars/version.rb b/lib/smolbars/version.rb
new file mode 100644
index 0000000..39e40e4
--- /dev/null
+++ b/lib/smolbars/version.rb
@@ -0,0 +1,3 @@
+module Smolbars
+  VERSION = "0.1.0"
+end
diff --git a/spec/handlebars_spec.rb b/spec/handlebars_spec.rb
deleted file mode 100644
index cc84c63..0000000
--- a/spec/handlebars_spec.rb
+++ /dev/null
@@ -1,145 +0,0 @@
-require 'handlebars'
-describe(Handlebars::Context) do
-
-  describe "a simple template" do
-    let(:t) { compile("Hello {{name}}") }
-    it "allows simple subsitution" do
-      t.call(:name => 'World').should eql "Hello World"
-    end
-
-    it "allows Ruby blocks as a property" do
-      t.call(:name => lambda { |context| ; "Mate" }).should eql "Hello Mate"
-    end
-
-    it "can use any Ruby object as a context" do
-      t.call(double(:Object, :name => "Flipper")).should eql "Hello Flipper"
-    end
-  end
-
-  describe "allows Handlebars whitespace operator" do
-    let(:t) { compile("whitespace    {{~word~}}   be replaced.") }
-    it "consumes all whitespace characters before/after the tag with the whitespace operator" do
-      t.call(:word => "should").should eql "whitespaceshouldbe replaced."
-    end
-  end
-
-  describe "loading Helpers" do
-    before do
-      subject.load_helper('spec/sample_helper.js')
-    end
-
-    it "can call helpers defined in a javascript file" do
-      t = compile('{{#nthTimes 2}}yep {{/nthTimes}}hurrah!')
-      t.call.should eql 'yep yep hurrah!'
-    end
-  end
-
-  describe "registering Helpers" do
-    before do
-      subject.register_helper('alsowith') do |this, context, block|
-        block.fn(context)
-      end
-      subject.register_helper(:twice) do |this, block|
-        "#{block.fn}#{block.fn}"
-      end
-    end
-
-    it "correctly passes context and implementation" do
-      t = compile("it's so {{#alsowith weather}}*{{summary}}*{{/alsowith}}!")
-      t.call(:weather => {:summary => "sunny"}).should eql "it's so *sunny*!"
-    end
-
-    it "doesn't nee a context or arguments to the call" do
-      t = compile("{{#twice}}Hurray!{{/twice}}")
-      t.call.should eql "Hurray!Hurray!"
-    end
-  end
-
-  describe "registering Partials" do
-    before do
-      subject.register_partial('legend', 'I am {{who}}')
-    end
-    it "renders partials" do
-      compile("{{> legend}}").call(:who => 'Legend!').should eql "I am Legend!"
-    end
-  end
-
-  describe "dynamically loading partial" do
-    it "can be done with a string" do
-      subject.partial_missing do |name|
-        "unable to find >#{name}"
-      end
-      compile("I am {{>missing}}").call().should eql "I am unable to find >missing"
-    end
-
-    it "can be done with a function" do
-      subject.partial_missing do |name|
-        lambda do |this, context, options|
-          "unable to find my #{name} #{context.what}"
-        end
-      end
-      compile("I am {{>missing}}").call(:what => 'shoes').should eql "I am unable to find my missing shoes"
-    end
-  end
-
-  describe "creating safe strings from ruby" do
-    let(:t) { subject.compile("{{safe}}") }
-    it "respects safe strings returned from ruby blocks" do
-      t.call(:safe => lambda { |this, *args| Handlebars::SafeString.new("
totally safe
") }).should eql "
totally safe
" - end - end - - describe "context specific data" do - before { subject['foo'] = 'bar' } - it 'can be get and set' do - subject['foo'].should eql 'bar' - end - end - - describe "precompiling templates" do - let(:t) { precompile("foo {{bar}}") } - it "should compile down to javascript" do - t.should include 'function' - end - end - - describe "private variables" do - let(:t) {subject.compile("{{#list array}}{{@index}}. {{title}} {{@dummy}}{{/list}}")} - before do - subject.register_helper('list') do |this, context, block| - "" - end - end - it "sets the index variable correctly" do - t.call(:array => [{:title => "You are"}, {:title => "He is"}]).should == "" - end - end - - describe "hash arguments" do - let(:t) {subject.compile(%({{#list nav id="nav-bar" class="top"}}{{title}}{{/list}}))} - before do - subject.register_helper :list do |this, context, options| - attrs = options[:hash].sort.map{|k,v| "#{k}=\"#{v}\""}.join(' ') - "" - end - end - it "accepts hash attributes correctly" do - t.call({nav: [{url: 'url', title: 'title'}]}).should == %() - end - end - - def compile(*args) - subject.compile(*args) - end - - def precompile(*args) - subject.precompile(*args) - end -end diff --git a/spec/sample_helper.js b/spec/sample_helper.js index 38ae8a2..80cdcdb 100644 --- a/spec/sample_helper.js +++ b/spec/sample_helper.js @@ -7,3 +7,65 @@ Handlebars.registerHelper("nthTimes", function(n, options){ return buffer; }); + +// Sourced from lodash +// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt +/* eslint-disable func-style */ +function isFunction(value) { + return typeof value === 'function'; +} + +function isEmpty(value) { + if (!value && value !== 0) { + return true; + } else if (Array.isArray(value) && value.length === 0) { + return true; + } else { + return false; + } +} + +// Stolen from https://github.com/handlebars-lang/handlebars.js/blob/master/lib/handlebars/helpers/with.js +Handlebars.registerHelper("alsowith", function(context, options){ + if (arguments.length != 2) { + throw new Exception('#with requires exactly one argument'); + } + if (isFunction(context)) { + context = context.call(this); + } + + let fn = options.fn; + + if (!isEmpty(context)) { + let data = options.data; + + return fn(context, { + data: data, + blockParams: [context] + }); + } else { + return options.inverse(this); + } +}); + +Handlebars.registerHelper("twice", function(options){ + var buffer = ""; + + buffer += options.fn(); + buffer += options.fn(); + + return buffer; +}); + +Handlebars.registerHelper("list", function(context, options) { + attrs = Object.entries(options["hash"]).map(function([key,value]){ return key + "=" + '"' + value + '"'; }).join(" "); + return `" +}); + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +Handlebars.registerHelper("sleepy", async function(context) { + await sleep(context); +}); \ No newline at end of file diff --git a/spec/sample_partials.js b/spec/sample_partials.js new file mode 100644 index 0000000..615e30e --- /dev/null +++ b/spec/sample_partials.js @@ -0,0 +1 @@ +Handlebars.registerPartial("legend", "I am {{ who }}"); \ No newline at end of file diff --git a/spec/smolbars_spec.rb b/spec/smolbars_spec.rb new file mode 100644 index 0000000..b535980 --- /dev/null +++ b/spec/smolbars_spec.rb @@ -0,0 +1,93 @@ +require 'smolbars' +describe(Smolbars::Context) do + + describe "a simple template" do + let(:t) { compile("Hello {{name}}") } + it "allows simple substitution" do + t.call(:name => 'World').should eql "Hello World" + end + end + + describe "allows Handlebars whitespace operator" do + let(:t) { compile("whitespace {{~word~}} be replaced.") } + it "consumes all whitespace characters before/after the tag with the whitespace operator" do + t.call(:word => "should").should eql "whitespaceshouldbe replaced." + end + end + + describe "sanity check templates" do + let(:t) { compile("a very uncool `template`") } + it "should raise an exception on backticks in the template" do + expect { t }.to raise_error(RuntimeError) + end + + end + + describe "loading Helpers" do + before do + subject.load('spec/sample_helper.js') + end + + it "can call helpers defined in a javascript file" do + t = compile('{{#nthTimes 2}}yep {{/nthTimes}}hurrah!') + t.call.should eql 'yep yep hurrah!' + end + end + + describe "registering Helpers" do + before do + subject.load('spec/sample_helper.js') + end + + it "correctly passes context and implementation" do + t = compile("it's so {{#alsowith weather}}*{{summary}}*{{/alsowith}}!") + t.call(:weather => {:summary => "sunny"}).should eql "it's so *sunny*!" + end + + it "doesn't need a context or arguments to the call" do + t = compile("{{#twice}}Hurray!{{/twice}}") + t.call.should eql "Hurray!Hurray!" + end + end + + describe "registering partials" do + before do + subject.load('spec/sample_partials.js') + end + it "renders partials" do + compile("{{> legend}}").call(:who => 'Legend!').should eql "I am Legend!" + end + end + + describe "hash arguments" do + let(:t) {subject.compile(%({{#list nav id="nav-bar" class="top"}}{{title}}{{/list}}))} + before do + subject.load("spec/sample_helper.js") + end + it "accepts hash attributes correctly" do + t.call({nav: [{url: 'url', title: 'title'}]}).should == %() + end + end + + describe "timeout" do + subject { Smolbars::Context.new(timeout: 500) } + before do + subject.load("spec/sample_helper.js") + end + let(:t) {subject.compile(%({{#sleepy 1000}} time to sleep {{/sleepy}}))} + + it "should timeout" do + skip "can't write async smolbars helpers" + t.call.should == %() + end + + end + + def compile(*args) + subject.compile(*args) + end + + def precompile(*args) + subject.precompile(*args) + end +end diff --git a/spike.rb b/spike.rb deleted file mode 100644 index df6ce31..0000000 --- a/spike.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'handlebars' - -Handlebars.register_helper "table" do |block| - "#{block.call}
" -end - -Handlebars.register_helper "row" do |block| - "#{block.call}" -end - -t = Handlebars.compile <<-HBS -{{#table width}} - {{#row}}Hi{{/row}} -{{/table}} -HBS - -puts t.call \ No newline at end of file