Skip to content

Commit

Permalink
Reimplement CGI and URI modules for loading speed
Browse files Browse the repository at this point in the history
Simply requiring "cgi" takes ~4 ms, while "uri" can take ~18 ms on Ruby
2.0.0, because loading either module results in Ruby interpreter having
to parse a huge amount of code, most of which is not needed for our
purposes.

For instance, we only use `CGI.escape` and `CGI.unescape`, and this
change provides a small replacement module that loads fast.

This also provides a lightweight URI::HTTP replacement that's able to
parse simple URLs. For the purpose of representing URLs of git remote
and handling API communication, this should be enough.
  • Loading branch information
mislav committed Dec 20, 2013
1 parent d562328 commit ad1f39b
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 11 deletions.
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require 'rake/testtask'
def command?(util)
Rake::Task[:load_path].invoke
context = Object.new
require 'uri'
require 'hub/context'
context.extend Hub::Context
context.send(:command?, util)
Expand Down
1 change: 1 addition & 0 deletions lib/hub.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'hub/version' unless defined?(Hub::VERSION)
require 'hub/speedy_stdlib'
require 'hub/args'
require 'hub/ssh_config'
require 'hub/github_api'
Expand Down
2 changes: 0 additions & 2 deletions lib/hub/commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,6 @@ def browse(args)

abort "Usage: hub browse [<USER>/]<REPOSITORY>" unless project

require 'cgi'
# $ hub browse -- wiki
path = case subpage = args.shift
when 'commits'
Expand Down Expand Up @@ -816,7 +815,6 @@ def help(args)
#

def branch_in_url(branch)
require 'cgi'
CGI.escape(branch.short_name).gsub("%2F", "/")
end

Expand Down
13 changes: 6 additions & 7 deletions lib/hub/context.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'shellwords'
require 'forwardable'
require 'uri'
require 'delegate'

module Hub
# Methods for inspecting the environment, such as reading git config,
Expand Down Expand Up @@ -272,7 +272,7 @@ def git_url(options = {})
end
end

class GithubURL < URI::HTTPS
class GithubURL < DelegateClass(URI::HTTP)
extend Forwardable

attr_reader :project
Expand All @@ -282,16 +282,15 @@ class GithubURL < URI::HTTPS
def self.resolve(url, local_repo)
u = URI(url)
if %[http https].include? u.scheme and project = GithubProject.from_url(u, local_repo)
self.new(u.scheme, u.userinfo, u.host, u.port, u.registry,
u.path, u.opaque, u.query, u.fragment, project)
self.new(u, project)
end
rescue URI::InvalidURIError
nil
end

def initialize(*args)
@project = args.pop
super(*args)
def initialize(uri, project)
@project = project
super(uri)
end

# segment of path after the project owner and name
Expand Down
2 changes: 0 additions & 2 deletions lib/hub/github_api.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
require 'uri'
require 'yaml'
require 'forwardable'
require 'fileutils'
require 'cgi'

module Hub
# Client for the GitHub v3 API.
Expand Down
102 changes: 102 additions & 0 deletions lib/hub/speedy_stdlib.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
unless defined?(CGI)
$" << 'cgi.rb'

module CGI
ESCAPE_RE = /[^a-zA-Z0-9 .~_-]/

def self.escape(s)
s.to_s.gsub(ESCAPE_RE) {|match|
'%' + match.unpack('H2' * match.bytesize).join('%').upcase
}.tr(' ', '+')
end

def self.unescape(s)
s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/) {
[$1.delete('%')].pack('H*')
}
end
end
end

unless defined?(URI)
$" << 'uri.rb'

Kernel.module_eval do
def URI(str)
URI.parse(str)
end
end

module URI
InvalidURIError = Class.new(StandardError)

def self.parse(str)
URI::HTTP.new(str)
end

def self.encode_www_form(params)
params.map { |k, v|
if v.class == Array
encode_www_form(v.map { |x| [k, x] })
else
ek = CGI.escape(k)
v.nil? ? ek : "#{ek}=#{CGI.escape(v)}"
end
}.join("&")
end

def self.===(other)
other.respond_to?(:host)
end

class HTTP
attr_accessor :scheme, :user, :password, :host, :path, :query, :fragment
attr_writer :port
alias hostname host

def initialize(str)
m = str.to_s.match(%r{^ ([\w-]+): // (?:([^/@]+)@)? ([^/?#]+) }x)
raise InvalidURIError unless m
_, self.scheme, self.userinfo, host = m.to_a
self.host, self.port = host.split(':', 2)
path, self.fragment = m.post_match.split('#', 2)
self.path, self.query = path.to_s.split('?', 2)
end

def to_s
url = "#{scheme}://"
url << "#{userinfo}@" if user || password
url << host
url << ":#{@port}" if @port
url << path
url << "?#{query}" if query
url << "##{fragment}" if fragment
url
end

def request_uri
url = path
url += "?#{query}" if query
url
end

def port
(@port || (scheme == 'https' ? 443 : 80)).to_i
end

def userinfo=(info)
self.user, self.password = info.to_s.split(':', 2)
info
end

def userinfo
if password then "#{user}:#{password}"
elsif user then user
end
end

def find_proxy
end
end
end
end

0 comments on commit ad1f39b

Please sign in to comment.