manatee 0.0.1.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +23 -0
- data/LICENSE.txt +22 -0
- data/README.mdown +55 -0
- data/Rakefile +7 -0
- data/app/assets/javascripts/manatee.js +1 -0
- data/app/assets/javascripts/manatee/helpers.js +4 -0
- data/app/assets/javascripts/manatee/helpers/asset_tag.jsh.coffee +138 -0
- data/app/assets/javascripts/manatee/helpers/asset_url.jsh.coffee +90 -0
- data/app/assets/javascripts/manatee/helpers/csrf.jsh.coffee +5 -0
- data/app/assets/javascripts/manatee/helpers/date.jsh.coffee +54 -0
- data/app/assets/javascripts/manatee/helpers/debug.jsh.coffee +2 -0
- data/app/assets/javascripts/manatee/helpers/form.jsh.coffee +25 -0
- data/app/assets/javascripts/manatee/helpers/form_builder.jsh.coffee +24 -0
- data/app/assets/javascripts/manatee/helpers/form_options.jsh.coffee +267 -0
- data/app/assets/javascripts/manatee/helpers/form_tag.jsh.coffee +204 -0
- data/app/assets/javascripts/manatee/helpers/javascript.jsh.coffee +15 -0
- data/app/assets/javascripts/manatee/helpers/number.jsh.coffee +58 -0
- data/app/assets/javascripts/manatee/helpers/sanitize.jsh.coffee +5 -0
- data/app/assets/javascripts/manatee/helpers/tag.jsh.coffee +58 -0
- data/app/assets/javascripts/manatee/helpers/text.jsh.coffee +12 -0
- data/app/assets/javascripts/manatee/helpers/translation.jsh.coffee +7 -0
- data/app/assets/javascripts/manatee/helpers/url.jsh.coffee +36 -0
- data/app/assets/javascripts/manatee/helpers/util.jsh.coffee +41 -0
- data/app/assets/javascripts/manatee/rails_helpers.js +2 -0
- data/app/assets/javascripts/manatee/rails_helpers/routes.js.erb +195 -0
- data/app/assets/javascripts/manatee/rails_routes.js +1 -0
- data/app/assets/javascripts/manatee/renderer.js.erb +53 -0
- data/app/assets/javascripts/manatee_railsless.js +1 -0
- data/lib/manatee.rb +87 -0
- data/lib/manatee/config.rb +51 -0
- data/lib/manatee/handler.rb +45 -0
- data/lib/manatee/rails.rb +6 -0
- data/lib/manatee/rails/extensions.rb +23 -0
- data/lib/manatee/rails/handler.rb +16 -0
- data/lib/manatee/rails/hash_visitor.rb +35 -0
- data/lib/manatee/rails/helper.rb +9 -0
- data/lib/manatee/rails/resolver.rb +70 -0
- data/lib/manatee/rails/routes_compiler.rb +34 -0
- data/lib/manatee/sprockets.rb +8 -0
- data/lib/manatee/sprockets/jsh_processor_2x.rb +32 -0
- data/lib/manatee/sprockets/jsh_processor_3x.rb +31 -0
- data/lib/manatee/version.rb +3 -0
- data/manatee.gemspec +32 -0
- data/test/example/renderer.js.erb +8 -0
- data/test/example/translations.en.yml +410 -0
- data/test/example/views/index.jst.eco +12 -0
- data/test/helpers/asset_tag_test.rb +175 -0
- data/test/helpers/asset_url_test.rb +349 -0
- data/test/helpers/form_options_test.rb +718 -0
- data/test/helpers/form_tag_test.rb +387 -0
- data/test/helpers/javascript_test.rb +39 -0
- data/test/helpers/number_test.rb +111 -0
- data/test/helpers/tag_test.rb +71 -0
- data/test/support/dom_assertion.rb +49 -0
- data/test/support/view_test.rb +91 -0
- data/test/test_helper.rb +17 -0
- metadata +213 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
javascriptCDATA = (content) ->
|
2
|
+
if typeof content == 'function'
|
3
|
+
content = content.call(this)
|
4
|
+
"\n//#{@CDATA( "\n" + content + "\n//" )}\n"
|
5
|
+
|
6
|
+
helper 'javascriptTag', (content_or_options, options_or_content = {}) ->
|
7
|
+
[content, options] = @_contentOrOptions content_or_options, options_or_content
|
8
|
+
@contentTag 'script', javascriptCDATA.call(this, content), options
|
9
|
+
|
10
|
+
helper 'escapeJavascript', (content) ->
|
11
|
+
if content
|
12
|
+
content.replace(/\\/g, '\\\\').replace(/<\//g, '<\\\/').replace(/\r\n/g, '\\n').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\"/g, '\\"').replace(/\'/g, "\\'").replace(/\u2028/g, "
").replace(/\u2029/g, "
")
|
13
|
+
else
|
14
|
+
''
|
15
|
+
alias 'j', 'escapeJavascript'
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# numberHelperBuilder = (_translation_key, _i18n_function) ->
|
2
|
+
# i18n_function = _i18n_function.bind I18n
|
3
|
+
# translation_key = _translation_key
|
4
|
+
# (number, options = {}) ->
|
5
|
+
# return null if number == false || number == null || number == undefined
|
6
|
+
# i18n_options = if options['locale']
|
7
|
+
# translated_options = @_clone @translate( translation_key, locale: options['locale'] )
|
8
|
+
# translated_options['negative_format'] = options['negative_format']
|
9
|
+
# translated_options
|
10
|
+
# else
|
11
|
+
# @_clone options
|
12
|
+
#
|
13
|
+
# result = if number < 0 && i18n_options['negative_format']
|
14
|
+
# i18n_options['format'] = i18n_options['negative_format']
|
15
|
+
# i18n_function(-number, i18n_options)
|
16
|
+
# else
|
17
|
+
# i18n_function(number, i18n_options)
|
18
|
+
#
|
19
|
+
# if i18n_options['escape'] || i18n_options['escape'] == undefined
|
20
|
+
# @htmlEscape result
|
21
|
+
# else
|
22
|
+
# result
|
23
|
+
|
24
|
+
numberHelperBuilder = (_translation_key, _i18n_function) ->
|
25
|
+
i18n_function = _i18n_function.bind I18n
|
26
|
+
translation_key = _translation_key
|
27
|
+
(number, options = {}) ->
|
28
|
+
return null if number == false || number == null || number == undefined
|
29
|
+
i18n_options = if options['locale']
|
30
|
+
@_clone @translate( translation_key, locale: options['locale'] )
|
31
|
+
else
|
32
|
+
opts = @_clone options
|
33
|
+
for key, value of @translate(translation_key)
|
34
|
+
opts[key] = value if opts[key] == undefined
|
35
|
+
opts
|
36
|
+
|
37
|
+
result = if number < 0 && options['negative_format']
|
38
|
+
i18n_options['format'] = options['negative_format']
|
39
|
+
i18n_function(-number, i18n_options)
|
40
|
+
else
|
41
|
+
i18n_function(number, i18n_options)
|
42
|
+
|
43
|
+
if options['escape'] || options['escape'] == undefined
|
44
|
+
@htmlEscape result
|
45
|
+
else
|
46
|
+
result
|
47
|
+
|
48
|
+
|
49
|
+
helper 'numberToCurrency', numberHelperBuilder.call(this, 'number.currency.format', I18n.toCurrency )
|
50
|
+
|
51
|
+
# TODO: Add number helper tests and fix I18n-js gem
|
52
|
+
helper 'numberToHuman', numberHelperBuilder.call(this, 'number.human.decimal_units', I18n.toNumber )
|
53
|
+
helper 'numberToHumanSize', numberHelperBuilder.call(this, 'number.human.storage_units', I18n.toHumanSize )
|
54
|
+
helper 'numberToPercentage', numberHelperBuilder.call(this, 'number.percentage.format', I18n.toPercentage )
|
55
|
+
|
56
|
+
# number_to_phone
|
57
|
+
# number_with_delimiter
|
58
|
+
# number_with_precision
|
@@ -0,0 +1,58 @@
|
|
1
|
+
writeTagAttributes = (options) ->
|
2
|
+
tag = ''
|
3
|
+
if options['data']
|
4
|
+
for key, value of options['data']
|
5
|
+
options['data-' + key] = value
|
6
|
+
options['data'] = undefined
|
7
|
+
for key, value of options
|
8
|
+
key = key.replace(/\_/g, '-')
|
9
|
+
unless typeof value == 'undefined'
|
10
|
+
if value == true
|
11
|
+
tag += " " + key + "=" + '"' + key + '"'
|
12
|
+
else
|
13
|
+
value = if typeof value == 'string'
|
14
|
+
value.replace(/\"/g, '"').replace(/\'/g, ''')
|
15
|
+
else
|
16
|
+
if value == null
|
17
|
+
value = ''
|
18
|
+
else
|
19
|
+
value.toString()
|
20
|
+
tag += " " + key + "=" + '"' + value + '"'
|
21
|
+
tag
|
22
|
+
|
23
|
+
helper 'tag', (name, options = {}) ->
|
24
|
+
options = @_clone options
|
25
|
+
tag = "<" + name
|
26
|
+
if arguments.length > 1
|
27
|
+
tag += writeTagAttributes(options)
|
28
|
+
tag + '/>'
|
29
|
+
|
30
|
+
helper 'contentTag', (name, content_or_options = '', options_or_content = '') ->
|
31
|
+
[content, options] = if arguments.length == 1
|
32
|
+
['', {}]
|
33
|
+
else if arguments.length == 2
|
34
|
+
if content_or_options != null && typeof content_or_options == 'object'
|
35
|
+
[ options_or_content || '', @_clone(content_or_options) ]
|
36
|
+
else
|
37
|
+
[ content_or_options || '', {} ]
|
38
|
+
else
|
39
|
+
if content_or_options != null && typeof content_or_options == 'object'
|
40
|
+
[ options_or_content || '', @_clone(content_or_options) ]
|
41
|
+
else
|
42
|
+
[ content_or_options || '', @_clone(options_or_content) ]
|
43
|
+
|
44
|
+
tag = "<" + name
|
45
|
+
tag += writeTagAttributes(options)
|
46
|
+
tag += '>'
|
47
|
+
|
48
|
+
if typeof content == 'function'
|
49
|
+
tag += content.call(this)
|
50
|
+
else
|
51
|
+
tag += content
|
52
|
+
|
53
|
+
tag + '</' + name + '>'
|
54
|
+
|
55
|
+
helper 'CDATA', (content) ->
|
56
|
+
if typeof content == 'function'
|
57
|
+
content = content.call(this)
|
58
|
+
"<![CDATA[" + content + "]]>"
|
@@ -0,0 +1,36 @@
|
|
1
|
+
helper 'urlFor', (source, options = {}) ->
|
2
|
+
if @_context.forceDomain
|
3
|
+
options['domain'] ||= @_context.domain.app
|
4
|
+
if options['domain']
|
5
|
+
source = options['domain'] + source
|
6
|
+
if options['format']
|
7
|
+
source += '.' + options['format']
|
8
|
+
source
|
9
|
+
|
10
|
+
helper 'linkTo', (name, url, options = {}) ->
|
11
|
+
options = @_clone options
|
12
|
+
options['href'] ||= url
|
13
|
+
if options['method']
|
14
|
+
options['rel'] = 'nofollow'
|
15
|
+
options['data-method'] = options['method']
|
16
|
+
options['method'] = undefined
|
17
|
+
if options['remote']
|
18
|
+
options['data-remote'] = 'true'
|
19
|
+
options['remote'] = undefined
|
20
|
+
@contentTag 'a', name, options
|
21
|
+
|
22
|
+
helper 'linkToIf', (condition, name, url, options = {}) ->
|
23
|
+
if condition then @linkTo(name, url, options) else ''
|
24
|
+
|
25
|
+
helper 'linkToUnless', (condition, name, url, options = {}) ->
|
26
|
+
@linkToIf !condition, name, url, options
|
27
|
+
|
28
|
+
helper 'contentLinkTo', (url, options_or_content, content_or_options) ->
|
29
|
+
[ content, options ] = @_contentOrOptions options_or_content, content_or_options
|
30
|
+
@linkTo content, url, options
|
31
|
+
|
32
|
+
|
33
|
+
# button_to
|
34
|
+
# mail_to
|
35
|
+
# current_page?
|
36
|
+
# link_to_unless_current
|
@@ -0,0 +1,41 @@
|
|
1
|
+
helper '_contentOrOptions', (content_or_options, options_or_content, default_content) ->
|
2
|
+
if content_or_options != null && typeof content_or_options == 'object'
|
3
|
+
[ options_or_content || default_content, @_clone(content_or_options) ]
|
4
|
+
else
|
5
|
+
[ content_or_options || default_content, @_clone( options_or_content || {} ) ]
|
6
|
+
|
7
|
+
htmlEscapeMap = { '&': '&', '<': '<', '>': '>' }
|
8
|
+
escapeHTMLCallback = (key) ->
|
9
|
+
htmlEscapeMap[key] || key;
|
10
|
+
|
11
|
+
helper '_clone', (object) ->
|
12
|
+
return object if object == null || typeof object != "object"
|
13
|
+
cloned_object = object.constructor()
|
14
|
+
for attr of object
|
15
|
+
cloned_object[attr] = object[attr] if object.hasOwnProperty(attr)
|
16
|
+
cloned_object
|
17
|
+
|
18
|
+
helper 'htmlEscape', (string) ->
|
19
|
+
string.replace(/[&<>]/g, escapeHTMLCallback);
|
20
|
+
|
21
|
+
helper 'htmlEscapeAttributes', (attributes) ->
|
22
|
+
escaped_attributes = {}
|
23
|
+
for index, value of attributes
|
24
|
+
if typeof value == 'string'
|
25
|
+
escaped_attributes[index] = @htmlEscape(value)
|
26
|
+
else if typeof value == 'object'
|
27
|
+
escaped_attributes[index] = @htmlEscapeAttributes(value)
|
28
|
+
else
|
29
|
+
escaped_attributes[index] = value
|
30
|
+
escaped_attributes
|
31
|
+
|
32
|
+
helper 'underscore', (string) ->
|
33
|
+
results = []
|
34
|
+
sub_strings = string.split '::'
|
35
|
+
for index, sub_string of sub_strings
|
36
|
+
replaced = sub_string.replace( /([A-Z0-9]+)/g, ( (str) -> '_' + str.toLowerCase() ) )
|
37
|
+
if (/([A-Z0-9])/).test(sub_string[0])
|
38
|
+
results.push replaced.slice(1)
|
39
|
+
else
|
40
|
+
results.push replaced
|
41
|
+
results.join '/'
|
@@ -0,0 +1,195 @@
|
|
1
|
+
var <%= Manatee.router_namespace %> = function(){
|
2
|
+
|
3
|
+
var RoutesVisitor = function(){
|
4
|
+
|
5
|
+
function call(options, node){
|
6
|
+
this.options = options;
|
7
|
+
this.consumed = {};
|
8
|
+
return this.visit(node);
|
9
|
+
}
|
10
|
+
|
11
|
+
function visit(node){
|
12
|
+
return this['visit'+node.type](node);
|
13
|
+
}
|
14
|
+
|
15
|
+
function nary(node){
|
16
|
+
var result = '';
|
17
|
+
for(var n in node.children){
|
18
|
+
result += this.visit(n);
|
19
|
+
}
|
20
|
+
return result;
|
21
|
+
}
|
22
|
+
|
23
|
+
function binary(node){
|
24
|
+
return this.visit( node.children[0] ) + this.visit( node.children[1] );
|
25
|
+
}
|
26
|
+
|
27
|
+
function unary(node){
|
28
|
+
return this.visit(node.children[0]);
|
29
|
+
}
|
30
|
+
|
31
|
+
function terminal(node){
|
32
|
+
return node.value;
|
33
|
+
}
|
34
|
+
|
35
|
+
function visitCAT(node){
|
36
|
+
return this.binary(node);
|
37
|
+
}
|
38
|
+
|
39
|
+
function visitOR(node){
|
40
|
+
return this.nary(node);
|
41
|
+
}
|
42
|
+
|
43
|
+
function visitGROUP(node){
|
44
|
+
if(compareObjects(this.options, this.consumed)){
|
45
|
+
return '';
|
46
|
+
} else {
|
47
|
+
var route = this.visit(node.children[0]);
|
48
|
+
|
49
|
+
if(this.hasNullByte(route)){
|
50
|
+
return '';
|
51
|
+
} else {
|
52
|
+
return route;
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
function visitSTAR(node){
|
58
|
+
return this.unary(node);
|
59
|
+
}
|
60
|
+
|
61
|
+
function visitLITERAL(node){
|
62
|
+
return this.terminal(node);
|
63
|
+
}
|
64
|
+
|
65
|
+
function visitSYMBOL(node){
|
66
|
+
var key = node.value.substring(1, node.value.lenght);
|
67
|
+
var value = this.options[key];
|
68
|
+
|
69
|
+
if(value){
|
70
|
+
this.consumed[key] = value;
|
71
|
+
return encodeURIComponent(value.toString());
|
72
|
+
} else {
|
73
|
+
return "\0";
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
function visitSLASH(node){
|
78
|
+
return this.terminal(node);
|
79
|
+
}
|
80
|
+
|
81
|
+
function visitDOT(node){
|
82
|
+
return this.terminal(node);
|
83
|
+
}
|
84
|
+
|
85
|
+
// Utils
|
86
|
+
|
87
|
+
function hasNullByte(string){
|
88
|
+
for(var i in string){
|
89
|
+
if( string[i] == "\0" ) return true;
|
90
|
+
}
|
91
|
+
return false;
|
92
|
+
}
|
93
|
+
|
94
|
+
function compareObjects(obj1, obj2){
|
95
|
+
return objectCount(obj1) == objectCount(obj2);
|
96
|
+
}
|
97
|
+
|
98
|
+
function objectCount(obj){
|
99
|
+
var count = 0;
|
100
|
+
for( var i in obj ){ count++; }
|
101
|
+
return count;
|
102
|
+
}
|
103
|
+
|
104
|
+
return {
|
105
|
+
call: call,
|
106
|
+
hasNullByte: hasNullByte,
|
107
|
+
compareObjects: compareObjects,
|
108
|
+
objectCount: objectCount,
|
109
|
+
|
110
|
+
binary: binary,
|
111
|
+
nary: nary,
|
112
|
+
terminal: terminal,
|
113
|
+
unary: unary,
|
114
|
+
visit: visit,
|
115
|
+
visitCAT: visitCAT,
|
116
|
+
visitDOT: visitDOT,
|
117
|
+
visitGROUP: visitGROUP,
|
118
|
+
visitLITERAL: visitLITERAL,
|
119
|
+
visitOR: visitOR,
|
120
|
+
visitSLASH: visitSLASH,
|
121
|
+
visitSTAR: visitSTAR,
|
122
|
+
visitSYMBOL: visitSYMBOL
|
123
|
+
};
|
124
|
+
}();
|
125
|
+
|
126
|
+
function pathFunctionBuilder(route){
|
127
|
+
return function(){
|
128
|
+
if( arguments.length != this.required_names.length && arguments.length != this.required_names.length + 1){
|
129
|
+
throw 'Argument Error';
|
130
|
+
} else {
|
131
|
+
var options = arguments[this.required_names.length];
|
132
|
+
|
133
|
+
var params = {};
|
134
|
+
for(var i in this.required_names){
|
135
|
+
var key = this.required_names[i];
|
136
|
+
params[key] = arguments[i];
|
137
|
+
}
|
138
|
+
|
139
|
+
var optionalMap = {};
|
140
|
+
for(var i in this.optional_names){
|
141
|
+
optionalMap[this.optional_names[i]] = true;
|
142
|
+
}
|
143
|
+
|
144
|
+
var queryParams = {};
|
145
|
+
for(var k in options){
|
146
|
+
if(optionalMap[k]){
|
147
|
+
params[k] = options[k];
|
148
|
+
} else {
|
149
|
+
queryParams[k] = options[k];
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
153
|
+
return <%= Manatee.renderer_namespace %>.urlFor(this._router._visitor.call(params, this.ast));
|
154
|
+
}
|
155
|
+
};
|
156
|
+
}
|
157
|
+
|
158
|
+
function urlHelper(){
|
159
|
+
return this._router._fullDomain + this.path.apply(this, arguments);
|
160
|
+
}
|
161
|
+
|
162
|
+
var compiledRoutes = <%= Manatee::Rails::RoutesCompiler.new.compile.to_json %>;
|
163
|
+
|
164
|
+
var routes = {
|
165
|
+
_routes: compiledRoutes,
|
166
|
+
_visitor: RoutesVisitor,
|
167
|
+
_fullDomain: <%= Manatee.full_domain.inspect %>
|
168
|
+
}
|
169
|
+
|
170
|
+
for( var i in compiledRoutes ){
|
171
|
+
var route = compiledRoutes[i];
|
172
|
+
|
173
|
+
route.url = urlHelper;
|
174
|
+
route.path = pathFunctionBuilder(route);
|
175
|
+
route._router = routes;
|
176
|
+
|
177
|
+
routes[route.helper_name] = route;
|
178
|
+
|
179
|
+
(function(r){
|
180
|
+
var route = r;
|
181
|
+
|
182
|
+
<%= Manatee.renderer_namespace %>.helper(route.helper_name+'Path', function(){
|
183
|
+
return route.path.apply(route, arguments);
|
184
|
+
});
|
185
|
+
|
186
|
+
<%= Manatee.renderer_namespace %>.helper(route.helper_name+'Url', function(){
|
187
|
+
return route.url.apply(route, arguments);
|
188
|
+
});
|
189
|
+
})(route);
|
190
|
+
|
191
|
+
}
|
192
|
+
|
193
|
+
return routes;
|
194
|
+
|
195
|
+
}();
|
@@ -0,0 +1 @@
|
|
1
|
+
//= require ./rails_helpers/routes
|