volt 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -2
  3. data/Readme.md +97 -56
  4. data/VERSION +1 -1
  5. data/app/volt/assets/js/sockjs-0.3.4.min.js +27 -0
  6. data/app/volt/assets/js/vertxbus.js +216 -0
  7. data/app/volt/tasks/live_query/live_query.rb +5 -5
  8. data/app/volt/tasks/live_query/live_query_pool.rb +1 -1
  9. data/app/volt/tasks/query_tasks.rb +5 -0
  10. data/app/volt/tasks/store_tasks.rb +44 -18
  11. data/docs/WHY.md +10 -0
  12. data/lib/volt/cli.rb +18 -7
  13. data/lib/volt/controllers/model_controller.rb +30 -8
  14. data/lib/volt/extra_core/inflections.rb +63 -0
  15. data/lib/volt/extra_core/inflector/inflections.rb +203 -0
  16. data/lib/volt/extra_core/inflector/methods.rb +63 -0
  17. data/lib/volt/extra_core/inflector.rb +4 -0
  18. data/lib/volt/extra_core/object.rb +9 -0
  19. data/lib/volt/extra_core/string.rb +10 -14
  20. data/lib/volt/models/array_model.rb +45 -27
  21. data/lib/volt/models/cursor.rb +6 -0
  22. data/lib/volt/models/model.rb +127 -12
  23. data/lib/volt/models/model_hash_behaviour.rb +8 -5
  24. data/lib/volt/models/model_helpers.rb +4 -4
  25. data/lib/volt/models/model_state.rb +22 -0
  26. data/lib/volt/models/persistors/array_store.rb +49 -35
  27. data/lib/volt/models/persistors/base.rb +3 -3
  28. data/lib/volt/models/persistors/model_store.rb +17 -6
  29. data/lib/volt/models/persistors/query/query_listener.rb +0 -2
  30. data/lib/volt/models/persistors/store.rb +0 -4
  31. data/lib/volt/models/persistors/store_state.rb +27 -0
  32. data/lib/volt/models/url.rb +2 -2
  33. data/lib/volt/models/validations/errors.rb +0 -0
  34. data/lib/volt/models/validations/length.rb +13 -0
  35. data/lib/volt/models/validations/validations.rb +82 -0
  36. data/lib/volt/models.rb +1 -1
  37. data/lib/volt/page/bindings/attribute_binding.rb +29 -14
  38. data/lib/volt/page/bindings/base_binding.rb +2 -2
  39. data/lib/volt/page/bindings/component_binding.rb +29 -25
  40. data/lib/volt/page/bindings/content_binding.rb +1 -0
  41. data/lib/volt/page/bindings/each_binding.rb +25 -33
  42. data/lib/volt/page/bindings/event_binding.rb +0 -1
  43. data/lib/volt/page/bindings/if_binding.rb +3 -1
  44. data/lib/volt/page/bindings/template_binding.rb +61 -28
  45. data/lib/volt/page/document_events.rb +3 -1
  46. data/lib/volt/page/draw_cycle.rb +22 -0
  47. data/lib/volt/page/page.rb +10 -1
  48. data/lib/volt/page/reactive_template.rb +23 -16
  49. data/lib/volt/page/sub_context.rb +1 -1
  50. data/lib/volt/page/targets/attribute_section.rb +3 -2
  51. data/lib/volt/page/targets/attribute_target.rb +0 -4
  52. data/lib/volt/page/targets/base_section.rb +25 -0
  53. data/lib/volt/page/targets/binding_document/component_node.rb +13 -14
  54. data/lib/volt/page/targets/binding_document/html_node.rb +4 -0
  55. data/lib/volt/page/targets/dom_section.rb +16 -67
  56. data/lib/volt/page/targets/dom_template.rb +99 -0
  57. data/lib/volt/page/targets/helpers/comment_searchers.rb +29 -0
  58. data/lib/volt/page/template_renderer.rb +2 -14
  59. data/lib/volt/reactive/array_extensions.rb +0 -1
  60. data/lib/volt/reactive/event_chain.rb +9 -2
  61. data/lib/volt/reactive/events.rb +44 -37
  62. data/lib/volt/reactive/object_tracking.rb +1 -1
  63. data/lib/volt/reactive/reactive_array.rb +18 -0
  64. data/lib/volt/reactive/reactive_count.rb +108 -0
  65. data/lib/volt/reactive/reactive_generator.rb +44 -0
  66. data/lib/volt/reactive/reactive_value.rb +73 -73
  67. data/lib/volt/reactive/string_extensions.rb +1 -1
  68. data/lib/volt/router/routes.rb +205 -88
  69. data/lib/volt/server/component_handler.rb +3 -1
  70. data/lib/volt/server/html_parser/view_parser.rb +20 -4
  71. data/lib/volt/server/rack/component_paths.rb +13 -10
  72. data/lib/volt/server/rack/index_files.rb +4 -4
  73. data/lib/volt/server/socket_connection_handler.rb +5 -1
  74. data/lib/volt/server.rb +10 -3
  75. data/spec/apps/kitchen_sink/.gitignore +8 -0
  76. data/spec/apps/kitchen_sink/Gemfile +32 -0
  77. data/spec/apps/kitchen_sink/app/home/views/index/index.html +3 -5
  78. data/spec/apps/kitchen_sink/config.ru +4 -0
  79. data/spec/apps/kitchen_sink/public/index.html +2 -2
  80. data/spec/extra_core/inflector_spec.rb +8 -0
  81. data/spec/models/event_chain_spec.rb +18 -0
  82. data/spec/models/model_buffers_spec.rb +9 -0
  83. data/spec/models/model_spec.rb +22 -9
  84. data/spec/models/reactive_array_spec.rb +26 -1
  85. data/spec/models/reactive_call_times_spec.rb +28 -0
  86. data/spec/models/reactive_value_spec.rb +19 -0
  87. data/spec/models/validations_spec.rb +39 -0
  88. data/spec/page/bindings/content_binding_spec.rb +1 -0
  89. data/spec/{templates → page/bindings}/template_binding_spec.rb +54 -0
  90. data/spec/router/routes_spec.rb +156 -8
  91. data/spec/server/html_parser/sandlebars_parser_spec.rb +55 -47
  92. data/spec/server/html_parser/view_parser_spec.rb +3 -0
  93. data/spec/server/rack/asset_files_spec.rb +1 -1
  94. data/spec/spec_helper.rb +25 -11
  95. data/spec/templates/targets/binding_document/component_node_spec.rb +12 -0
  96. data/templates/project/Gemfile.tt +11 -0
  97. data/templates/project/app/home/config/routes.rb +1 -1
  98. data/templates/project/app/home/controllers/index_controller.rb +5 -5
  99. data/templates/project/app/home/views/index/index.html +6 -6
  100. data/volt.gemspec +5 -6
  101. metadata +34 -76
  102. data/app/volt/assets/js/sockjs-0.2.1.min.js +0 -27
  103. data/lib/volt/reactive/object_tracker.rb +0 -107
