Permalink
Browse files

Merge branch 'journey'

  • Loading branch information...
bogdan committed Feb 28, 2012
2 parents d33b4d3 + 9fbdf1c commit e790a3ee827fa17781ec325984a3bd603c13fc8c
Showing with 123 additions and 108 deletions.
  1. +1 −1 Gemfile
  2. +1 −1 Gemfile.lock
  3. +38 −55 lib/js_routes.rb
  4. +83 −51 lib/routes.js
View
@@ -1,6 +1,6 @@
source "http://rubygems.org"
-gem "rails", ">= 3.1.1"
+gem "rails", ">= 3.2"
group :development do
gem "therubyracer"
View
@@ -102,7 +102,7 @@ PLATFORMS
DEPENDENCIES
bundler (~> 1.0.0)
jeweler (~> 1.6.2)
- rails (>= 3.1.1)
+ rails (>= 3.2)
rcov
rspec (~> 2.7.0)
therubyracer
View
@@ -4,11 +4,7 @@ class JsRoutes
# OPTIONS
#
- DEFAULT_PATH = if Rails.version >= "3.1"
- File.join('app','assets','javascripts','routes.js')
- else
- File.join('public','javascripts','routes.js')
- end
+ DEFAULT_PATH = File.join('app','assets','javascripts','routes.js')
DEFAULTS = {
:namespace => "Routes",
@@ -19,6 +15,18 @@ class JsRoutes
:prefix => ""
}
+ # We encode node symbols as integer to reduce the routes.js file size
+ NODE_TYPES = {
+ :GROUP => 1,
+ :CAT => 2,
+ :SYMBOL => 3,
+ :OR => 4,
+ :STAR => 5,
+ :LITERAL => 6,
+ :SLASH => 7,
+ :DOT => 8
+ }
+
class Options < Struct.new(*DEFAULTS.keys)
def to_hash
Hash[*members.zip(values).flatten(1)].symbolize_keys
@@ -61,6 +69,10 @@ def assert_usable_configuration!
end
true
end
+
+ def json(string)
+ ActiveSupport::JSON.encode(string)
+ end
end
#
@@ -76,6 +88,7 @@ def generate
js.gsub!("NAMESPACE", @options[:namespace])
js.gsub!("DEFAULT_FORMAT", @options[:default_format].to_s)
js.gsub!("PREFIX", @options[:prefix])
+ js.gsub!("NODE_TYPES", json(NODE_TYPES))
js.gsub!("ROUTES", js_routes)
end
@@ -111,68 +124,38 @@ def any_match?(route, matchers)
end
def build_js(route)
- params = build_params route
_ = <<-JS.strip!
- // #{route.name} => #{route_spec(route)}
- #{route.name}_path: function(#{(params + ["options"]).join(", ")}) {
- return Utils.build_path(#{params.size}, #{path_parts(route).inspect}, #{optional_params(route).inspect}, arguments)
+ // #{route.name} => #{route.path.spec}
+ #{route.name}_path: function(#{build_params(route)}) {
+ return Utils.build_path(#{json(route.required_parts)}, #{json(serialize(route.path.spec))}, arguments)
}
JS
end
- # TODO: might be possible to simplify this to use route.path
- # instead of all this path_info.source madness
- def optional_params(route)
- if Rails.version >= "3.2.0"
- return route.optional_parts.map(&:to_s)
- end
- path_info = route.conditions[:path_info]
- path_info_source = path_info.source
- if RUBY_VERSION >= '1.9.2'
- optional_named_captures_regexp = /\?\:.+?\(\?\<(.+?)\>/
- path_info_source.scan(optional_named_captures_regexp).flatten
- else
- re = Regexp.escape("([^/.?]+)")
- optional_named_captures_regexp = /#{re}|\(\?\:.+?\)\?/
- captures = path_info_source.scan(optional_named_captures_regexp).flatten
- named_captures = path_info.named_captures.to_a.sort_by {|cap|cap.last.first}
- captures.zip(named_captures).map do |type, (name, pos)|
- name unless type == '([^/.?]+)'
- end.compact
- end
+ def json(string)
+ self.class.json(string)
end
def build_params route
- required_params(route).map do |name|
+ params = route.required_parts.map do |name|
# prepending each parameter name with underscore
# to prevent conflict with JS reserved words
"_" + name.to_s
- end
+ end << "options"
+ params.join(", ")
end
-
- def path_parts route
- route_spec(route).gsub(/\(\.:format\)$/, "").split(/:[a-z\-_]+/)
- end
-
- def route_spec route
- if Rails.version >= "3.2.0"
- route.path.spec
- else
- route.path
- end.to_s
- end
-
- def required_params(route)
- if Rails.version >= "3.2.0"
- return route.required_parts.map(&:to_s)
- end # if
- optional_named_captures = optional_params(route)
- route.conditions[:path_info].named_captures.to_a.sort_by do |cap1|
- # Hash is not ordered in Ruby 1.8.7
- cap1.last.first
- end.map(&:first).reject do |name|
- optional_named_captures.include?(name.to_s)
- end
+ # This function serializes Journey route into JSON structure
+ # We do not use Hash for human readable serialization
+ # And preffer Array serialization because it is shorter.
+ # Routes.js file will be smaller.
+ def serialize(spec)
+ return nil unless spec
+ return spec.tr(':', '') if spec.is_a?(String)
+ [
+ NODE_TYPES[spec.type],
+ serialize(spec.left),
+ spec.respond_to?(:right) && serialize(spec.right)
+ ]
end
end
View
@@ -1,9 +1,16 @@
(function(){
+ function ParameterMissing(message) {
+ this.message = message;
+ }
+ ParameterMissing.prototype = new Error();
+
var defaults = {
prefix: 'PREFIX',
format: 'DEFAULT_FORMAT'
};
+
+ var NodeTypes = NODE_TYPES
var Utils = {
@@ -32,21 +39,8 @@
return path.replace(/\/+/g, "/").replace(/[\)\(]/g, "").replace(/\.$/m, '').replace(/\/$/m, '');
},
- extract: function(name, options) {
- var o = undefined;
- if (options.hasOwnProperty(name)) {
- o = options[name];
- delete options[name];
- } else if (defaults.hasOwnProperty(name)) {
- o = defaults[name];
- }
- return o;
- },
-
- extract_format: function(options) {
- var format = options.hasOwnProperty("format") ? options.format : defaults.format;
- delete options.format;
- return format ? "." + format : "";
+ set_default_format: function(options) {
+ if (!options.hasOwnProperty("format")) options.format = defaults.format;
},
extract_anchor: function(options) {
@@ -74,49 +68,87 @@
}
},
- build_path: function(number_of_params, parts, optional_params, args) {
+ clone: function (obj) {
+ if (null == obj || "object" != typeof obj) return obj;
+ var copy = obj.constructor();
+ for (var attr in obj) {
+ if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
+ }
+ return copy;
+ },
+
+ prepare_parameters: function(required_parameters, actual_parameters, options) {
+ var result = this.clone(options);
+ for (var i=0; i < required_parameters.length; i++) {
+ result[required_parameters[i]] = actual_parameters[i];
+ }
+ return result;
+ },
+
+ build_path: function(required_parameters, route, args) {
args = Array.prototype.slice.call(args);
- var result = Utils.get_prefix();
- var opts = Utils.extract_options(number_of_params, args);
- if (args.length > number_of_params) {
+ var opts = this.extract_options(required_parameters.length, args);
+ if (args.length > required_parameters.length) {
throw new Error("Too many parameters provided for path");
}
- var params_count = 0, optional_params_count = 0;
- for (var i=0; i < parts.length; i++) {
- var part = parts[i];
- if (Utils.optional_part(part)) {
- var name = optional_params[optional_params_count];
- optional_params_count++;
- // try and find the option in opts
- var optional = Utils.extract(name, opts);
- if (Utils.specified(optional)) {
- result += part;
- result += Utils.path_identifier(optional);
- }
- } else {
- result += part;
- if (params_count < number_of_params) {
- params_count++;
- var value = args.shift();
- if (Utils.specified(value)) {
- result += Utils.path_identifier(value);
+
+ parameters = this.prepare_parameters(required_parameters, args, opts);
+ this.set_default_format(parameters);
+ var result = Utils.get_prefix();
+ var anchor = Utils.extract_anchor(parameters);
+
+ result += this.visit(route, parameters)
+ return Utils.clean_path(result + anchor) + Utils.serialize(parameters);
+ },
+
+ /*
+ * This function is JavaScript impelementation of the
+ * Journey::Visitors::Formatter that builds route by given parameters
+ * and parsed route binary tree.
+ * Binary tree is serialized in the following way:
+ * [node type, left node, right node ]
+ */
+ visit: function(route, options) {
+ var type = route[0];
+ var left = route[1];
+ var right = route[2];
+ switch (type) {
+ case NodeTypes.GROUP:
+ try {
+ return this.visit(left, options);
+ } catch(e) {
+ if (e instanceof ParameterMissing) {
+ return "";
} else {
- throw new Error("Insufficient parameters to build path");
+ throw e;
}
}
- }
+ case NodeTypes.CAT:
+ return this.visit(left, options) + this.visit(right, options);
+ case NodeTypes.SYMBOL:
+ var value = options[left];
+ if (value) {
+ delete options[left];
+ return this.path_identifier(value);
+ } else {
+ throw new ParameterMissing("Route parameter missing: " + left);
+ }
+ /*
+ * I don't know what are these node types
+ * Please send your PR if you do
+ */
+ //case NodeTypes.OR:
+ //case NodeTypes.STAR:
+ case NodeTypes.LITERAL:
+ return left;
+ case NodeTypes.SLASH:
+ return left;
+ case NodeTypes.DOT:
+ return left;
+ default:
+ throw new Error("Unknown Rails node type");
}
- var format = Utils.extract_format(opts);
- var anchor = Utils.extract_anchor(opts);
- return Utils.clean_path(result + format + anchor) + Utils.serialize(opts);
- },
-
- specified: function(value) {
- return !(value === undefined || value === null);
- },
-
- optional_part: function(part) {
- return part.match(/\(/);
+
},
get_prefix: function(){

0 comments on commit e790a3e

Please sign in to comment.