[![CircleCI](https://circleci.com/gh/effective-spa/jsrb.svg?style=svg)](https://circleci.com/gh/effective-spa/jsrb) [![codecov](https://codecov.io/gh/effective-spa/jsrb/branch/master/graph/badge.svg)](https://codecov.io/gh/effective-spa/jsrb) [![Maintainability](https://api.codeclimate.com/v1/badges/8bf4666ed7b983f01519/maintainability)](https://codeclimate.com/github/effective-spa/jsrb/maintainability) [![Gem Version](https://badge.fury.io/rb/jsrb.svg)](https://badge.fury.io/rb/jsrb) # Jsrb Jsrb is a template engine to generate JavaScript code in simple Ruby DSL. # Getting Started Jsrb handler works in jsrb files, which contain `.js.jsrb` extension. All ruby syntax is available and `jsrb` is provided in it. You can construct JavaScript code via `jsrb`. ```rb name = jsrb.var!(:name) { 'foo' } ary = jsrb.var! :ary obj = jsrb.var! :obj result = jsrb.var! # var name = 'foo'; # var ary; # var obj; # var _v1; <- auto generate variable name ary.set!([1, 2, 3]) # ary = [1, 2, 3]; obj.set!( name: name, profile: { age: 20, sex: 'm' } ) # obj = { # name: name, # profile: { # age: 20, # sex: 'm' # } # }; result.set! (obj.name + "(" + obj.profile.age + ")") # _v1 = obj.name + "(" + obj.profile.age + ")"; ary.set! ary.map { |x| x * 2 } # ary = ary.map(function(x) { # return x * 2; # }); jsrb.if!(ary[1] === 4) { result.set! 'four' }.elsif(ary[1] === 2) { result.set! 'two' }.else { result.set! 'other' } # // the actual output doesn't looks like this, but will be better code in regard to variable scope. # if (ary[1] === 4) { # _v1 = 'four' # } else if (ary[1] === 2) { # _v1 = 'two' # } else { # _v1 = 'other' # } result.set! jsrb.expr.Date.new! # _v1 = new Date; jsrb.expr.console.log('hello').as_statement! # console.log('hello'); ``` # Usage ## Statements ### Variable declaration `jsrb.var!` pushes a **VariableDeclaration** into current context. ```rb # with variable name and initializer jsrb.var!('varname') { 100 } # var varname = 100; # without initializer jsrb.var!('varname') # var varname; # variable name is auto-generated if not specified jsrb.var! # var _v1; # var! returns Jsrb::ExprChain instance, so that you can # assign value with `.set!` method. a = jsrb.var! a.set! 100 # var _v1; # v1 = 100; ``` ### If statement, and conditional expression `jsrb.if!` pushes an **IfStatement** into current context, `jsrb.if` to build **a conditional expression**. ```rb # start with `#if!` # and chain `#elsif` to add next case. # Note that this is a statement, not expression. jsrb.if!(v === 1) { # .. }.elsif(v === 2) { # .. }.else { # .. } # if you don't need else clause, close with `#end` jsrb.if!(v === 1) { # .. }.end # if you want to regard this as an expression, use `#if` without exclamation. v.set! jsrb.if(v === 1) { # .. }.else { # .. } ``` ### Assignment statement `ExprChain#set!` pushes an **assignment statement** (ExpressionStatement of AssignmentExpression). ```rb a = jsrb.var! :a a.set! 100 # var a; # a = 100; ``` ### Expression statement `ExprChain#as_statement!` pushes an **ExpressionStatement** of the left hand side of chain. ```rb get_elements = jsrb.expr['getElements'] get_elements.('.foo').forEach { |n| n.delete.() }.as_statement! # getElements('.foo').forEach(function(n) { return n.delete(); }); ``` ## Expression chain Expression chain (`ExprChain` class) is an utility class to construct JavaScript expressions. ### Initialize with wrapping a ruby value `jsrb.expr` create a new `ExprChain` instance, taking an initial value optionally. Some methods in jsrb automatically convert ruby expression to ExprChain. ```rb x = jsrb.var! :x x.set! jsrb.expr(100) # x = 100; # set! automatically wrap with ExprChain. x.set! 100 # x = 100; # If you need to compare by operator with another ExprChain, # you have to wrap first. x.set!(jsrb.expr(100) < jsrb.expr.y) # x.set!(100 < jsrb.expr.y) will fail. ``` ### Chains #### Member expression `ExprChain#[], #member!, #..` constructs **MemberExpression**. `#[]` and `#member!` is safe. `#..` can be used only if the name has no conflict. ```rb x = jsrb.var! :x obj = jsrb.expr['someObj'] # jsrb.expr with no argument constructs empty chain, # in which every member chain will be an identifier. x.set! obj.field # x = someObj['field']; x.set! obj['field'] # x = someObj['field']; x.set! obj.member! 'field' # x = someObj['field']; x.set! obj.send # NOTE that this is interpreted as a ruby Object's method, and causes an error. ``` #### Function Call `ExprChain#call, so #.(), #.. with argument or block` constructs **CallExpression**. ```rb x = jsrb.var! :x console = jsrb.expr['console'] # using call method console.log.('foo').as_statement! # console.log('foo') console.log.call('foo').as_statement! # console.log('foo') # using dynamic method # if #..() has at least one argument or block, it will be a call expression. console.log('foo').as_statement! # console.log('foo') x.map { |item| item.field }.as_statement! # x.map(function(item) { return item.field; }); ``` #### Operators Any ruby-overridable and JS-existing operators are overridden for chaining. Supported operators are: `** + - * / % >> << & ^ | <= < > >= == === != ! && ||`. ```rb x = jsrb.var! :x a = jsrb.expr['a'] x.set!(a === 1) # x = a === 1; x.set!(a * a) # x = a * a; x.set!(3 * a) # raises an error because Fixnum does not accept ExprChain as RHS. ``` #### New `ExprChain#new!` constructs **NewExpression**. ```rb x = jsrb.var! :x x.set! jsrb.expr['Date'].new! # x = new Date; ``` #### Function expression `ExprChain#for_all!` constructs **FunctionExpression**. You can also construct it from Proc directly or passing a block. ```rb ary = jsrb.var! :ary, [1, 2, 3] ary.map((jsrb.expr['x'] * 2).for_all!('x')).as_statement! # ary.map(function(x) { return x * 2; }); ary.map { |x| x * 2 }.as_statement! # ary.map(function(x) { return x * 2; }); ary.map(->(x) { x * 2 }).as_statement! # ary.map(function(x) { return x * 2; }); ``` # Conversion Every Ruby's generic value will be converted to Javascript value by following rule: |Ruby value|JavaScript| |---|---| | finite `100` | `100` | | NaN `Float::NAN` | `NaN` | | +Infinity `Float::INFINITY` | `Number.POSITIVE_INFINITY` | | -Infinity `Float::INFINITY` | `Number.NEGATIVE_INFINITY` | | `true` | `true` | | `false` | `false` | | `:foo` | `"foo"` | | `"foo"` | `"foo"` | | `nil` | `null` | | `[1, 2, 3]` | `[1, 2, 3]` | | `{ foo: 'bar' }` | `{ "foo": "bar" }` | | `->(x, y, z) { x + y + z }` | `function (x, y, z) { return x + y + z; }` | # Customize Chain You can add custom chain methods in `ExprChain` via `Jsrb::ExprChain.#add_custom_chain`. ```rb Jsrb::ExprChain.add_custom_chain('log_here', '__tap_log__') jsrb.expr['foo']['bar'].log_here.as_statement! # __tap_log__(foo['bar']); ``` ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/effective-spa/jsrb. ## License The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).