riot_js-rails 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: 7d683957b5d3a585f6e493b42f17f4908075264b
4
+ data.tar.gz: 8532cc7d6277124a95475144d6198b6774e30bfc
5
+ SHA512:
6
+ metadata.gz: 771a137a6e2e9e16a327b1b12e9b65f30fbc0dff2bab1b4983188a6a54a1e6167f1817a54d83ab12b8189589866455a68595260f2ef1fa64a1a440aaa36991dd
7
+ data.tar.gz: a7b03afec4071dfee9105283308b82d706c6f670c10dd528405b4ae5fa74f755569752ac7d0dd08132f06a40cdd56cab40b20f6f033f7ee6748d749966d8f261
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.idea
11
+ /*.gem
12
+ /.editorconfig
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in riot_js-rails.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # RiotJs::Rails
2
+
3
+ Muut Riot integration with Rails
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'riot_js-rails'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ ## Usage
18
+
19
+ TODO: Write usage instructions here
20
+
21
+ ## Contributing
22
+
23
+ 1. Fork it ( https://github.com/[my-github-username]/riot_js-rails/fork )
24
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
25
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
26
+ 4. Push to the branch (`git push origin my-new-feature`)
27
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'test'
7
+ end
8
+
9
+ desc "Run tests"
10
+ task :default => :test
@@ -0,0 +1,8 @@
1
+ require 'riot_js/rails/version'
2
+ require 'riot_js/rails/engine' if defined?(Rails)
3
+ require 'riot_js/rails/railtie' if defined?(Rails)
4
+
5
+ module RiotJs
6
+ module Rails
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ require 'rails/engine'
2
+
3
+ module RiotJs
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,51 @@
1
+ module RiotJs
2
+ module Rails
3
+ module Helper
4
+
5
+ def riot_component(*args, &block)
6
+ case args.count
7
+ when 2
8
+ custom_tag_riot_component(*args, &block)
9
+ when 3
10
+ html_tag_riot_component(*args, &block)
11
+ else
12
+ raise ArgumentError, 'wrong number of arguments (required 2 or 3)'
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def custom_tag_riot_component(name, data, &block)
19
+ component_name, attributes = component_attributes(name, data)
20
+
21
+ component_tag(component_name, attributes, &block)
22
+ end
23
+
24
+ def html_tag_riot_component(tag, name, data, &block)
25
+ component_name, attributes = component_attributes(name, data)
26
+ attributes['riot-tag'] = component_name
27
+
28
+ component_tag(tag, attributes, &block)
29
+ end
30
+
31
+ def component_attributes(name, data)
32
+ component_name = name.to_s.gsub('_', '-')
33
+ attributes = {
34
+ id: "riot-#{component_name}-#{Random.rand(10000000)}",
35
+ class: 'riot-rails-component',
36
+ data: { opts: data.to_json }
37
+ }
38
+ return component_name, attributes
39
+ end
40
+
41
+ def component_tag(tag_name, attributes, &block)
42
+ if block_given?
43
+ content_tag(tag_name, attributes, &block)
44
+ else
45
+ content_tag(tag_name, attributes) {}
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,38 @@
1
+ require 'action_view'
2
+ require 'execjs'
3
+ require 'byebug'
4
+
5
+ module RiotJs
6
+ module Rails
7
+ class Compiler
8
+ include ::ActionView::Helpers::JavaScriptHelper
9
+
10
+ JS_RUNTIME = ::ExecJS::ExternalRuntime.new(
11
+ name: 'Node.js (V8)',
12
+ command: ['nodejs', 'node'],
13
+ encoding: 'UTF-8',
14
+ runner_path: File.expand_path('../../../../../vendor/assets/javascripts/compiler/node_runner.js', __FILE__),
15
+ )
16
+
17
+ RIOT_COMPILER_PATH = File.expand_path('../../../../../vendor/assets/javascripts/compiler/compiler.js', __FILE__)
18
+
19
+ def self.instance
20
+ @@instance ||= new
21
+ end
22
+
23
+ def self.compile(tag_source)
24
+ instance.compile(tag_source)
25
+ end
26
+
27
+ def compile(tag_source)
28
+ compiler_source = <<-JS
29
+ var compiler = require("#{RIOT_COMPILER_PATH}");
30
+ return compiler.compile("#{escape_javascript(tag_source)}");
31
+ JS
32
+
33
+ JS_RUNTIME.exec(compiler_source)
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,34 @@
1
+ require 'riot_js/rails/processors/compiler'
2
+
3
+ module RiotJs
4
+ module Rails
5
+ class Processor
6
+ def self.instance
7
+ @instance ||= new
8
+ end
9
+
10
+ def self.call(input)
11
+ instance.call(input)
12
+ end
13
+
14
+ def call(input)
15
+ prepare(input)
16
+
17
+ data = compile_tag
18
+
19
+ @context.metadata.merge(data: data)
20
+ end
21
+
22
+ private
23
+
24
+ def prepare(input)
25
+ @context = input[:environment].context_class.new(input)
26
+ @data = input[:data]
27
+ end
28
+
29
+ def compile_tag
30
+ ::RiotJs::Rails::Compiler.compile(@data)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ require 'rails/railtie'
2
+ require 'riot_js/rails/processors/processor'
3
+ require 'riot_js/rails/helper'
4
+
5
+ module RiotJs
6
+ module Rails
7
+ class Railtie < ::Rails::Railtie
8
+ initializer :setup_sprockets do |app|
9
+ app.assets.register_engine '.tag', Processor, mime_type: 'application/javascript'
10
+
11
+ if defined?(::Haml)
12
+ require 'tilt/haml'
13
+ app.assets.register_engine '.haml', ::Tilt::HamlTemplate, mime_type: 'text/html'
14
+ end
15
+ end
16
+
17
+ initializer :add_helpers do |app|
18
+ helpers = %q{ include ::RiotJs::Rails::Helper }
19
+ ::ActionView::Base.module_eval(helpers)
20
+ ::Rails.application.assets.context_class.class_eval(helpers)
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ module RiotJs
2
+ module Rails
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'riot_js/rails/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'riot_js-rails'
8
+ spec.version = RiotJs::Rails::VERSION
9
+ spec.authors = ['Bartosz Jaroszewski']
10
+ spec.email = ['b.jarosze@gmail.com']
11
+
12
+ spec.summary = %q{Muut Riot integration with Rails.}
13
+ spec.description = %q{This gem provides integration of Muut Riot with Rails}
14
+ spec.homepage = 'https://github.com/bjarosze/riot_js-rails'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+
22
+
23
+ spec.add_dependency 'rails', '~> 4'
24
+ spec.add_dependency 'execjs', '~> 2'
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.8'
27
+ spec.add_development_dependency 'rake', '~> 10.0'
28
+ spec.add_development_dependency 'minitest', '~> 5.1'
29
+ spec.add_development_dependency 'byebug', '~> 5.0'
30
+ end
@@ -0,0 +1,16 @@
1
+ var compilerPath = require('path').join(__dirname, './compiler_core.js')
2
+
3
+ global.riot = require(process.env.RIOT || '../riot')
4
+ global.riot.parsers = require('./parsers')
5
+
6
+ // Evaluate the compiler shared functions in this context
7
+ /*eslint-disable*/
8
+ eval(require('fs').readFileSync(compilerPath, 'utf8'))
9
+ /*eslint-enable*/
10
+
11
+ module.exports = {
12
+ compile: compile,
13
+ html: compileHTML,
14
+ style: compileCSS,
15
+ js: compileJS
16
+ }
@@ -0,0 +1,234 @@
1
+ var BOOL_ATTR = ('allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,'+
2
+ 'defaultchecked,defaultmuted,defaultselected,defer,disabled,draggable,enabled,formnovalidate,hidden,'+
3
+ 'indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,'+
4
+ 'pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,spellcheck,translate,truespeed,'+
5
+ 'typemustmatch,visible').split(','),
6
+ // these cannot be auto-closed
7
+ VOID_TAGS = 'area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr'.split(','),
8
+ /*
9
+ Following attributes give error when parsed on browser with { exrp_values }
10
+
11
+ 'd' describes the SVG <path>, Chrome gives error if the value is not valid format
12
+ https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
13
+ */
14
+ PREFIX_ATTR = ['style', 'src', 'd'],
15
+
16
+ LINE_TAG = /^<([\w\-]+)>(.*)<\/\1>/gim,
17
+ QUOTE = /=({[^}]+})([\s\/\>]|$)/g,
18
+ SET_ATTR = /([\w\-]+)=(["'])([^\2]+?)\2/g,
19
+ EXPR = /{\s*([^}]+)\s*}/g,
20
+ // (tagname) (html) (javascript) endtag
21
+ CUSTOM_TAG = /^<([\w\-]+)\s?([^>]*)>([^\x00]*[\w\/}"']>$)?([^\x00]*?)^<\/\1>/gim,
22
+ SCRIPT = /<script(?:\s+type=['"]?([^>'"]+)['"]?)?>([^\x00]*?)<\/script>/gi,
23
+ STYLE = /<style(?:\s+([^>]+))?>([^\x00]*?)<\/style>/gi,
24
+ CSS_SELECTOR = /(^|\}|\{)\s*([^\{\}]+)\s*(?=\{)/g,
25
+ HTML_COMMENT = /<!--.*?-->/g,
26
+ CLOSED_TAG = /<([\w\-]+)([^>]*)\/\s*>/g,
27
+ BLOCK_COMMENT = /\/\*[\s\S]*?\*\//g,
28
+ LINE_COMMENT = /^\s*\/\/.*$/gm,
29
+ INPUT_NUMBER = /(<input\s[^>]*?)type=['"]number['"]/gm
30
+
31
+ function mktag(name, html, css, attrs, js) {
32
+ return 'riot.tag(\'' +
33
+ name + '\', \'' +
34
+ html + '\'' +
35
+ (css ? ', \'' + css + '\'' : '') +
36
+ (attrs ? ', \'' + attrs.replace(/'/g, "\\'") + '\'' : '') +
37
+ ', function(opts) {' + js + '\n});'
38
+ }
39
+
40
+ function compileHTML(html, opts, type) {
41
+
42
+ if (!html) return ''
43
+
44
+ var brackets = riot.util.brackets,
45
+ b0 = brackets(0),
46
+ b1 = brackets(1)
47
+
48
+ // foo={ bar } --> foo="{ bar }"
49
+ html = html.replace(brackets(QUOTE), '="$1"$2')
50
+
51
+ // whitespace
52
+ html = opts.whitespace ? html.replace(/\r\n?|\n/g, '\\n') : html.replace(/\s+/g, ' ')
53
+
54
+ // strip comments
55
+ html = html.trim().replace(HTML_COMMENT, '')
56
+
57
+ // input type=numbr
58
+ html = html.replace(INPUT_NUMBER, '$1riot-type='+ b0 +'"number"'+ b1) // fake expression
59
+
60
+ // alter special attribute names
61
+ html = html.replace(SET_ATTR, function(full, name, _, expr) {
62
+ if (expr.indexOf(b0) >= 0) {
63
+ name = name.toLowerCase()
64
+
65
+ if (PREFIX_ATTR.indexOf(name) >= 0) name = 'riot-' + name
66
+
67
+ // IE8 looses boolean attr values: `checked={ expr }` --> `__checked={ expr }`
68
+ else if (BOOL_ATTR.indexOf(name) >= 0) name = '__' + name
69
+ }
70
+
71
+ return name + '="' + expr + '"'
72
+ })
73
+
74
+ // run expressions trough parser
75
+ if (opts.expr) {
76
+ html = html.replace(brackets(EXPR), function(_, expr) {
77
+ var ret = compileJS(expr, opts, type).trim().replace(/[\r\n]+/g, '').trim()
78
+ if (ret.slice(-1) == ';') ret = ret.slice(0, -1)
79
+ return b0 + ret + b1
80
+ })
81
+ }
82
+
83
+ // <foo/> -> <foo></foo>
84
+ html = html.replace(CLOSED_TAG, function(_, name, attr) {
85
+ var tag = '<' + name + (attr ? ' ' + attr.trim() : '') + '>'
86
+
87
+ // Do not self-close HTML5 void tags
88
+ if (VOID_TAGS.indexOf(name.toLowerCase()) == -1) tag += '</' + name + '>'
89
+ return tag
90
+ })
91
+
92
+ // escape single quotes
93
+ html = html.replace(/'/g, "\\'")
94
+
95
+ // \{ jotain \} --> \\{ jotain \\}
96
+ html = html.replace(brackets(/\\{|\\}/g), '\\$&')
97
+
98
+ // compact: no whitespace between tags
99
+ if (opts.compact) html = html.replace(/> </g, '><')
100
+
101
+ return html
102
+
103
+ }
104
+
105
+
106
+ function riotjs(js) {
107
+
108
+ // strip comments
109
+ js = js.replace(LINE_COMMENT, '').replace(BLOCK_COMMENT, '')
110
+
111
+ // ES6 method signatures
112
+ var lines = js.split('\n'),
113
+ es6Ident = ''
114
+
115
+ lines.forEach(function(line, i) {
116
+ var l = line.trim()
117
+
118
+ // method start
119
+ if (l[0] != '}' && ~l.indexOf('(')) {
120
+ var end = l.match(/[{}]$/),
121
+ m = end && line.match(/^(\s+)([$\w]+)\s*\(([$\w,\s]*)\)\s*\{/)
122
+
123
+ if (m && !/^(if|while|switch|for|catch|function)$/.test(m[2])) {
124
+ lines[i] = m[1] + 'this.' + m[2] + ' = function(' + m[3] + ') {'
125
+
126
+ // foo() { }
127
+ if (end[0] == '}') {
128
+ lines[i] += ' ' + l.slice(m[0].length - 1, -1) + '}.bind(this)'
129
+
130
+ } else {
131
+ es6Ident = m[1]
132
+ }
133
+ }
134
+
135
+ }
136
+
137
+ // method end
138
+ if (line.slice(0, es6Ident.length + 1) == es6Ident + '}') {
139
+ lines[i] = es6Ident + '}.bind(this);'
140
+ es6Ident = ''
141
+ }
142
+
143
+ })
144
+
145
+ return lines.join('\n')
146
+
147
+ }
148
+
149
+ function scopedCSS (tag, style, type) {
150
+ // 1. Remove CSS comments
151
+ // 2. Find selectors and separate them by conmma
152
+ // 3. keep special selectors as is
153
+ // 4. prepend tag and [riot-tag]
154
+ return style.replace(BLOCK_COMMENT, '').replace(CSS_SELECTOR, function (m, p1, p2) {
155
+ return p1 + ' ' + p2.split(/\s*,\s*/g).map(function(sel) {
156
+ var s = sel.trim()
157
+ var t = (/:scope/.test(s) ? '' : ' ') + s.replace(/:scope/, '')
158
+ return s[0] == '@' || s == 'from' || s == 'to' || /%$/.test(s) ? s :
159
+ tag + t + ', [riot-tag="' + tag + '"]' + t
160
+ }).join(',')
161
+ }).trim()
162
+ }
163
+
164
+ function compileJS(js, opts, type) {
165
+ if (!js) return ''
166
+ var parser = opts.parser || (type ? riot.parsers.js[type] : riotjs)
167
+ if (!parser) throw new Error('Parser not found "' + type + '"')
168
+ return parser(js.replace(/\r\n?/g, '\n'), opts)
169
+ }
170
+
171
+ function compileTemplate(lang, html) {
172
+ var parser = riot.parsers.html[lang]
173
+ if (!parser) throw new Error('Template parser not found "' + lang + '"')
174
+ return parser(html.replace(/\r\n?/g, '\n'))
175
+ }
176
+
177
+ function compileCSS(style, tag, type, scoped) {
178
+ if (type === 'scoped-css') scoped = 1
179
+ else if (riot.parsers.css[type]) style = riot.parsers.css[type](tag, style)
180
+ else if (type !== 'css') throw new Error('CSS parser not found: "' + type + '"')
181
+ if (scoped) style = scopedCSS(tag, style)
182
+ return style.replace(/\s+/g, ' ').replace(/\\/g, '\\\\').replace(/'/g, "\\'").trim()
183
+ }
184
+
185
+ function compile(src, opts) {
186
+
187
+ if (!opts) opts = {}
188
+ else {
189
+
190
+ if (opts.brackets) riot.settings.brackets = opts.brackets
191
+
192
+ if (opts.template) src = compileTemplate(opts.template, src)
193
+ }
194
+
195
+ src = src.replace(LINE_TAG, function(_, tagName, html) {
196
+ return mktag(tagName, compileHTML(html, opts), '', '', '')
197
+ })
198
+
199
+ return src.replace(CUSTOM_TAG, function(_, tagName, attrs, html, js) {
200
+ var style = '',
201
+ type = opts.type
202
+
203
+ if (html) {
204
+
205
+ // js wrapped inside <script> tag
206
+ if (!js.trim()) {
207
+ html = html.replace(SCRIPT, function(_, _type, script) {
208
+ if (_type) type = _type.replace('text/', '')
209
+ js = script
210
+ return ''
211
+ })
212
+ }
213
+
214
+ // styles in <style> tag
215
+ html = html.replace(STYLE, function(_, types, _style) {
216
+ var scoped = /(?:^|\s+)scoped(\s|=|$)/i.test(types),
217
+ type = types && types.match(/(?:^|\s+)type\s*=\s*['"]?([^'"\s]+)['"]?/i)
218
+ if (type) type = type[1].replace('text/', '')
219
+ style += (style ? ' ' : '') + compileCSS(_style.trim(), tagName, type || 'css', scoped)
220
+ return ''
221
+ })
222
+ }
223
+
224
+ return mktag(
225
+ tagName,
226
+ compileHTML(html, opts, type),
227
+ style,
228
+ compileHTML(attrs, ''),
229
+ compileJS(js, opts, type)
230
+ )
231
+
232
+ })
233
+
234
+ }