jsrb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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