jsrb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d11a911afb7baddbd00ec547cd0b5679be9873a7
4
+ data.tar.gz: 0e28f797a62a8f8b640039b13f8ab54203e12602
5
+ SHA512:
6
+ metadata.gz: 31518c3f3adceb687937556bec2c598db034812662c48505616acd46462fed85a0ce1448b2aaea336ec3b3c5cf43605956079b3a1b6516a8035f6ce5265b837a
7
+ data.tar.gz: d29797e2528c20b83ed3f54573a0cde76f665b6aa9773f6860481187461c2ec1f597c9505dae97f4ef35204f519464a5d27ee095439deb136910b9290315f2fa
@@ -0,0 +1,32 @@
1
+ # Ruby CircleCI 2.0 configuration file
2
+ #
3
+ # Check https://circleci.com/docs/2.0/language-ruby/ for more details
4
+ #
5
+ version: 2
6
+ jobs:
7
+ build:
8
+ docker:
9
+ - image: circleci/ruby:2.5.1-node-browsers
10
+ working_directory: ~/repo
11
+ steps:
12
+ - checkout
13
+ - run:
14
+ name: install dependencies
15
+ working_directory: ~/repo
16
+ command: |
17
+ bundle install --jobs=4 --retry=3 --path vendor/bundle
18
+ - run:
19
+ name: run tests
20
+ working_directory: ~/repo
21
+ command: |
22
+ bundle exec rspec
23
+ - run:
24
+ name: run rubocop
25
+ working_directory: ~/repo
26
+ command: |
27
+ bundle exec rubocop
28
+ - run:
29
+ name: report coverage
30
+ working_directory: ~/repo
31
+ command: |
32
+ bash <(curl -s https://codecov.io/bash) -f coverage/.resultset.json
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /bin/
2
+ /.bundle/
3
+ /.yardoc
4
+ /Gemfile.lock
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,37 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'tmp/**/*'
4
+ - 'vendor/**/*'
5
+ TargetRubyVersion: 2.5
6
+
7
+ Metrics/LineLength:
8
+ Max: 130
9
+
10
+ Metrics/MethodLength:
11
+ Max: 30
12
+
13
+ Metrics/AbcSize:
14
+ Max: 25
15
+
16
+ Metrics/ClassLength:
17
+ Max: 200
18
+
19
+ Style/MissingRespondToMissing:
20
+ Enabled: false
21
+
22
+ Style/Documentation:
23
+ Enabled: false
24
+
25
+ Style/AccessModifierDeclarations:
26
+ Enabled: false
27
+
28
+ Style/NumericPredicate:
29
+ Enabled: false
30
+
31
+ Style/LambdaCall:
32
+ Exclude:
33
+ - 'spec/**/*'
34
+
35
+ Metrics/BlockLength:
36
+ Exclude:
37
+ - 'spec/**/*'
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.13.6
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in jsrb.gemspec
6
+ gemspec
7
+
8
+ group :development, :test do
9
+ gem 'pry-byebug'
10
+ gem 'rubocop', '~> 0.61.0'
11
+ gem 'simplecov'
12
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Shun MIZUKAMI
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,20 @@
1
+ [![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)
2
+
3
+ **This library is still development version. It's currently not recommended to be used in production environment.**
4
+
5
+ # Jsrb
6
+
7
+ Jsrb is a template engine to generate JavaScript code in simple Ruby DSL.
8
+
9
+ ## Usage
10
+
11
+ TODO
12
+
13
+ ## Contributing
14
+
15
+ Bug reports and pull requests are welcome on GitHub at https://github.com/effective-spa/jsrb.
16
+
17
+ ## License
18
+
19
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
20
+
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/jsrb.gemspec ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'jsrb/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'jsrb'
9
+ spec.version = Jsrb::VERSION
10
+ spec.authors = ['Shun MIZUKAMI']
11
+ spec.email = ['norainu234@gmail.com']
12
+
13
+ spec.summary = 'generates JavaScript code with Ruby DSL'
14
+ spec.description = 'Jsrb is a template engine to generate JavaScript code in simple Ruby DSL.'
15
+ spec.homepage = 'https://github.com/effective-spa/jsrb'
16
+ spec.license = 'MIT'
17
+
18
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
19
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
20
+ if spec.respond_to?(:metadata)
21
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
22
+ else
23
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
24
+ 'public gem pushes.'
25
+ end
26
+
27
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
28
+ f.match(%r{^(test|spec|features)/})
29
+ end
30
+ spec.bindir = 'exe'
31
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ['lib']
33
+
34
+ spec.add_runtime_dependency 'execjs', '~> 2.7', '>= 2.7.0'
35
+ spec.add_runtime_dependency 'stitch-rb', '~> 0.0.8'
36
+
37
+ spec.add_development_dependency 'bundler', '~> 1.13'
38
+ spec.add_development_dependency 'rake', '~> 10.0'
39
+ spec.add_development_dependency 'rspec', '~> 3.0'
40
+ end
data/lib/jsrb/base.rb ADDED
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsrb
4
+ class Base
5
+ def initialize
6
+ @context = JSStatementContext.new
7
+ end
8
+
9
+ def generate_code
10
+ generator = self.class.code_generator_class.new
11
+ generator.call type: 'Program',
12
+ sourceType: 'script',
13
+ body: @context.stacks.first
14
+ end
15
+
16
+ def var!(id = nil)
17
+ id ||= @context.gen_var_name!
18
+ if block_given?
19
+ raw_expr = yield
20
+ val = raw_expr.is_a?(ExprChain) ? raw_expr : expr(@context.ruby_to_js_ast(raw_expr))
21
+ val.as_variable_declaration!(id)
22
+ else
23
+ expr.as_variable_declaration!(id)
24
+ end
25
+ expr.member!(id)
26
+ end
27
+
28
+ def if!(cond_expr, &block)
29
+ CondChain.new(@context, false).elsif(cond_expr, &block)
30
+ end
31
+
32
+ def if(cond_expr, &block)
33
+ CondChain.new(@context, true).elsif(cond_expr, &block)
34
+ end
35
+
36
+ def expr(object = nil)
37
+ @context.new_expression(object)
38
+ end
39
+
40
+ class << self
41
+ def code_generator_class
42
+ @code_generator_class ||= Object.const_get(code_generator)
43
+ end
44
+
45
+ def code_generator
46
+ @code_generator || 'Jsrb::NotFastGenerator'
47
+ end
48
+
49
+ attr_writer :code_generator
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsrb
4
+ class CondChain
5
+ def initialize(context, as_expr)
6
+ @context = context
7
+ @as_expr = as_expr
8
+ @stack = []
9
+ end
10
+
11
+ def elsif(expr, &block)
12
+ add_case(expr, block)
13
+ self
14
+ end
15
+
16
+ def else(&block)
17
+ finalize(block)
18
+ end
19
+
20
+ def end
21
+ finalize(nil)
22
+ end
23
+ alias end! end
24
+
25
+ private
26
+
27
+ def add_case(expr, block)
28
+ @stack << [@context.ruby_to_js_ast(expr), block]
29
+ end
30
+
31
+ def finalize(block)
32
+ if_value = @context.new_expression(
33
+ type: 'CallExpression',
34
+ callee: @context.ruby_to_js_ast(create_proc(block)),
35
+ arguments: []
36
+ )
37
+ if @as_expr
38
+ if_value
39
+ else
40
+ @context.push(
41
+ type: 'ExpressionStatement',
42
+ expression: if_value.unwrap!
43
+ )
44
+ end
45
+ end
46
+
47
+ def create_proc(block)
48
+ lambda do
49
+ final_alternate_stmt = block && @context.new_block do
50
+ @context.new_expression(
51
+ type: 'CallExpression',
52
+ callee: @context.ruby_to_js_ast(block),
53
+ arguments: []
54
+ ).as_return!
55
+ end
56
+ if_stmt = @stack.reverse.reduce(final_alternate_stmt) do |alternate_stmt, cond_block|
57
+ condition_expr, cond_block = cond_block
58
+ consequent_stmt = @context.new_block do
59
+ @context.new_expression(
60
+ type: 'CallExpression',
61
+ callee: @context.ruby_to_js_ast(cond_block),
62
+ arguments: []
63
+ ).as_return!
64
+ end
65
+ stmt_ast = {
66
+ type: 'IfStatement',
67
+ test: condition_expr,
68
+ consequent: consequent_stmt
69
+ }
70
+ stmt_ast[:alternate] = alternate_stmt if alternate_stmt
71
+ stmt_ast
72
+ end
73
+ @context.push(if_stmt)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsrb
4
+ class ExprChain
5
+ JS_LOGICAL_OPS = %w[&& ||].freeze
6
+
7
+ def initialize(context, object = nil)
8
+ @context = context
9
+ @object = object
10
+ end
11
+
12
+ # rubocop:disable Style/MethodMissingSuper
13
+ def method_missing(name, *args, &block)
14
+ if (matches = name.to_s.match(/\A(.+)\?\z/))
15
+ member!(matches[1]).cond?(*args)
16
+ elsif (matches = name.to_s.match(/\A(.+)=\z/))
17
+ member!(matches[1]).set!(*args)
18
+ elsif (function_name = self.class.custom_chains[name.to_sym])
19
+ bind_chain!(function_name, *args, &block)
20
+ elsif args.empty? && !block
21
+ member!(name.to_s)
22
+ else
23
+ member!(name.to_s).call(*args, &block)
24
+ end
25
+ end
26
+ # rubocop:enable Style/MethodMissingSuper
27
+
28
+ def [](value)
29
+ member!(value)
30
+ end
31
+
32
+ %i[** + - * / % >> << & ^ | <= < > >= == === != !].each do |operator|
33
+ define_method(operator) do |*args|
34
+ op!(operator, *args)
35
+ end
36
+ end
37
+
38
+ def unwrap!
39
+ @object
40
+ end
41
+
42
+ def as_statement!
43
+ raise ArgumentError, "Can't chain as_statement! on empty context" unless @object
44
+
45
+ @context.push(
46
+ type: 'ExpressionStatement',
47
+ expression: @object
48
+ )
49
+ nil # chain ends
50
+ end
51
+
52
+ def as_variable_declaration!(id)
53
+ if @object
54
+ @context.push(
55
+ type: 'VariableDeclaration',
56
+ declarations: [{
57
+ type: 'VariableDeclarator',
58
+ id: {
59
+ type: 'Identifier',
60
+ name: id.to_s
61
+ },
62
+ init: @object
63
+ }],
64
+ kind: 'var'
65
+ )
66
+ else
67
+ @context.push(
68
+ type: 'VariableDeclaration',
69
+ declarations: [{
70
+ type: 'VariableDeclarator',
71
+ id: {
72
+ type: 'Identifier',
73
+ name: id.to_s
74
+ }
75
+ }],
76
+ kind: 'var'
77
+ )
78
+ end
79
+ end
80
+
81
+ def as_return!
82
+ @context.push(
83
+ type: 'ReturnStatement',
84
+ argument: @object
85
+ )
86
+ end
87
+
88
+ def member!(value)
89
+ if @object
90
+ self.class.new @context, type: 'MemberExpression',
91
+ computed: true,
92
+ object: @object,
93
+ property: @context.ruby_to_js_ast(value)
94
+ else
95
+ self.class.new @context, type: 'Identifier',
96
+ name: value.to_s
97
+ end
98
+ end
99
+
100
+ def assign!(value)
101
+ raise ArgumentError, "Can't chain assign! on empty context" unless @object
102
+
103
+ self.class.new @context, type: 'AssignmentExpression',
104
+ operator: '=',
105
+ left: @object,
106
+ right: @context.ruby_to_js_ast(value)
107
+ end
108
+
109
+ def set!(value)
110
+ assign!(value).as_statement!
111
+ end
112
+
113
+ def call(*args, &block)
114
+ raise ArgumentError, "Can't chain call on empty context" unless @object
115
+
116
+ js_args = args.map do |arg|
117
+ @context.ruby_to_js_ast(arg)
118
+ end
119
+ js_args << @context.ruby_to_js_ast(block) if block
120
+ self.class.new @context, type: 'CallExpression',
121
+ callee: @object,
122
+ arguments: js_args
123
+ end
124
+
125
+ def new!(*args)
126
+ raise ArgumentError, "Can't chain new! on empty context" unless @object
127
+
128
+ arguments = args.map do |arg|
129
+ @context.ruby_to_js_ast(arg)
130
+ end
131
+ self.class.new @context, type: 'NewExpression',
132
+ callee: @object,
133
+ arguments: arguments
134
+ end
135
+
136
+ def op!(operator, *args)
137
+ raise ArgumentError, "Can't chain op! on empty context" unless @object
138
+
139
+ opstr = operator.to_s
140
+ if args.size == 1
141
+ _binary_op!(opstr, *args)
142
+ elsif args.empty?
143
+ _unary_op!(opstr)
144
+ else
145
+ raise ArgumentError, "#{opstr} is not a valid operator"
146
+ end
147
+ end
148
+
149
+ private def _binary_op!(opstr, arg)
150
+ if JS_LOGICAL_OPS.include? opstr
151
+ self.class.new @context, type: 'LogicalExpression',
152
+ operator: opstr,
153
+ left: @object,
154
+ right: @context.ruby_to_js_ast(arg)
155
+ else
156
+ self.class.new @context, type: 'BinaryExpression',
157
+ operator: opstr,
158
+ left: @object,
159
+ right: @context.ruby_to_js_ast(arg)
160
+ end
161
+ end
162
+
163
+ private def _unary_op!(opstr)
164
+ self.class.new @context, type: 'UnaryExpression',
165
+ operator: opstr,
166
+ argument: @object,
167
+ prefix: true
168
+ end
169
+
170
+ def cond?(consequent, alternate)
171
+ raise ArgumentError, "Can't chain cond? on empty context" unless @object
172
+
173
+ self.class.new @context, type: 'ConditionalExpression',
174
+ test: @object,
175
+ consequent: @context.ruby_to_js_ast(consequent),
176
+ alternate: @context.ruby_to_js_ast(alternate)
177
+ end
178
+
179
+ def for_all!(*args)
180
+ raise ArgumentError, "Can't chain for_all! on empty context" unless @object
181
+
182
+ self.class.new @context, type: 'FunctionExpression',
183
+ id: nil,
184
+ params: args.map { |arg| @context.ruby_to_js_ast(arg) },
185
+ body: {
186
+ type: 'BlockStatement',
187
+ body: [
188
+ {
189
+ type: 'ReturnStatement',
190
+ argument: @object
191
+ }
192
+ ]
193
+ }
194
+ end
195
+
196
+ def bind_chain!(function_name, *args, &block)
197
+ raise ArgumentError, "Can't bind_chain! on empty context" unless @object
198
+
199
+ arg_asts = [@object]
200
+ args.each do |arg|
201
+ arg_asts << @context.ruby_to_js_ast(arg)
202
+ end
203
+ arg_asts << @context.ruby_to_js_ast(block) if block_given?
204
+ self.class.new @context, type: 'CallExpression',
205
+ callee: self.class.new(@context).member!(function_name).unwrap!,
206
+ arguments: arg_asts
207
+ end
208
+
209
+ class << self
210
+ def add_custom_chain(chain_method_name, function_name)
211
+ @_custom_chains ||= {}
212
+ @_custom_chains[chain_method_name] = function_name
213
+ end
214
+
215
+ def custom_chains
216
+ @_custom_chains || {}
217
+ end
218
+ end
219
+ end
220
+ end