manatee 0.0.1.pre1
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 +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
|