@@ -0,0 +1,216 @@
1
+ /*
2
+ * Copyright 2011-2012 the original author or authors.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ var vertx = vertx || {};
18
+
19
+ !function(factory) {
20
+ if (typeof define === "function" && define.amd) {
21
+ // Expose as an AMD module with SockJS dependency.
22
+ // "vertxbus" and "sockjs" names are used because
23
+ // AMD module names are derived from file names.
24
+ define("vertxbus", ["sockjs"], factory);
25
+ } else {
26
+ // No AMD-compliant loader
27
+ factory(SockJS);
28
+ }
29
+ }(function(SockJS) {
30
+
31
+ vertx.EventBus = function(url, options) {
32
+
33
+ var that = this;
34
+ var sockJSConn = new SockJS(url, undefined, options);
35
+ var handlerMap = {};
36
+ var replyHandlers = {};
37
+ var state = vertx.EventBus.CONNECTING;
38
+ var sessionID = null;
39
+ var pingTimerID = null;
40
+
41
+ that.onopen = null;
42
+ that.onclose = null;
43
+
44
+ that.login = function(username, password, replyHandler) {
45
+ sendOrPub("send", 'vertx.basicauthmanager.login', {username: username, password: password}, function(reply) {
46
+ if (reply.status === 'ok') {
47
+ that.sessionID = reply.sessionID;
48
+ }
49
+ if (replyHandler) {
50
+ delete reply.sessionID;
51
+ replyHandler(reply)
52
+ }
53
+ });
54
+ }
55
+
56
+ that.send = function(address, message, replyHandler) {
57
+ sendOrPub("send", address, message, replyHandler)
58
+ }
59
+
60
+ that.publish = function(address, message, replyHandler) {
61
+ sendOrPub("publish", address, message, replyHandler)
62
+ }
63
+
64
+ that.registerHandler = function(address, handler) {
65
+ checkSpecified("address", 'string', address);
66
+ checkSpecified("handler", 'function', handler);
67
+ checkOpen();
68
+ var handlers = handlerMap[address];
69
+ if (!handlers) {
70
+ handlers = [handler];
71
+ handlerMap[address] = handlers;
72
+ // First handler for this address so we should register the connection
73
+ var msg = { type : "register",
74
+ address: address };
75
+ sockJSConn.send(JSON.stringify(msg));
76
+ } else {
77
+ handlers[handlers.length] = handler;
78
+ }
79
+ }
80
+
81
+ that.unregisterHandler = function(address, handler) {
82
+ checkSpecified("address", 'string', address);
83
+ checkSpecified("handler", 'function', handler);
84
+ checkOpen();
85
+ var handlers = handlerMap[address];
86
+ if (handlers) {
87
+ var idx = handlers.indexOf(handler);
88
+ if (idx != -1) handlers.splice(idx, 1);
89
+ if (handlers.length == 0) {
90
+ // No more local handlers so we should unregister the connection
91
+
92
+ var msg = { type : "unregister",
93
+ address: address};
94
+ sockJSConn.send(JSON.stringify(msg));
95
+ delete handlerMap[address];
96
+ }
97
+ }
98
+ }
99
+
100
+ that.close = function() {
101
+ checkOpen();
102
+ if (pingTimerID) clearInterval(pingTimerID);
103
+ state = vertx.EventBus.CLOSING;
104
+ sockJSConn.close();
105
+ }
106
+
107
+ that.readyState = function() {
108
+ return state;
109
+ }
110
+
111
+ sockJSConn.onopen = function() {
112
+ // Send the first ping then send a ping every 5 seconds
113
+ sendPing();
114
+ pingTimerID = setInterval(sendPing, 5000);
115
+ state = vertx.EventBus.OPEN;
116
+ if (that.onopen) {
117
+ that.onopen();
118
+ }
119
+ };
120
+
121
+ sockJSConn.onclose = function() {
122
+ state = vertx.EventBus.CLOSED;
123
+ if (that.onclose) {
124
+ that.onclose();
125
+ }
126
+ };
127
+
128
+ sockJSConn.onmessage = function(e) {
129
+ var msg = e.data;
130
+ var json = JSON.parse(msg);
131
+ var body = json.body;
132
+ var replyAddress = json.replyAddress;
133
+ var address = json.address;
134
+ var replyHandler;
135
+ if (replyAddress) {
136
+ replyHandler = function(reply, replyHandler) {
137
+ // Send back reply
138
+ that.send(replyAddress, reply, replyHandler);
139
+ };
140
+ }
141
+ var handlers = handlerMap[address];
142
+ if (handlers) {
143
+ // We make a copy since the handler might get unregistered from within the
144
+ // handler itself, which would screw up our iteration
145
+ var copy = handlers.slice(0);
146
+ for (var i = 0; i < copy.length; i++) {
147
+ copy[i](body, replyHandler);
148
+ }
149
+ } else {
150
+ // Might be a reply message
151
+ var handler = replyHandlers[address];
152
+ if (handler) {
153
+ delete replyHandlers[address];
154
+ handler(body, replyHandler);
155
+ }
156
+ }
157
+ }
158
+
159
+ function sendPing() {
160
+ var msg = {
161
+ type: "ping"
162
+ }
163
+ sockJSConn.send(JSON.stringify(msg));
164
+ }
165
+
166
+ function sendOrPub(sendOrPub, address, message, replyHandler) {
167
+ checkSpecified("address", 'string', address);
168
+ checkSpecified("replyHandler", 'function', replyHandler, true);
169
+ checkOpen();
170
+ var envelope = { type : sendOrPub,
171
+ address: address,
172
+ body: message };
173
+ if (that.sessionID) {
174
+ envelope.sessionID = that.sessionID;
175
+ }
176
+ if (replyHandler) {
177
+ var replyAddress = makeUUID();
178
+ envelope.replyAddress = replyAddress;
179
+ replyHandlers[replyAddress] = replyHandler;
180
+ }
181
+ var str = JSON.stringify(envelope);
182
+ sockJSConn.send(str);
183
+ }
184
+
185
+ function checkOpen() {
186
+ if (state != vertx.EventBus.OPEN) {
187
+ throw new Error('INVALID_STATE_ERR');
188
+ }
189
+ }
190
+
191
+ function checkSpecified(paramName, paramType, param, optional) {
192
+ if (!optional && !param) {
193
+ throw new Error("Parameter " + paramName + " must be specified");
194
+ }
195
+ if (param && typeof param != paramType) {
196
+ throw new Error("Parameter " + paramName + " must be of type " + paramType);
197
+ }
198
+ }
199
+
200
+ function isFunction(obj) {
201
+ return !!(obj && obj.constructor && obj.call && obj.apply);
202
+ }
203
+
204
+ function makeUUID(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
205
+ .replace(/[xy]/g,function(a,b){return b=Math.random()*16,(a=="y"?b&3|8:b|0).toString(16)})}
206
+
207
+ }
208
+
209
+ vertx.EventBus.CONNECTING = 0;
210
+ vertx.EventBus.OPEN = 1;
211
+ vertx.EventBus.CLOSING = 2;
212
+ vertx.EventBus.CLOSED = 3;
213
+
214
+ return vertx.EventBus;
215
+
216
+ });
@@ -24,28 +24,28 @@ class LiveQuery
24
24
 
25
25
  def notify_removed(ids, skip_channel)
26
26
  notify! do |channel|
27
- # puts "Removed: #{ids.inspect} to #{channel.inspect}"
27
+ puts "Removed: #{ids.inspect} to #{channel.inspect}"
28
28
  channel.send_message("removed", nil, @collection, @query, ids)
29
29
  end
30
30
  end
31
31
 
32
32
  def notify_added(index, data, skip_channel)
33
- notify!(skip_channel) do |channel|
34
- # puts "Added: #{index} - #{data.inspect} to #{channel.inspect}"
33
+ notify! do |channel|
34
+ puts "Added: #{index} - #{data.inspect} to #{channel.inspect}"
35
35
  channel.send_message("added", nil, @collection, @query, index, data)
36
36
  end
37
37
  end
38
38
 
39
39
  def notify_moved(id, new_position, skip_channel)
40
40
  notify! do |channel|
41
- # puts "Moved: #{id}, #{new_position} to #{channel.inspect}"
41
+ puts "Moved: #{id}, #{new_position} to #{channel.inspect}"
42
42
  channel.send_message("moved", nil, @collection, @query, id, new_position)
43
43
  end
44
44
  end
45
45
 
46
46
  def notify_changed(id, data, skip_channel)
47
47
  notify!(skip_channel) do |channel|
48
- # puts "Changed: #{id}, #{data} to #{channel.inspect}"
48
+ puts "Changed: #{id}, #{data} to #{channel.inspect}"
49
49
  channel.send_message("changed", nil, @collection, @query, id, data)
50
50
  end
51
51
  end
@@ -15,7 +15,7 @@ class LiveQueryPool < GenericPool
15
15
 
16
16
  def updated_collection(collection, skip_channel)
17
17
  lookup_all(collection).each do |live_query|
18
- # puts "RUN ON: #{live_query} with #{live_query.instance_variable_get('@channels').inspect}"
18
+ puts "RUN ON: #{live_query} with #{live_query.instance_variable_get('@channels').inspect}"
19
19
  live_query.run(skip_channel)
20
20
  end
21
21
  end
@@ -19,12 +19,17 @@ class QueryTasks
19
19
  live_query = @@live_query_pool.lookup(collection, query)
20
20
  track_channel_in_live_query(live_query)
21
21
 
22
+ puts "Load data on #{collection.inspect} - #{query.inspect}"
22
23
  live_query.add_channel(@channel)
23
24
 
24
25
  # Return the initial data
25
26
  return live_query.initial_data
26
27
  end
27
28
 
29
+ def initial_data
30
+ return live_query.initial_data
31
+ end
32
+
28
33
  # Remove a listening channel, the LiveQuery will automatically remove
29
34
  # itsself from the pool when there are no channels.
30
35
  def remove_listener(collection, query)
@@ -1,4 +1,5 @@
1
1
  require 'mongo'
2
+ require 'query_tasks'
2
3
 
3
4
  class StoreTasks
4
5
  def initialize(channel=nil, dispatcher=nil)
@@ -13,29 +14,54 @@ class StoreTasks
13
14
  @@db
14
15
  end
15
16
 
16
- def save(collection, data)
17
- # puts "Insert: #{data.inspect} on #{collection.inspect}"
17
+ def model_errors(collection, data)
18
+ model_name = collection[1..-1].singularize.camelize
19
+
20
+ # TODO: Security check to make sure we have a valid model
21
+ begin
22
+ model_class = Object.send(:const_get, model_name)
23
+ rescue NameError => e
24
+ model_class = nil
25
+ end
26
+
27
+ if model_class
28
+ return model_class.new(data).errors
29
+ end
30
+
31
+ return {}
32
+ end
18
33
 
34
+ def save(collection, data)
35
+ puts "Insert: #{data.inspect} on #{collection.inspect}"
19
36
  data = data.symbolize_keys
20
- id = data[:_id]
21
37
 
22
- # Try to create
23
- # TODO: Seems mongo is dumb and doesn't let you upsert with custom id's
24
- begin
25
- @@db[collection].insert(data)
26
- rescue Mongo::OperationFailure => error
27
- # Really mongo client?
28
- if error.message[/^11000[:]/]
29
- # Update because the id already exists
30
- update_data = data.dup
31
- update_data.delete(:_id)
32
- @@db[collection].update({:_id => id}, update_data)
33
- else
34
- raise
38
+ errors = model_errors(collection, data)
39
+
40
+ if errors.size == 0
41
+ id = data[:_id]
42
+
43
+ # Try to create
44
+ # TODO: Seems mongo is dumb and doesn't let you upsert with custom id's
45
+ begin
46
+ @@db[collection].insert(data)
47
+ rescue Mongo::OperationFailure => error
48
+ # Really mongo client?
49
+ if error.message[/^11000[:]/]
50
+ # Update because the id already exists
51
+ update_data = data.dup
52
+ update_data.delete(:_id)
53
+ @@db[collection].update({:_id => id}, update_data)
54
+ else
55
+ raise
56
+ end
35
57
  end
36
- end
37
58
 
38
- QueryTasks.live_query_pool.updated_collection(collection, @channel)
59
+ puts "SAVE: #{@channel.inspect}"
60
+ QueryTasks.live_query_pool.updated_collection(collection, @channel)
61
+ return {}
62
+ else
63
+ return errors
64
+ end
39
65
  end
40
66
 
41
67
  def delete(collection, id)
data/docs/WHY.md CHANGED
@@ -25,6 +25,14 @@ Volt's reactive objects contain extra data about how to propigate events. Thing
25
25
 
26
26
 
27
27
 
28
+ # Why Volt is Awesome
29
+
30
+ - only the relevant DOM is updated. There is no match and patch algorithm to update from strings like other frameworks, all associations are tracked through our reactive values, so we know exactly what needs to be updated without the need to generate any extra HTML. This has a few advantages, namely that things like input fields are retained, so any properties (focus, tab position, etc...) are also retained.
31
+
32
+
33
+
34
+
35
+
28
36
 
29
37
 
30
38
 
@@ -38,6 +46,8 @@ Volt's reactive objects contain extra data about how to propigate events. Thing
38
46
 
39
47
  # Why Ruby
40
48
 
49
+ Isomorphic type system with javascript
50
+
41
51
  In web development today, JavaScript gets to be the default language by virtue of being in the browser. JavaScript is a very good language, but it has a lot of warts. (See http://wtfjs.com/ for some great examples) Some of these can introduce bugs, others are just difficult to deal with. JavaScript was rushed to market quickly and standardized very quickly. Ruby was used by a small community for years while most of the kinks were worked out. Ruby also has some great concepts such as [uniform access](http://en.wikipedia.org/wiki/Uniform_access_principle), [mixin's](http://en.wikipedia.org/wiki/Mixin), [duck typing](http://en.wikipedia.org/wiki/Duck_typing), and [blocks](http://yehudakatz.com/2012/01/10/javascript-needs-blocks/) to name a few. While many of these features can be implemented in JavaScript in userland, few are standardardized and the solutions are seldom eloquent.
42
52
 
43
53
  [5,10,1].sort()
data/lib/volt/cli.rb CHANGED
@@ -23,7 +23,12 @@ class CLI < Thor
23
23
  desc "server", "run the server on the project in the current directory"
24
24
  method_option :port, :type => :string, :aliases => '-p', :banner => 'specify which port the server should run on'
25
25
  def server
26
- require 'thin'
26
+ if RUBY_PLATFORM == 'java'
27
+ require 'volt/server'
28
+ else
29
+ require 'thin'
30
+ end
31
+
27
32
  require 'fileutils'
28
33
 
29
34
  # If we're in a Volt project, clear the temp directory
@@ -36,14 +41,20 @@ class CLI < Thor
36
41
  return
37
42
  end
38
43
 
39
- ENV['SERVER'] = 'true'
40
- args = ['start', '--threaded', '--max-persistent-conns', '300', "--max-conns", "400"]
44
+ if RUBY_PLATFORM == 'java'
45
+ server = Server.new.app
46
+ Rack::Handler::Jubilee.run(server)
47
+ Thread.stop
48
+ else
49
+ ENV['SERVER'] = 'true'
50
+ args = ['start', '--threaded', '--max-persistent-conns', '300', "--max-conns", "400"]
41
51
 
42
- if options[:port]
43
- args += ['-p', options[:port].to_s]
44
- end
52
+ if options[:port]
53
+ args += ['-p', options[:port].to_s]
54
+ end
45
55
 
46
- Thin::Runner.new(args).run!
56
+ Thin::Runner.new(args).run!
57
+ end
47
58
 
48
59
  # require 'volt/server'
49
60
  #
@@ -1,35 +1,57 @@
1
1
  class ModelController
2
2
  def self.model(val)
3
- @@default_model = val
3
+ @default_model = val
4
4
  end
5
5
 
6
6
  # Sets the current model on this controller
7
- def model(val)
7
+ def model=(val)
8
+ # Start with a nil reactive value.
9
+ @model ||= ReactiveValue.new(Proc.new { nil })
10
+
8
11
  if Symbol === val || String === val
9
- collections = [:page, :store, :params]
12
+ collections = [:page, :store, :params, :controller]
10
13
  if collections.include?(val.to_sym)
11
- @model = self.send(val)
14
+ @model.cur = self.send(val).cur
12
15
  else
13
16
  raise "#{val} is not the name of a valid model, choose from: #{collections.join(', ')}"
14
17
  end
15
18
  elsif val
16
- @model = val
19
+ @model.cur = val.cur
17
20
  else
18
21
  raise "model can not be #{val.inspect}"
19
22
  end
20
23
  end
21
24
 
25
+ def model
26
+ @model
27
+ end
28
+
22
29
  def self.new(*args, &block)
23
30
  inst = self.allocate
24
- if @@default_model
25
- inst.model(@@default_model || :page)
26
- end
31
+
32
+ inst.model = (@default_model || :controller)
27
33
 
28
34
  inst.initialize(*args, &block)
29
35
 
30
36
  return inst
31
37
  end
32
38
 
39
+ def initialize(*args)
40
+
41
+
42
+ # Set the instance variable to match any passed in arguments
43
+ if args.size > 0
44
+ args[0].each_pair do |key, value|
45
+ instance_variable_set(:"@#{key}", value)
46
+ end
47
+ end
48
+ end
49
+
50
+ # Change the url params, similar to redirecting to a new url
51
+ def go(url)
52
+ self.url.parse(url)
53
+ end
54
+
33
55
  def page
34
56
  $page.page
35
57
  end
@@ -0,0 +1,63 @@
1
+ require 'volt/extra_core/inflector/inflections'
2
+
3
+ Inflector.inflections(:en) do |inflect|
4
+ inflect.plural(/$/, 's')
5
+ inflect.plural(/s$/i, 's')
6
+ inflect.plural(/^(ax|test)is$/i, '\1es')
7
+ inflect.plural(/(octop|vir)us$/i, '\1i')
8
+ inflect.plural(/(octop|vir)i$/i, '\1i')
9
+ inflect.plural(/(alias|status)$/i, '\1es')
10
+ inflect.plural(/(bu)s$/i, '\1ses')
11
+ inflect.plural(/(buffal|tomat)o$/i, '\1oes')
12
+ inflect.plural(/([ti])um$/i, '\1a')
13
+ inflect.plural(/([ti])a$/i, '\1a')
14
+ inflect.plural(/sis$/i, 'ses')
15
+ inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
16
+ inflect.plural(/(hive)$/i, '\1s')
17
+ inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
18
+ inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
19
+ inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
20
+ inflect.plural(/^(m|l)ouse$/i, '\1ice')
21
+ inflect.plural(/^(m|l)ice$/i, '\1ice')
22
+ inflect.plural(/^(ox)$/i, '\1en')
23
+ inflect.plural(/^(oxen)$/i, '\1')
24
+ inflect.plural(/(quiz)$/i, '\1zes')
25
+
26
+ inflect.singular(/s$/i, '')
27
+ inflect.singular(/(ss)$/i, '\1')
28
+ inflect.singular(/(n)ews$/i, '\1ews')
29
+ inflect.singular(/([ti])a$/i, '\1um')
30
+ inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis')
31
+ inflect.singular(/(^analy)(sis|ses)$/i, '\1sis')
32
+ inflect.singular(/([^f])ves$/i, '\1fe')
33
+ inflect.singular(/(hive)s$/i, '\1')
34
+ inflect.singular(/(tive)s$/i, '\1')
35
+ inflect.singular(/([lr])ves$/i, '\1f')
36
+ inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
37
+ inflect.singular(/(s)eries$/i, '\1eries')
38
+ inflect.singular(/(m)ovies$/i, '\1ovie')
39
+ inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
40
+ inflect.singular(/^(m|l)ice$/i, '\1ouse')
41
+ inflect.singular(/(bus)(es)?$/i, '\1')
42
+ inflect.singular(/(o)es$/i, '\1')
43
+ inflect.singular(/(shoe)s$/i, '\1')
44
+ inflect.singular(/(cris|test)(is|es)$/i, '\1is')
45
+ inflect.singular(/^(a)x[ie]s$/i, '\1xis')
46
+ inflect.singular(/(octop|vir)(us|i)$/i, '\1us')
47
+ inflect.singular(/(alias|status)(es)?$/i, '\1')
48
+ inflect.singular(/^(ox)en/i, '\1')
49
+ inflect.singular(/(vert|ind)ices$/i, '\1ex')
50
+ inflect.singular(/(matr)ices$/i, '\1ix')
51
+ inflect.singular(/(quiz)zes$/i, '\1')
52
+ inflect.singular(/(database)s$/i, '\1')
53
+
54
+ inflect.irregular('person', 'people')
55
+ inflect.irregular('man', 'men')
56
+ inflect.irregular('child', 'children')
57
+ inflect.irregular('sex', 'sexes')
58
+ inflect.irregular('move', 'moves')
59
+ # inflect.irregular('cow', 'kine')
60
+ inflect.irregular('zombie', 'zombies')
61
+
62
+ inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police))
63
+ end