riot_js-rails 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: 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
+ }