domain_switcher 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,11 @@
1
+ = DomainSwitcher
2
+
3
+ Rack based domain switcher with configuration
4
+
5
+ * Add a website and a domain object in the context
6
+ * Switch cache namespace
7
+ * Switch cookie domain
8
+
9
+ == Usage for Rails 2.3:
10
+
11
+ config.middleware.use 'DomainSwitcher::Middleware'
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rspec/core/rake_task'
4
+
5
+ desc "Run specs"
6
+ RSpec::Core::RakeTask.new do |t|
7
+ t.pattern = "spec/**/*_spec.rb"
8
+ t.verbose = true
9
+ end
10
+
11
+ task :default => :spec
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'domain_switcher'
@@ -0,0 +1,36 @@
1
+ module DomainSwitcher
2
+ module Cache
3
+
4
+ def self.init
5
+ $domain_switcher_cache_systems = {}
6
+ end
7
+
8
+ def self.switch(website)
9
+ namespace = website.cache_prefix || website.symbol
10
+
11
+ if (c = RAILS_CACHE)
12
+ if !$domain_switcher_cache_systems.key?(namespace)
13
+ Rails.logger.info "DomainSwitcher: Create new cache system for namespace : #{c.class} | #{namespace}"
14
+
15
+ # MemcachedStore => Tested and working with Memcached gem only
16
+ $domain_switcher_cache_systems[namespace] = if ActiveSupport::Cache::MemCacheStore === c
17
+ d = RAILS_CACHE.instance_variable_get('@data')
18
+ if d.prefix_key == namespace
19
+ Rails.logger.info "DomainSwitcher: Use existing cache => namespace is the same: #{namespace}"
20
+ RAILS_CACHE
21
+ else
22
+ c.class.new(d.instance_variable_get('@servers'), d.instance_variable_get('@options').merge(:namespace => namespace, :prefix_key => namespace))
23
+ end
24
+ elsif c.is_a? ActiveSupport::Cache::FileStore
25
+ c.class.new("tmp/cache/#{namespace}")
26
+ else
27
+ c
28
+ end
29
+ end
30
+ ActionController::Caching.send(:class_variable_set, :@@cache_store, (Thread.current[:domain_switcher_cache] = $domain_switcher_cache_systems[namespace]))
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,40 @@
1
+ module DomainSwitcher
2
+ module ConfigBuilder
3
+
4
+ # Create a bunch of method for the configuration hash
5
+ def build_config!(hash)
6
+ raise "Build config only accept hashes and not #{conf.class}" unless Hash === hash
7
+
8
+ # From: https://github.com/kwi/awesome_conf
9
+ m = Module.new do
10
+ instance_methods.each { |selector| remove_method(selector) }
11
+
12
+ hash.each do |k, v|
13
+ const_set('AC_' + k.to_s, v)
14
+ module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
15
+ def #{k}
16
+ #{'AC_'+ k.to_s}
17
+ end
18
+ END_EVAL
19
+
20
+ if TrueClass === v or FalseClass === v
21
+ module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
22
+ def #{k}?
23
+ #{v ? true : false}
24
+ end
25
+ END_EVAL
26
+ end
27
+ end
28
+
29
+ end
30
+
31
+ extend m
32
+ end
33
+
34
+ # Do nothing when a method is missing
35
+ def method_missing(m)
36
+ nil
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,59 @@
1
+ require 'yaml'
2
+
3
+ module DomainSwitcher
4
+ class ConfigLoader
5
+
6
+ attr_reader :websites
7
+ attr_reader :domains
8
+
9
+ def initialize(config_file = 'config/websites.yml')
10
+ begin
11
+ @conf = YAML::load_file(config_file)
12
+ rescue ArgumentError => e
13
+ raise ArgumentError.new("DomainSwitcher: Malformed config file (#{config_file}): #{e}")
14
+ end
15
+ raise ArgumentError.new("DomainSwitcher: Empty config file (#{config_file})") if !@conf or @conf.size == 0
16
+
17
+ @websites = []
18
+ @domains = []
19
+ parse_conf!
20
+ end
21
+
22
+ private
23
+ def parse_conf!
24
+ @conf.each do |website, conf|
25
+ domains = conf.delete('domains')
26
+ default_domain = conf.delete('default_domain')
27
+ raise "DomainSwitcher: Website without domain: #{website}" if !domains or domains.size == 0
28
+ raise "DomainSwitcher: Website without default domain: #{website}" if !default_domain
29
+
30
+ website = Website.new(conf['name'], website, conf)
31
+
32
+ domains.each do |d|
33
+ if String === d
34
+ website << Domain.new(website, d, conf.merge(conf))
35
+ else # It's an hash like this: {"www.website1.com"=>{"name"=>"Website 1 override", "width"=>900}}
36
+ raise "DomainSwitcher: Bad indentation in the config file" if d.size != 1
37
+ website << Domain.new(website, d.first.first, d.first.last.merge(conf))
38
+ end
39
+ end
40
+
41
+ # Set the default domain for the website
42
+ website.default_domain = website.domains.find {|d| d.domain == default_domain } || website.domains.first
43
+
44
+ # Collect websites and domains
45
+ @websites << website
46
+ @domains += website.domains
47
+ end
48
+ end
49
+
50
+ public
51
+ def default_website
52
+ @default_website ||= @websites.find { |w| w.default? } || @websites.first
53
+ end
54
+
55
+ def default_domain
56
+ @default_domain ||= @domains.find { |d| d.default? } || @domains.first
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,10 @@
1
+ module DomainSwitcher
2
+ module Cookie
3
+
4
+ def self.switch(domain, env)
5
+ env['rack.session.options'].update(:domain => domain.cookie_domain)
6
+ env
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ module DomainSwitcher
2
+ class Domain
3
+ include ConfigBuilder
4
+
5
+ attr_reader :website
6
+ attr_reader :domain
7
+
8
+ def initialize(website, domain, conf = {})
9
+ @website = website
10
+ @domain = domain
11
+ build_config!(conf)
12
+ end
13
+
14
+ def cookie_domain
15
+ @cookie_domain ||= domain.split('.').last(2).join('.')
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ module DomainSwitcher
2
+ module Helper
3
+
4
+ def domain
5
+ Thread.current[:domain_switcher_domain]
6
+ end
7
+
8
+ def website
9
+ Thread.current[:domain_switcher_website]
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ module DomainSwitcher
2
+ class Middleware
3
+
4
+ def initialize(app, config = nil)
5
+ @app = app
6
+ @config = config || DomainSwitcher::ConfigLoader.new
7
+ @scanner = DomainSwitcher::Scanner.new(@config.domains)
8
+
9
+ DomainSwitcher::Cache.init
10
+ end
11
+
12
+ def call(env)
13
+ domain = @scanner.scan_domain(env['HTTP_HOST']) || @config.default_domain
14
+
15
+ Rails.logger.debug "DomainSwitcher: Switch context for domain: #{domain.domain} (#{domain.website.name})"
16
+
17
+ if Thread.current[:domain_switcher_domain] != domain
18
+ Thread.current[:domain_switcher_domain] = domain
19
+ Thread.current[:domain_switcher_website] = domain.website
20
+
21
+ DomainSwitcher::Cache::switch(domain.website)
22
+ env = DomainSwitcher::Cookie::switch(domain, env)
23
+ end
24
+
25
+ @app.call(env)
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,12 @@
1
+ # Override the default Rails.cache method in order to user the current memcache store
2
+ module Rails
3
+ class << self
4
+ def cache
5
+ Thread.current[:domain_switcher_cache] || RAILS_CACHE
6
+ end
7
+ end
8
+ end
9
+
10
+ ActionController::Base.send :include, DomainSwitcher::Helper
11
+ ActiveRecord::Base.send :include, DomainSwitcher::Helper
12
+ ActionView::Base.send :include, DomainSwitcher::Helper
@@ -0,0 +1,19 @@
1
+ module DomainSwitcher
2
+ class Scanner
3
+
4
+ @@domain_cache = {}
5
+
6
+ def initialize(domains)
7
+ @domains = domains
8
+
9
+ domains.each do |d|
10
+ @@domain_cache[d.domain] = d
11
+ end
12
+ end
13
+
14
+ def scan_domain(host)
15
+ @@domain_cache[host]
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ module DomainSwitcher
2
+ class Website
3
+ include ConfigBuilder
4
+
5
+ attr_reader :name
6
+ attr_reader :symbol
7
+ attr_reader :domains
8
+ attr :default_domain, true
9
+
10
+ def initialize(name, symbol, conf = {}, domains = [])
11
+ @name = name
12
+ @symbol = symbol
13
+ @domains = domains
14
+ build_config!(conf)
15
+ end
16
+
17
+ def <<(domain)
18
+ @domains << domain
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ module DomainSwitcher
2
+ autoload :Middleware, 'domain_switcher/middleware'
3
+ autoload :ConfigLoader, 'domain_switcher/config_loader'
4
+ autoload :ConfigBuilder, 'domain_switcher/config_builder'
5
+ autoload :Website, 'domain_switcher/website'
6
+ autoload :Domain, 'domain_switcher/domain'
7
+ autoload :Helper, 'domain_switcher/helper'
8
+ autoload :Scanner, 'domain_switcher/scanner'
9
+ autoload :Cache, 'domain_switcher/cache'
10
+ autoload :Cookie, 'domain_switcher/cookie'
11
+ autoload :Logger, 'domain_switcher/logger'
12
+ end
13
+
14
+ current_path = File.dirname(__FILE__)
15
+ require File.join(current_path, 'domain_switcher/rails')
@@ -0,0 +1,27 @@
1
+ website1:
2
+ name: Website 1
3
+ default_domain: www.website1.com
4
+ cache_prefix: first
5
+ domains:
6
+ - www.website1.com:
7
+ width: 900
8
+ name: Website 1 override
9
+ - www.website1-other-domain.com:
10
+ cookie_domain: www.website1-other-domain.com
11
+ - www.website1-other-domain-again.com
12
+ - www.website1-other-domain-again-again.com
13
+ width: 800
14
+ bool: true
15
+ bool2: false
16
+
17
+ website2:
18
+ name: Website2
19
+ default: true
20
+ default_domain: fr.website2.com
21
+ cookie_domain: 'website2cookie.com'
22
+ domains:
23
+ - www.website2.com:
24
+ default: true
25
+ - fr.website2.com
26
+ lang: fr
27
+ false_meth: false
@@ -0,0 +1,24 @@
1
+ website1:
2
+ name: Website 1
3
+ default_domain: www.website1.com
4
+ domains:
5
+ - www.website1.com:
6
+ width: 900
7
+ name: Website 1 override
8
+ - www.website1-other-domain.com:
9
+ cookie_domain: www.website1-other-domain.com
10
+ - www.website1-other-domain-again.com
11
+ - www.website1-other-domain-again-again.com
12
+ width: 800
13
+ bool: true
14
+ bool2: false
15
+
16
+ website2:
17
+ name: Website2
18
+ default: true
19
+ default_domain: fr.website2.com
20
+ domains:
21
+ - www.website2.com:
22
+ default: true
23
+ - fr.website2.com
24
+ lang: fr
@@ -0,0 +1,5 @@
1
+ bouahh
2
+ - broken_file:
3
+ broken:
4
+ - yes
5
+ malformed
File without changes
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ describe :config_loader do
4
+
5
+ context "With incorrect conf file" do
6
+ it "Raise correctly on empty" do
7
+ e = nil
8
+ begin
9
+ DomainSwitcher::ConfigLoader.new(File.join(File.dirname(__FILE__), '../config/websites_empty.yml'))
10
+ rescue Exception => e
11
+ end
12
+ e.to_s.index('DomainSwitcher: Empty config file').should_not be_nil
13
+ end
14
+
15
+ it "Raise correctly on not found config file" do
16
+ e = nil
17
+ begin
18
+ DomainSwitcher::ConfigLoader.new(File.join(File.dirname(__FILE__), '../config/websites_not_existing.yml'))
19
+ rescue Exception => e
20
+ end
21
+ e.to_s.index('No such file or directory').should_not be_nil
22
+ end
23
+
24
+ it "Raise correctly on malformed config file" do
25
+ e = nil
26
+ begin
27
+ DomainSwitcher::ConfigLoader.new(File.join(File.dirname(__FILE__), '../config/websites_broken.yml'))
28
+ rescue ArgumentError => e
29
+ end
30
+ e.to_s.index('DomainSwitcher: Malformed config file').should_not be_nil
31
+ end
32
+
33
+ it "Raise correctly on bad indentation config file" do
34
+ e = nil
35
+ begin
36
+ DomainSwitcher::ConfigLoader.new(File.join(File.dirname(__FILE__), '../config/websites_bad_indentation.yml'))
37
+ rescue Exception => e
38
+ end
39
+ e.to_s.index('DomainSwitcher: Bad indentation in the config file').should_not be_nil
40
+ end
41
+ end
42
+
43
+ context 'With a correct file' do
44
+ before :all do
45
+ @config = DomainSwitcher::ConfigLoader.new(File.join(File.dirname(__FILE__), '../config/websites.yml'))
46
+ end
47
+
48
+ it "load correctly all domains" do
49
+ @config.domains.size.should == 6
50
+ @config.domains.collect(&:domain).uniq.size.should == 6
51
+ @config.domains.first.domain.should == 'www.website1.com'
52
+ end
53
+
54
+ it "load correctly all websites" do
55
+ @config.websites.size.should == 2
56
+ @config.websites.collect(&:symbol).uniq.size.should == 2
57
+ @config.websites.first.symbol.should == 'website1'
58
+ end
59
+
60
+ it "set correctly default domain" do
61
+ @config.websites[0].default_domain.domain.should == 'www.website1.com'
62
+ @config.websites[1].default_domain.domain.should == 'fr.website2.com'
63
+ @config.default_domain.domain.should == 'www.website2.com'
64
+ end
65
+
66
+ it "set correctly default website" do
67
+ @config.default_website.symbol.should == 'website2'
68
+ end
69
+
70
+ it "conf variables are well set" do
71
+ @config.default_website.name.should == 'Website2'
72
+ @config.default_website.default.should == true
73
+ @config.default_website.lang.should == 'fr'
74
+ end
75
+
76
+ it "boolean conf variables are well set" do
77
+ @config.default_website.default?.should == true
78
+ @config.default_website.false_meth?.should == false
79
+ end
80
+
81
+ it "cookie domain can be overrided" do
82
+ @config.default_domain.cookie_domain.should == 'website2cookie.com'
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe :domain_switcher do
4
+
5
+ before :all do
6
+ @conf = DomainSwitcher::ConfigLoader.new(File.join(File.dirname(__FILE__), '../config/websites.yml'))
7
+ @app = DomainSwitcher::Middleware.new(Proc.new {|b| [200, 'OK']}, @conf)
8
+ end
9
+
10
+ it "not create a new memcache connection if namespace is the same" do
11
+ @app.call({'HTTP_HOST' => 'www.website1.com', 'rack.session.options' => {:domain => :test, :something => true}})
12
+ Thread.current[:domain_switcher_cache].instance_variable_get('@data') == RAILS_CACHE
13
+ end
14
+
15
+ it "switch on default when nothing is found" do
16
+ @app.call({'HTTP_HOST' => 'nothing', 'rack.session.options' => {:domain => :test, :something => true}})
17
+ Thread.current[:domain_switcher_domain].should == @conf.default_domain
18
+ Thread.current[:domain_switcher_website].should == @conf.default_website
19
+ end
20
+
21
+ it "correctly switch" do
22
+ @app.call({'HTTP_HOST' => 'www.website1.com', 'rack.session.options' => {:domain => :test, :something => true}})
23
+ Thread.current[:domain_switcher_domain].domain.should == 'www.website1.com'
24
+ Thread.current[:domain_switcher_website].symbol.should == 'website1'
25
+ end
26
+
27
+ it "correctly switch the cookie domain" do
28
+ env = {'HTTP_HOST' => 'www.website1.com', 'rack.session.options' => {:domain => :test, :something => true}}
29
+ @app.call(env)
30
+ env['rack.session.options'][:domain].should == 'website1.com'
31
+ end
32
+
33
+ it "correctly switch the cache namespace" do
34
+ @app.call({'HTTP_HOST' => 'www.website1.com', 'rack.session.options' => {:domain => :test, :something => true}})
35
+ Rails.cache.instance_variable_get('@data').prefix_key.should == 'first'
36
+ @app.call({'HTTP_HOST' => 'www.website2.com', 'rack.session.options' => {:domain => :test, :something => true}})
37
+ Rails.cache.instance_variable_get('@data').prefix_key.should == 'website2'
38
+ @app.call({'HTTP_HOST' => 'www.website1.com', 'rack.session.options' => {:domain => :test, :something => true}})
39
+ Rails.cache.instance_variable_get('@data').prefix_key.should == 'first'
40
+ end
41
+
42
+
43
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe :helper do
4
+
5
+ before :all do
6
+ @conf = DomainSwitcher::ConfigLoader.new(File.join(File.dirname(__FILE__), '../config/websites.yml'))
7
+ @app = DomainSwitcher::Middleware.new(Proc.new {|b| [200, 'OK']}, @conf)
8
+ end
9
+
10
+ it "Check helper" do
11
+ @app.call({'HTTP_HOST' => 'www.website1.com', 'rack.session.options' => {:domain => :test, :something => true}})
12
+ ActionController::Base.new.website.symbol.should == 'website1'
13
+ ActionController::Base.new.website.should == Thread.current[:domain_switcher_website]
14
+ ActionController::Base.new.domain.should == Thread.current[:domain_switcher_domain]
15
+
16
+ @app.call({'HTTP_HOST' => 'www.website2.com', 'rack.session.options' => {:domain => :test, :something => true}})
17
+ ActionController::Base.new.website.symbol.should == 'website2'
18
+ ActionController::Base.new.website.should == Thread.current[:domain_switcher_website]
19
+ ActionController::Base.new.domain.should == Thread.current[:domain_switcher_domain]
20
+ ActionView::Base.new.website.should == Thread.current[:domain_switcher_website]
21
+ ActiveRecord::Base.new.domain.should == Thread.current[:domain_switcher_domain]
22
+ end
23
+
24
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe :scanner do
4
+
5
+ before :all do
6
+ @config = DomainSwitcher::ConfigLoader.new(File.join(File.dirname(__FILE__), '../config/websites.yml'))
7
+ @scanner = DomainSwitcher::Scanner.new(@config.domains)
8
+ end
9
+
10
+ it "return nil when not existing" do
11
+ @scanner.scan_domain(nil).should == nil
12
+ @scanner.scan_domain('something_not_existing').should == nil
13
+ end
14
+
15
+ it "return correct domain when existing" do
16
+ @scanner.scan_domain('www.website2.com').should_not be_nil
17
+ @scanner.scan_domain('www.website2.com').domain == 'www.website2.com'
18
+ @scanner.scan_domain('fr.website2.com').domain == 'fr.website2.com'
19
+ @scanner.scan_domain('www.website1-other-domain-again.com').domain == 'www.website1-other-domain-again.com'
20
+ @scanner.scan_domain('www.website1.com').domain == 'www.website1.com'
21
+ end
22
+
23
+ end
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rspec'
3
+ require 'logger'
4
+
5
+ module ActionController
6
+ class Base
7
+ end
8
+
9
+ module Caching
10
+ end
11
+ end
12
+
13
+ module ActionView
14
+ class Base
15
+ end
16
+ end
17
+
18
+ module ActiveRecord
19
+ class Base
20
+ end
21
+ end
22
+
23
+ module ActiveSupport
24
+ module Cache
25
+ class MemCacheStore
26
+ def initialize(*args)
27
+ @data = Memcached.new(*args)
28
+ end
29
+ end
30
+
31
+ class Memcached
32
+ def initialize(servers, options = {})
33
+ @servers = servers
34
+ @options = options
35
+ end
36
+
37
+ def prefix_key
38
+ @options[:prefix_key]
39
+ end
40
+
41
+ end
42
+
43
+ class FileStore
44
+ end
45
+ end
46
+ end
47
+
48
+ module Rails
49
+ def self.logger
50
+ @logger ||= Logger.new(STDOUT)
51
+ end
52
+ end
53
+
54
+ RAILS_CACHE = ActiveSupport::Cache::MemCacheStore.new(['localhost'], {:test_options => true, :prefix_key => 'first'})
55
+
56
+ require File.dirname(__FILE__) + '/../init.rb'
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: domain_switcher
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Guillaume Luccisano
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-02-24 00:00:00 -08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rack
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 19
30
+ segments:
31
+ - 1
32
+ - 1
33
+ - 0
34
+ version: 1.1.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ description: Rack based domain switcher with configuration
38
+ email: guillaume@justin.tv
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ files:
46
+ - lib/domain_switcher/cache.rb
47
+ - lib/domain_switcher/config_builder.rb
48
+ - lib/domain_switcher/config_loader.rb
49
+ - lib/domain_switcher/cookie.rb
50
+ - lib/domain_switcher/domain.rb
51
+ - lib/domain_switcher/helper.rb
52
+ - lib/domain_switcher/middleware.rb
53
+ - lib/domain_switcher/rails.rb
54
+ - lib/domain_switcher/scanner.rb
55
+ - lib/domain_switcher/website.rb
56
+ - lib/domain_switcher.rb
57
+ - spec/config/websites.yml
58
+ - spec/config/websites_bad_indentation.yml
59
+ - spec/config/websites_broken.yml
60
+ - spec/config/websites_empty.yml
61
+ - spec/domain_switcher/config_spec.rb
62
+ - spec/domain_switcher/domain_switcher_spec.rb
63
+ - spec/domain_switcher/helper_spec.rb
64
+ - spec/domain_switcher/scanner_spec.rb
65
+ - spec/spec_helper.rb
66
+ - Rakefile
67
+ - README.rdoc
68
+ - init.rb
69
+ has_rdoc: true
70
+ homepage: http://www.justin.tv
71
+ licenses: []
72
+
73
+ post_install_message:
74
+ rdoc_options: []
75
+
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ hash: 3
84
+ segments:
85
+ - 0
86
+ version: "0"
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 19
93
+ segments:
94
+ - 1
95
+ - 3
96
+ - 4
97
+ version: 1.3.4
98
+ requirements: []
99
+
100
+ rubyforge_project: domain_switcher
101
+ rubygems_version: 1.4.2
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: Rack based domain switcher with configuration
105
+ test_files: []
106
+