manatee 0.0.1.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +23 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.mdown +55 -0
  5. data/Rakefile +7 -0
  6. data/app/assets/javascripts/manatee.js +1 -0
  7. data/app/assets/javascripts/manatee/helpers.js +4 -0
  8. data/app/assets/javascripts/manatee/helpers/asset_tag.jsh.coffee +138 -0
  9. data/app/assets/javascripts/manatee/helpers/asset_url.jsh.coffee +90 -0
  10. data/app/assets/javascripts/manatee/helpers/csrf.jsh.coffee +5 -0
  11. data/app/assets/javascripts/manatee/helpers/date.jsh.coffee +54 -0
  12. data/app/assets/javascripts/manatee/helpers/debug.jsh.coffee +2 -0
  13. data/app/assets/javascripts/manatee/helpers/form.jsh.coffee +25 -0
  14. data/app/assets/javascripts/manatee/helpers/form_builder.jsh.coffee +24 -0
  15. data/app/assets/javascripts/manatee/helpers/form_options.jsh.coffee +267 -0
  16. data/app/assets/javascripts/manatee/helpers/form_tag.jsh.coffee +204 -0
  17. data/app/assets/javascripts/manatee/helpers/javascript.jsh.coffee +15 -0
  18. data/app/assets/javascripts/manatee/helpers/number.jsh.coffee +58 -0
  19. data/app/assets/javascripts/manatee/helpers/sanitize.jsh.coffee +5 -0
  20. data/app/assets/javascripts/manatee/helpers/tag.jsh.coffee +58 -0
  21. data/app/assets/javascripts/manatee/helpers/text.jsh.coffee +12 -0
  22. data/app/assets/javascripts/manatee/helpers/translation.jsh.coffee +7 -0
  23. data/app/assets/javascripts/manatee/helpers/url.jsh.coffee +36 -0
  24. data/app/assets/javascripts/manatee/helpers/util.jsh.coffee +41 -0
  25. data/app/assets/javascripts/manatee/rails_helpers.js +2 -0
  26. data/app/assets/javascripts/manatee/rails_helpers/routes.js.erb +195 -0
  27. data/app/assets/javascripts/manatee/rails_routes.js +1 -0
  28. data/app/assets/javascripts/manatee/renderer.js.erb +53 -0
  29. data/app/assets/javascripts/manatee_railsless.js +1 -0
  30. data/lib/manatee.rb +87 -0
  31. data/lib/manatee/config.rb +51 -0
  32. data/lib/manatee/handler.rb +45 -0
  33. data/lib/manatee/rails.rb +6 -0
  34. data/lib/manatee/rails/extensions.rb +23 -0
  35. data/lib/manatee/rails/handler.rb +16 -0
  36. data/lib/manatee/rails/hash_visitor.rb +35 -0
  37. data/lib/manatee/rails/helper.rb +9 -0
  38. data/lib/manatee/rails/resolver.rb +70 -0
  39. data/lib/manatee/rails/routes_compiler.rb +34 -0
  40. data/lib/manatee/sprockets.rb +8 -0
  41. data/lib/manatee/sprockets/jsh_processor_2x.rb +32 -0
  42. data/lib/manatee/sprockets/jsh_processor_3x.rb +31 -0
  43. data/lib/manatee/version.rb +3 -0
  44. data/manatee.gemspec +32 -0
  45. data/test/example/renderer.js.erb +8 -0
  46. data/test/example/translations.en.yml +410 -0
  47. data/test/example/views/index.jst.eco +12 -0
  48. data/test/helpers/asset_tag_test.rb +175 -0
  49. data/test/helpers/asset_url_test.rb +349 -0
  50. data/test/helpers/form_options_test.rb +718 -0
  51. data/test/helpers/form_tag_test.rb +387 -0
  52. data/test/helpers/javascript_test.rb +39 -0
  53. data/test/helpers/number_test.rb +111 -0
  54. data/test/helpers/tag_test.rb +71 -0
  55. data/test/support/dom_assertion.rb +49 -0
  56. data/test/support/view_test.rb +91 -0
  57. data/test/test_helper.rb +17 -0
  58. 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, "&#x2028;").replace(/\u2029/g, "&#x2029;")
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,5 @@
1
+
2
+ # sanitize
3
+ # sanitize_css
4
+ # strip_links
5
+ # strip_tags
@@ -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, '&#34;').replace(/\'/g, '&#39;')
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,12 @@
1
+
2
+ # concat
3
+ # current_cycle
4
+ # cycle
5
+ # excerpt
6
+ # highlight
7
+ # pluralize
8
+ # reset_cycle
9
+ # safe_concat
10
+ # simple_format
11
+ # truncate
12
+ # word_wrap
@@ -0,0 +1,7 @@
1
+ helper 'translate', (key, options) ->
2
+ I18n.t key, options
3
+ alias 't', 'translate'
4
+
5
+ helper 'localize', (key, options) ->
6
+ I18n.l key, options
7
+ alias 'l', 'localize'
@@ -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 = { '&': '&amp;', '<': '&lt;', '>': '&gt;' }
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,2 @@
1
+ //= require ./helpers
2
+ //= require_tree ./rails_helpers
@@ -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