rack-i18n_routes 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.yardopts ADDED
@@ -0,0 +1,4 @@
1
+ -m markdown
2
+ --no-private
3
+ -
4
+ COPYING
data/COPYING ADDED
@@ -0,0 +1,121 @@
1
+ Creative Commons Legal Code
2
+
3
+ CC0 1.0 Universal
4
+
5
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12
+ HEREUNDER.
13
+
14
+ Statement of Purpose
15
+
16
+ The laws of most jurisdictions throughout the world automatically confer
17
+ exclusive Copyright and Related Rights (defined below) upon the creator
18
+ and subsequent owner(s) (each and all, an "owner") of an original work of
19
+ authorship and/or a database (each, a "Work").
20
+
21
+ Certain owners wish to permanently relinquish those rights to a Work for
22
+ the purpose of contributing to a commons of creative, cultural and
23
+ scientific works ("Commons") that the public can reliably and without fear
24
+ of later claims of infringement build upon, modify, incorporate in other
25
+ works, reuse and redistribute as freely as possible in any form whatsoever
26
+ and for any purposes, including without limitation commercial purposes.
27
+ These owners may contribute to the Commons to promote the ideal of a free
28
+ culture and the further production of creative, cultural and scientific
29
+ works, or to gain reputation or greater distribution for their Work in
30
+ part through the use and efforts of others.
31
+
32
+ For these and/or other purposes and motivations, and without any
33
+ expectation of additional consideration or compensation, the person
34
+ associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35
+ is an owner of Copyright and Related Rights in the Work, voluntarily
36
+ elects to apply CC0 to the Work and publicly distribute the Work under its
37
+ terms, with knowledge of his or her Copyright and Related Rights in the
38
+ Work and the meaning and intended legal effect of CC0 on those rights.
39
+
40
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
41
+ protected by copyright and related or neighboring rights ("Copyright and
42
+ Related Rights"). Copyright and Related Rights include, but are not
43
+ limited to, the following:
44
+
45
+ i. the right to reproduce, adapt, distribute, perform, display,
46
+ communicate, and translate a Work;
47
+ ii. moral rights retained by the original author(s) and/or performer(s);
48
+ iii. publicity and privacy rights pertaining to a person's image or
49
+ likeness depicted in a Work;
50
+ iv. rights protecting against unfair competition in regards to a Work,
51
+ subject to the limitations in paragraph 4(a), below;
52
+ v. rights protecting the extraction, dissemination, use and reuse of data
53
+ in a Work;
54
+ vi. database rights (such as those arising under Directive 96/9/EC of the
55
+ European Parliament and of the Council of 11 March 1996 on the legal
56
+ protection of databases, and under any national implementation
57
+ thereof, including any amended or successor version of such
58
+ directive); and
59
+ vii. other similar, equivalent or corresponding rights throughout the
60
+ world based on applicable law or treaty, and any national
61
+ implementations thereof.
62
+
63
+ 2. Waiver. To the greatest extent permitted by, but not in contravention
64
+ of, applicable law, Affirmer hereby overtly, fully, permanently,
65
+ irrevocably and unconditionally waives, abandons, and surrenders all of
66
+ Affirmer's Copyright and Related Rights and associated claims and causes
67
+ of action, whether now known or unknown (including existing as well as
68
+ future claims and causes of action), in the Work (i) in all territories
69
+ worldwide, (ii) for the maximum duration provided by applicable law or
70
+ treaty (including future time extensions), (iii) in any current or future
71
+ medium and for any number of copies, and (iv) for any purpose whatsoever,
72
+ including without limitation commercial, advertising or promotional
73
+ purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74
+ member of the public at large and to the detriment of Affirmer's heirs and
75
+ successors, fully intending that such Waiver shall not be subject to
76
+ revocation, rescission, cancellation, termination, or any other legal or
77
+ equitable action to disrupt the quiet enjoyment of the Work by the public
78
+ as contemplated by Affirmer's express Statement of Purpose.
79
+
80
+ 3. Public License Fallback. Should any part of the Waiver for any reason
81
+ be judged legally invalid or ineffective under applicable law, then the
82
+ Waiver shall be preserved to the maximum extent permitted taking into
83
+ account Affirmer's express Statement of Purpose. In addition, to the
84
+ extent the Waiver is so judged Affirmer hereby grants to each affected
85
+ person a royalty-free, non transferable, non sublicensable, non exclusive,
86
+ irrevocable and unconditional license to exercise Affirmer's Copyright and
87
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
88
+ maximum duration provided by applicable law or treaty (including future
89
+ time extensions), (iii) in any current or future medium and for any number
90
+ of copies, and (iv) for any purpose whatsoever, including without
91
+ limitation commercial, advertising or promotional purposes (the
92
+ "License"). The License shall be deemed effective as of the date CC0 was
93
+ applied by Affirmer to the Work. Should any part of the License for any
94
+ reason be judged legally invalid or ineffective under applicable law, such
95
+ partial invalidity or ineffectiveness shall not invalidate the remainder
96
+ of the License, and in such case Affirmer hereby affirms that he or she
97
+ will not (i) exercise any of his or her remaining Copyright and Related
98
+ Rights in the Work or (ii) assert any associated claims and causes of
99
+ action with respect to the Work, in either case contrary to Affirmer's
100
+ express Statement of Purpose.
101
+
102
+ 4. Limitations and Disclaimers.
103
+
104
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
105
+ surrendered, licensed or otherwise affected by this document.
106
+ b. Affirmer offers the Work as-is and makes no representations or
107
+ warranties of any kind concerning the Work, express, implied,
108
+ statutory or otherwise, including without limitation warranties of
109
+ title, merchantability, fitness for a particular purpose, non
110
+ infringement, or the absence of latent or other defects, accuracy, or
111
+ the present or absence of errors, whether or not discoverable, all to
112
+ the greatest extent permissible under applicable law.
113
+ c. Affirmer disclaims responsibility for clearing rights of other persons
114
+ that may apply to the Work or any use thereof, including without
115
+ limitation any person's Copyright and Related Rights in the Work.
116
+ Further, Affirmer disclaims responsibility for obtaining any necessary
117
+ consents, permissions or other rights required for any use of the
118
+ Work.
119
+ d. Affirmer understands and acknowledges that Creative Commons is not a
120
+ party to this document and has no duty or obligation with respect to
121
+ this CC0 or use of the Work.
data/README.md ADDED
@@ -0,0 +1,103 @@
1
+ rack-i8n_routes: route translated URLs to their canonical URLs
2
+ ==============================================================
3
+
4
+ rack-i18n_routes is a Rack middleware component that internally re-routes URLS
5
+ that have been translated into untranslated or canonical URL.
6
+
7
+ If you manage a site that has content many languages and also localized URLs,
8
+ you will find `rack-i18n_routes` very useful, especially when used in
9
+ conjunction with `rack-i18n_best_langs`.
10
+
11
+
12
+ Features
13
+ --------
14
+
15
+ The main task of `rack-i18n_routes` is the normalization of request paths.
16
+
17
+ Path normalization rewrites the URI (actually the `PATH_INFO`) so that that the
18
+ downstream applications will have to deal with the normalized path only, instead
19
+ of a myriad of localized paths.
20
+
21
+
22
+ Examples
23
+ --------
24
+
25
+ rack-i18n_routes works like any other Rack middleware component:
26
+
27
+ # in your server.ru rackup file
28
+ require 'rack/i18n_routes'
29
+
30
+ aliases = {
31
+ 'articles' => {
32
+ 'fra' => 'articles',
33
+ 'spa' => ['artículos', 'articulos'],
34
+
35
+ :children => {
36
+ 'the-victory' => {
37
+ 'fra' => 'la-victoire',
38
+ 'spa' => 'la-victoria',
39
+ },
40
+ 'the-block' => {
41
+ 'fra' => 'le-bloc',
42
+ 'spa' => 'el-bloque',
43
+ },
44
+ },
45
+ },
46
+
47
+ 'paintings' => {
48
+ 'fra' => 'peintures',
49
+ 'spa' => 'pinturas',
50
+ }
51
+ }
52
+
53
+ MAPPING_FN = Rack::I18nRoutes::AliasMapping.new(aliases)
54
+
55
+ use Rack::I18nRoutes, MAPPING_FN
56
+ run MyApp
57
+
58
+ Request to `/articulos/el-bloque`, `/articles/le-bloc` and even
59
+ `/articulos/le-bloc` will all be sent to `/articles/the-block`.
60
+
61
+ This component deals only with URL normalization. You can use
62
+ `rack-i18n_best_langs` to automatically associate the translated URLs to their
63
+ languages.
64
+
65
+
66
+ Requirements
67
+ ------------
68
+
69
+ No requirements outside Ruby >= 1.8.7 and Rack.
70
+
71
+
72
+ Install
73
+ -------
74
+
75
+ gem install rack-i18n_routes
76
+
77
+
78
+ Author
79
+ ------
80
+
81
+ * Gioele Barabucci <http://svario.it/gioele> (initial author)
82
+
83
+
84
+ Development
85
+ -----------
86
+
87
+ Code
88
+ : <https://github.com/gioele/rack-i18n_routes>
89
+
90
+ Report issues
91
+ : <https://github.com/gioele/rack-i18n_routes/issues>
92
+
93
+ Documentation
94
+ : <http://rubydoc.info/gems/rack-i18n_routes>
95
+
96
+
97
+ License
98
+ -------
99
+
100
+ This is free software released into the public domain (CC0 license).
101
+
102
+ See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
103
+ for more details.
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ # This is free software released into the public domain (CC0 license).
2
+ #
3
+ # See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
4
+ # for more details.
5
+
6
+
7
+ begin
8
+ require 'bones'
9
+ rescue LoadError
10
+ abort '### Please install the "bones" gem ###'
11
+ end
12
+
13
+ Bones {
14
+ name 'rack-i18n_routes'
15
+ authors 'Gioele Barabucci'
16
+ email 'gioele@svario.it'
17
+ url 'https://github.com/gioele/rack-i18n_routes'
18
+
19
+ version '0.1'
20
+
21
+ ignore_file '.gitignore'
22
+
23
+ depend_on 'rack'
24
+ depend_on 'rack-test', :development => true
25
+ depend_on 'bones-rspec', :development => true
26
+ }
27
+
28
+ task :default => 'spec:run'
29
+ task 'gem:release' => 'spec:run'
@@ -0,0 +1,125 @@
1
+ # This is free software released into the public domain (CC0 license).
2
+ #
3
+ # See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
4
+ # for more details.
5
+
6
+
7
+ require 'rack'
8
+
9
+ # A middleware component to route translated URLs to their canonical URL.
10
+ #
11
+ # @example Basic setup with `AliasMapping`
12
+ #
13
+ # require 'rack/i18n_routes'
14
+ #
15
+ # aliases = {
16
+ # 'articles' => {
17
+ # 'fra' => 'articles',
18
+ # 'spa' => ['artículos', 'articulos'],
19
+ #
20
+ # :children => {
21
+ # 'the-victory' => {
22
+ # 'fra' => 'la-victoire',
23
+ # 'spa' => 'la-victoria',
24
+ # },
25
+ # 'the-block' => {
26
+ # 'fra' => 'le-bloc',
27
+ # 'spa' => 'el-bloque',
28
+ # },
29
+ # },
30
+ # },
31
+ #
32
+ # 'paintings' => {
33
+ # 'fra' => 'peintures',
34
+ # 'spa' => 'pinturas',
35
+ # }
36
+ # }
37
+ #
38
+ # MAPPING_FN = Rack::I18nRoutes::AliasMapping.new(aliases)
39
+ #
40
+ # use Rack::I18nRoutes, MAPPING_FN
41
+ # run MyApp
42
+ #
43
+ # # /articulos/el-bloque => /articles/the-block
44
+ # # /articles/le-bloc => /articles/the-block
45
+ # # /articulos/le-block => /articles/the-block
46
+
47
+ class Rack::I18nRoutes
48
+
49
+ # Set up an i18n routing table.
50
+ #
51
+ # @overload initialize(app, url_mapper)
52
+ #
53
+ # Uses the `#map` method of `url_mapper` to derive the normalized
54
+ # path.
55
+ #
56
+ # @example
57
+ # aliases = {
58
+ # 'articles' => {
59
+ # 'fra' => 'articles',
60
+ # 'spa' => ['artículos', 'articulos'],
61
+ #
62
+ # :children => {
63
+ # 'the-victory' => {
64
+ # 'fra' => 'la-victoire',
65
+ # 'spa' => 'la-victoria',
66
+ # },
67
+ # 'the-block' => {
68
+ # 'fra' => 'le-bloc',
69
+ # 'spa' => 'el-bloque',
70
+ # },
71
+ # },
72
+ # },
73
+ # }
74
+ #
75
+ # MAPPING = Rack::I18nRoutes::AliasMapping.new(aliases)
76
+ # use Rack::I18nRoutes, MAPPING
77
+ #
78
+ # @param app the downstream Rack application
79
+ # @param [#map] url_mapper the mapper that will perform the
80
+ # path normalization
81
+ #
82
+ # @see Rack::I18nRoutes::AliasMapping#initialize
83
+ # @see Rack::I18nRoutes::AliasMappingUpdater#initialize
84
+ #
85
+ # @overload initialize(app, url_mapping_fn)
86
+ #
87
+ # Uses the passed function to derive the normalized path.
88
+ #
89
+ # @example
90
+ # MAPPING_FN = Proc.new do |orig_path|
91
+ # orig_path.sub(r{^/it/}, '/italian/')
92
+ # orig_path.sub('/ristorante/', '/restaurant/')
93
+ # orig_path.sub('/ostello/', '/hostell/')
94
+ # end
95
+ # use Rack::I18nRoutes, MAPPING_FN
96
+ #
97
+ #
98
+ # @param app the downstream Rack application
99
+ # @param [Proc|lambda] url_mapping_fn the function used to
100
+ # perform the path
101
+ # normalization
102
+
103
+ def initialize(app, path_lookup)
104
+ @app = app
105
+
106
+ @path_lookup = path_lookup
107
+ end
108
+
109
+ def call(env)
110
+ path = env['PATH_INFO']
111
+ normalized_path = if @path_lookup.respond_to?(:map)
112
+ @path_lookup.map(path)
113
+ else
114
+ @path_lookup[path]
115
+ end
116
+
117
+ env['rack.i18n-routes_PATH_INFO'] = path
118
+ env['PATH_INFO'] = normalized_path
119
+
120
+ return @app.call(env)
121
+ end
122
+ end
123
+
124
+ require 'rack/i18n_routes/alias_mapping'
125
+ require 'rack/i18n_routes/alias_mapping_updater'
@@ -0,0 +1,130 @@
1
+ # This is free software released into the public domain (CC0 license).
2
+ #
3
+ # See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
4
+ # for more details.
5
+
6
+
7
+ # Describe translated paths as aliases for the normalized ones.
8
+ #
9
+ # To be used as a mapping object for {Rack::I18nRoutes}.
10
+ #
11
+ # If the list of aliases is not known at buildtime, you can use a
12
+ # {Rack::I18nRoutes::AliasMappingUpdater}.
13
+
14
+ class Rack::I18nRoutes::AliasMapping
15
+
16
+ # Create a new alias-based Mapping object.
17
+ #
18
+ # The aliases as stored in a hash. Each keys of the hash contain
19
+ # a normalized path; its value contains another hash that associates
20
+ # _ids_ to one or more translations. The special id `:children` is
21
+ # used to specify the aliases of subpaths.
22
+ #
23
+ # @example A basic set of aliases
24
+ #
25
+ # # "articles" is the normalized path; there are three available
26
+ # # translations: an french translation ("articles") and two
27
+ # # spanish translations ("artículos" and "articulos").
28
+ #
29
+ # 'articles' => {
30
+ # 'fra' => 'articles',
31
+ # 'spa' => ['artículos', 'articulos'],
32
+ # }
33
+ #
34
+ # @example A set of aliases with subpaths
35
+ #
36
+ # # a special id `:children` is used to specify the aliases of
37
+ # # subpaths
38
+ #
39
+ # 'articles' => {
40
+ # 'fra' => 'articles',
41
+ # 'spa' => ['artículos', 'articulos'],
42
+ #
43
+ # :children => {
44
+ # 'the-victory' => {
45
+ # 'fra' => 'la-victoire',
46
+ # 'spa' => 'la-victoria',
47
+ # },
48
+ # 'the-block' => {
49
+ # 'fra' => 'le-bloc',
50
+ # 'spa' => 'el-bloque',
51
+ # },
52
+ # },
53
+ # }
54
+ #
55
+ # @example An AliasMapping as mapping object in I18nRoutes
56
+ #
57
+ # aliases = {
58
+ # 'articles' => {
59
+ # 'fra' => 'articles',
60
+ # 'spa' => ['artículos', 'articulos'],
61
+ #
62
+ # :children => {
63
+ # 'the-victory' => {
64
+ # 'fra' => 'la-victoire',
65
+ # 'spa' => 'la-victoria',
66
+ # },
67
+ # 'the-block' => {
68
+ # 'fra' => 'le-bloc',
69
+ # 'spa' => 'el-bloque',
70
+ # },
71
+ # },
72
+ # },
73
+ # }
74
+ #
75
+ # MAPPING = Rack::I18nRoutes::AliasMapping.new(aliases)
76
+ # use Rack::I18nRoutes, MAPPING
77
+ #
78
+ # @param [Hash] aliases the aliases
79
+
80
+ def initialize(aliases)
81
+ @aliases = aliases
82
+ end
83
+
84
+ def map(path)
85
+ orig_pieces = path.split('/')
86
+ normalized_pieces = []
87
+
88
+ normalized_pieces << orig_pieces.shift
89
+
90
+ aliases = @aliases
91
+
92
+ orig_pieces.each do |orig_piece|
93
+ normalized = normalization_for(orig_piece, aliases)
94
+ replacement = (normalized || orig_piece)
95
+
96
+ normalized_pieces << replacement
97
+
98
+ if !aliases.nil?
99
+ subaliases = aliases[replacement]
100
+ aliases = subaliases[:children] unless subaliases.nil?
101
+ end
102
+ end
103
+
104
+ if path.end_with?('/')
105
+ normalized_pieces << ""
106
+ end
107
+
108
+ return normalized_pieces.join('/')
109
+ end
110
+
111
+ def normalization_for(piece, aliases)
112
+ if aliases.nil?
113
+ return nil
114
+ end
115
+
116
+ entities = aliases.keys
117
+ entities.each do |entity|
118
+ if piece == entity
119
+ return entity
120
+ end
121
+
122
+ subentities = aliases[entity].values.reject { |e| e.is_a? Hash }
123
+ if subentities.any? { |subentity| Array(subentity).any? { |sube| piece == sube } }
124
+ return entity
125
+ end
126
+ end
127
+
128
+ return nil
129
+ end
130
+ end
@@ -0,0 +1,60 @@
1
+ # This is free software released into the public domain (CC0 license).
2
+ #
3
+ # See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
4
+ # for more details.
5
+
6
+
7
+ require 'rack/i18n_routes/alias_mapping'
8
+
9
+ # An AliasMapping that updates on every `#map` call.
10
+ #
11
+ # To be used as a mapping object for {Rack::I18nRoutes} when the aliases
12
+ # needed by {Rack::I18nRoutes::AliasMapping} cannot be statically generated
13
+ # at middleware buildtime.
14
+
15
+ class Rack::I18nRoutes::AliasMappingUpdater
16
+
17
+ # Creates a new alias-based Mapping object that updates its aliases
18
+ # on every path normalization.
19
+ #
20
+ # @example Update aliases for modified user-generated content
21
+ #
22
+ # update_fn = Proc.new do
23
+ # aliases = {}
24
+ #
25
+ # aliases['articles'] => {
26
+ # 'ita' => 'articoli',
27
+ # 'spa' => 'articulos',
28
+ # }
29
+ #
30
+ # if @articles.any { |article| article.changed? }
31
+ # @cached_articles_aliases = all_article_aliases()
32
+ # end
33
+ #
34
+ # aliases['articles'][:children] = @cached_articles_aliases
35
+ # end
36
+ #
37
+ # MAPPING = Rack::I18nRoutes::AliasMappingUpdater.new(update_fn)
38
+ # use Rack::I18nRoutes, MAPPING
39
+ #
40
+ # @example Delegate the aliases generation to another class
41
+ #
42
+ # update_fn = Proc.new { @translation_mngr.updated_aliases }
43
+ #
44
+ # MAPPING = Rack::I18nRoutes::AliasMappingUpdater.new(update_fn)
45
+ # use Rack::I18nRoutes, MAPPING
46
+ #
47
+ # @param [Proc] new_aliases_fn a parameter-less function that returns
48
+ # the new aliases
49
+
50
+ def initialize(new_aliases_fn)
51
+ @new_aliases_fn = new_aliases_fn
52
+ end
53
+
54
+ def map(path)
55
+ aliases = @new_aliases_fn[]
56
+ alias_mapping = Rack::I18nRoutes::AliasMapping.new(aliases)
57
+
58
+ return alias_mapping.map(path)
59
+ end
60
+ end
@@ -0,0 +1,115 @@
1
+ # This is free software released into the public domain (CC0 license).
2
+ #
3
+ # See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
4
+ # for more details.
5
+
6
+
7
+ require File.join(File.dirname(__FILE__), 'spec_helper')
8
+
9
+ TEST_ALIASES = {
10
+ 'articles' => {
11
+ 'fra' => 'articles',
12
+ 'spa' => ['artículos', 'articulos'],
13
+
14
+ :children => {
15
+ 'the-victory' => {
16
+ 'fra' => 'la-victoire',
17
+ 'spa' => 'la-victoria',
18
+ },
19
+ 'the-block' => {
20
+ 'fra' => 'le-bloc',
21
+ 'spa' => 'el-bloque',
22
+ },
23
+ },
24
+ },
25
+ 'paintings' => {
26
+ 'fra' => 'peintures',
27
+ 'spa' => 'pinturas',
28
+ }
29
+ }
30
+
31
+ describe Rack::I18nRoutes do
32
+ def app(*opts)
33
+ builder = Rack::Builder.new do
34
+ use Rack::Lint
35
+ use Rack::I18nRoutes, *opts
36
+ use Rack::Lint
37
+
38
+ run lambda { |env| [200, {"Content-Type" => "text/plain"}, [""]] }
39
+ end
40
+
41
+ return builder.to_app
42
+ end
43
+
44
+ def request_with(path, mapping_fn)
45
+ session = Rack::Test::Session.new(Rack::MockSession.new(app(mapping_fn)))
46
+ session.request(path)
47
+
48
+ return session.last_request
49
+ end
50
+
51
+ context "with an AliasMapping" do
52
+ let(:mapping) { Rack::I18nRoutes::AliasMapping.new(TEST_ALIASES) }
53
+
54
+ it "keep the same path when already normalized" do
55
+ env = request_with('/articles', mapping).env
56
+ path = env['PATH_INFO']
57
+
58
+ path.should == '/articles'
59
+ end
60
+
61
+ it "normalizes a path with 2 components" do
62
+ env = request_with('/articles/le-bloc', mapping).env
63
+ path = env['PATH_INFO']
64
+
65
+ path.should == '/articles/the-block'
66
+ end
67
+
68
+ it "normalizes a path that ends with a slash" do
69
+ env = request_with('/articulos/le-bloc/', mapping).env
70
+ path = env['PATH_INFO']
71
+
72
+ path.should == '/articles/the-block/'
73
+ end
74
+
75
+ it "does not change unknown paths" do
76
+ env = request_with('/foobar', mapping).env
77
+ path = env['PATH_INFO']
78
+
79
+ path.should == '/foobar'
80
+ end
81
+ end
82
+
83
+ context "with an AliasMappingUpdater" do
84
+ let(:mapping) { Rack::I18nRoutes::AliasMappingUpdater.new(Proc.new { TEST_ALIASES }) }
85
+
86
+ it "keep the same path when already normalized" do
87
+ env = request_with('/articles', mapping).env
88
+ path = env['PATH_INFO']
89
+
90
+ path.should == '/articles'
91
+ end
92
+
93
+ it "normalizes a path with 2 components" do
94
+ env = request_with('/articles/le-bloc', mapping).env
95
+ path = env['PATH_INFO']
96
+
97
+ path.should == '/articles/the-block'
98
+ end
99
+
100
+ it "normalizes a path that ends with a slash" do
101
+ env = request_with('/articulos/le-bloc/', mapping).env
102
+ path = env['PATH_INFO']
103
+
104
+ path.should == '/articles/the-block/'
105
+ end
106
+
107
+ it "does not change unknown paths" do
108
+ env = request_with('/foobar', mapping).env
109
+ path = env['PATH_INFO']
110
+
111
+ path.should == '/foobar'
112
+ end
113
+ end
114
+ end
115
+
@@ -0,0 +1,17 @@
1
+ # This is free software released into the public domain (CC0 license).
2
+ #
3
+ # See the `COPYING` file or <http://creativecommons.org/publicdomain/zero/1.0/>
4
+ # for more details.
5
+
6
+
7
+ LIB_DIR = File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib]))
8
+ $LOAD_PATH.unshift(LIB_DIR) unless $LOAD_PATH.include?(LIB_DIR)
9
+
10
+ require 'rack/i18n_routes'
11
+ require 'rack/test'
12
+
13
+ include Rack::Test::Methods
14
+
15
+ RSpec.configure do |config|
16
+ end
17
+
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-i18n_routes
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ version: "0.1"
10
+ platform: ruby
11
+ authors:
12
+ - Gioele Barabucci
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2012-08-05 00:00:00 Z
18
+ dependencies:
19
+ - !ruby/object:Gem::Dependency
20
+ name: rack
21
+ prerelease: false
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ hash: 5
28
+ segments:
29
+ - 1
30
+ - 4
31
+ - 1
32
+ version: 1.4.1
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: rack-test
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 5
44
+ segments:
45
+ - 0
46
+ - 6
47
+ - 1
48
+ version: 0.6.1
49
+ type: :development
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: bones-rspec
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 13
60
+ segments:
61
+ - 2
62
+ - 0
63
+ - 1
64
+ version: 2.0.1
65
+ type: :development
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: bones
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 39
76
+ segments:
77
+ - 3
78
+ - 8
79
+ - 0
80
+ version: 3.8.0
81
+ type: :development
82
+ version_requirements: *id004
83
+ description: |-
84
+ rack-i18n_routes is a Rack middleware component that internally re-routes URLS
85
+ that have been translated into untranslated or canonical URL.
86
+ email: gioele@svario.it
87
+ executables: []
88
+
89
+ extensions: []
90
+
91
+ extra_rdoc_files: []
92
+
93
+ files:
94
+ - .yardopts
95
+ - COPYING
96
+ - README.md
97
+ - Rakefile
98
+ - lib/rack/i18n_routes.rb
99
+ - lib/rack/i18n_routes/alias_mapping.rb
100
+ - lib/rack/i18n_routes/alias_mapping_updater.rb
101
+ - spec/i18n_routes_spec.rb
102
+ - spec/spec_helper.rb
103
+ homepage: https://github.com/gioele/rack-i18n_routes
104
+ licenses: []
105
+
106
+ post_install_message:
107
+ rdoc_options:
108
+ - --main
109
+ - README.md
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ hash: 3
118
+ segments:
119
+ - 0
120
+ version: "0"
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ hash: 3
127
+ segments:
128
+ - 0
129
+ version: "0"
130
+ requirements: []
131
+
132
+ rubyforge_project: rack-i18n_routes
133
+ rubygems_version: 1.8.24
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: rack-i18n_routes is a Rack middleware component that internally re-routes URLS that have been translated into untranslated or canonical URL.
137
+ test_files: []
138
+