onesie 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +5 -0
- data/CHANGELOG.rdoc +3 -0
- data/Manifest.txt +7 -0
- data/README.rdoc +88 -0
- data/Rakefile +15 -0
- data/lib/onesie.rb +81 -0
- data/test/test_onesie.rb +81 -0
- metadata +149 -0
data/.autotest
ADDED
data/CHANGELOG.rdoc
ADDED
data/Manifest.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
= Onesie
|
2
|
+
|
3
|
+
* http://github.com/jbarnette/onesie
|
4
|
+
|
5
|
+
== Description
|
6
|
+
|
7
|
+
A Rack middleware to make URLs in one-page webapps easier.
|
8
|
+
|
9
|
+
In a couple of recent projects, I've needed to avoid full page
|
10
|
+
refreshes as much as possible. In the first, I wanted to keep an
|
11
|
+
embedded music player active while the user was browsing. In the
|
12
|
+
second, I just wanted fancier transitions between pages.
|
13
|
+
|
14
|
+
It's possible to do this in an ad-hoc way, but I very quickly got
|
15
|
+
tired of hacking things together. Enter Onesie.
|
16
|
+
|
17
|
+
Onesie congealed from these requirements:
|
18
|
+
|
19
|
+
* I want a one-page web app,
|
20
|
+
* But I want the back button to work,
|
21
|
+
* And I want search engines to still index some stuff,
|
22
|
+
* And I (mostly) don't want to change the way I write a Rails/Sinatra app.
|
23
|
+
|
24
|
+
If someone visits <tt>http://example.org/meta/contact</tt>, I want
|
25
|
+
them to be redirected to <tt>http://example.org/blah/#/meta/contact</tt>,
|
26
|
+
but after the redirection I still want the original route to be
|
27
|
+
rendered for search engine indexing, etc.
|
28
|
+
|
29
|
+
When Onesie gets a request, it looks to see if under your preferred
|
30
|
+
one-page app path ("blah" in the example above). If it's not, Onesie
|
31
|
+
sets the current request's path in the session and redirects to your
|
32
|
+
app path.
|
33
|
+
|
34
|
+
If a request is under the one-page app path, the "real" request's path
|
35
|
+
is retrieved from the session and used for subsequent routing and
|
36
|
+
rendering. This means that, as above, a request for
|
37
|
+
|
38
|
+
http://example.org/meta/contact
|
39
|
+
|
40
|
+
Will be redirected to
|
41
|
+
|
42
|
+
http://example.org/blah/#/meta/contact
|
43
|
+
|
44
|
+
But still render the correct action in the wrapped app, even though
|
45
|
+
URL fragments aren't passed to the server.
|
46
|
+
|
47
|
+
This is a terrible explanation. I'll write a sample app or something
|
48
|
+
soon.
|
49
|
+
|
50
|
+
== Examples
|
51
|
+
|
52
|
+
require "onesie"
|
53
|
+
|
54
|
+
# in config.ru, after any session support
|
55
|
+
use Onesie
|
56
|
+
|
57
|
+
# in Rails
|
58
|
+
|
59
|
+
unless Rails.env.test?
|
60
|
+
config.middleware.insert_before ActionDispatch::Flash, "Onesie"
|
61
|
+
end
|
62
|
+
|
63
|
+
== Installation
|
64
|
+
|
65
|
+
$ gem install onesie
|
66
|
+
|
67
|
+
== License
|
68
|
+
|
69
|
+
Copyright 2010 John Barnette (code@jbarnette.com)
|
70
|
+
|
71
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
72
|
+
a copy of this software and associated documentation files (the
|
73
|
+
'Software'), to deal in the Software without restriction, including
|
74
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
75
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
76
|
+
permit persons to whom the Software is furnished to do so, subject to
|
77
|
+
the following conditions:
|
78
|
+
|
79
|
+
The above copyright notice and this permission notice shall be
|
80
|
+
included in all copies or substantial portions of the Software.
|
81
|
+
|
82
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
83
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
84
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
85
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
86
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
87
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
88
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "hoe"
|
2
|
+
|
3
|
+
Hoe.plugins.delete :rubyforge
|
4
|
+
Hoe.plugin :doofus, :isolate, :git
|
5
|
+
|
6
|
+
Hoe.spec "onesie" do
|
7
|
+
developer "John Barnette", "code@jbarnette.com"
|
8
|
+
|
9
|
+
self.extra_rdoc_files = Dir["*.rdoc"]
|
10
|
+
self.history_file = "CHANGELOG.rdoc"
|
11
|
+
self.readme_file = "README.rdoc"
|
12
|
+
self.testlib = :minitest
|
13
|
+
|
14
|
+
extra_deps << ["rack", ">= 1.2"]
|
15
|
+
end
|
data/lib/onesie.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require "rack/request"
|
2
|
+
|
3
|
+
# For any request path that doesn't match <tt>options[:path]</tt>,
|
4
|
+
# immediately redirect to <tt>#{options[:path]}/#/#{request-path}</tt>
|
5
|
+
# after dropping the original request path in the session. For any
|
6
|
+
# request that comes in, matches <tt>options[:path]</tt>, and has a
|
7
|
+
# request-path in the session, rewrite the request to match the path
|
8
|
+
# info in the session, remove the session copy, and allow the app to
|
9
|
+
# route/handle as normal.
|
10
|
+
|
11
|
+
class Onesie
|
12
|
+
|
13
|
+
# Duh.
|
14
|
+
VERSION = "1.0.0"
|
15
|
+
|
16
|
+
attr_reader :except, :only, :path
|
17
|
+
|
18
|
+
# Create a new instance of this middleware. +app+ (required) is the
|
19
|
+
# Rack application we're wrapping. Valid +options+:
|
20
|
+
#
|
21
|
+
# :except # An Array of prefix strings or Regexps to ignore.
|
22
|
+
# :log # A lambda for logging, takes one message arg.
|
23
|
+
# :only # An array of prefix strings or Regexps to require.
|
24
|
+
# :path # The correct path for the one-page app. Default: "/"
|
25
|
+
|
26
|
+
def initialize app, options = {}
|
27
|
+
@app = app
|
28
|
+
@except = Array(options[:except]).map { |e| regexpify e }
|
29
|
+
@log = options[:log]
|
30
|
+
@only = Array(options[:only]).map { |o| regexpify o }
|
31
|
+
@path = options[:path] || "/"
|
32
|
+
|
33
|
+
if FalseClass === @log
|
34
|
+
@log = lambda { |m| }
|
35
|
+
elsif !@log
|
36
|
+
if defined? Rails
|
37
|
+
@log = lambda { |m| Rails.logger.info " [Onesie] #{m}" }
|
38
|
+
else
|
39
|
+
@log = lambda { |m| puts "[Onesie] #{m}" }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def call env
|
45
|
+
request = Rack::Request.new env
|
46
|
+
session = env["rack.session"]
|
47
|
+
|
48
|
+
session.delete "onesie.path" if /onesie.clear/ =~ env["REQUEST_URI"]
|
49
|
+
|
50
|
+
allowed = only.empty? || only.any? { |o| o =~ request.path_info }
|
51
|
+
denied = except.any? { |e| e =~ request.path_info }
|
52
|
+
|
53
|
+
return @app.call env if request.xhr? || !allowed || denied
|
54
|
+
|
55
|
+
if request.path_info == @path
|
56
|
+
path = session.delete("onesie.path") || "/"
|
57
|
+
old_path_info = request.path_info
|
58
|
+
|
59
|
+
env["PATH_INFO"] = env["onesie.path"] = path
|
60
|
+
env["REQUEST_URI"].sub!(/^#{old_path_info}/, path)
|
61
|
+
|
62
|
+
@log.call "actual path is #{path}"
|
63
|
+
return @app.call env
|
64
|
+
end
|
65
|
+
|
66
|
+
session["onesie.path"] = dest = request.path_info
|
67
|
+
request.path_info = @path
|
68
|
+
request.path_info << "/" unless "/" == request.path_info[-1, 1]
|
69
|
+
|
70
|
+
dest = request.url + "##{dest}"
|
71
|
+
@log.call "redirecting to #{dest}"
|
72
|
+
|
73
|
+
[302, { "Location" => dest }, "Redirecting."]
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
def regexpify thing
|
79
|
+
Regexp === thing ? thing : /^#{Regexp.escape thing}/
|
80
|
+
end
|
81
|
+
end
|
data/test/test_onesie.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "onesie"
|
3
|
+
|
4
|
+
class TestOnesie < MiniTest::Unit::TestCase
|
5
|
+
def test_initialize
|
6
|
+
app = Onesie.new nil
|
7
|
+
|
8
|
+
assert_equal [], app.except
|
9
|
+
assert_equal [], app.only
|
10
|
+
assert_equal "/", app.path
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_initialize_except
|
14
|
+
app = Onesie.new nil, :except => "/foo"
|
15
|
+
refute_nil app.except.first
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_initialize_only
|
19
|
+
app = Onesie.new nil, :only => "/foo"
|
20
|
+
refute_nil app.only.first
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_call_onesie_clear
|
24
|
+
app = make_app
|
25
|
+
env = make_env "REQUEST_URI" => "onesie.clear"
|
26
|
+
session = env["rack.session"]
|
27
|
+
|
28
|
+
session["onesie.path"] = "foo"
|
29
|
+
|
30
|
+
app.call env
|
31
|
+
refute_equal "foo", session["onesie.path"]
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_call_except
|
35
|
+
called = false
|
36
|
+
app = make_app(:except => "/foo") { called = true }
|
37
|
+
env = make_env
|
38
|
+
|
39
|
+
env["REQUEST_URI"] = env["PATH_INFO"] = "/bar"
|
40
|
+
status, headers, body = app.call env
|
41
|
+
|
42
|
+
refute called
|
43
|
+
assert_equal 302, status
|
44
|
+
assert_match(/\/bar$/, headers["Location"])
|
45
|
+
|
46
|
+
env["REQUEST_URI"] = env["PATH_INFO"] = "/foo"
|
47
|
+
app.call env
|
48
|
+
assert called
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_call_only
|
52
|
+
called = false
|
53
|
+
app = make_app(:only => "/foo") { called = true }
|
54
|
+
env = make_env
|
55
|
+
|
56
|
+
env["REQUEST_URI"] = env["PATH_INFO"] = "/bar"
|
57
|
+
app.call env
|
58
|
+
assert called
|
59
|
+
|
60
|
+
called = false
|
61
|
+
env["REQUEST_URI"] = env["PATH_INFO"] = "/foo"
|
62
|
+
status, headers, body = app.call env
|
63
|
+
|
64
|
+
refute called
|
65
|
+
assert_equal 302, status
|
66
|
+
assert_match(/\/foo$/, headers["Location"])
|
67
|
+
end
|
68
|
+
|
69
|
+
def make_app *args, &block
|
70
|
+
options = Hash === args.last ? args.pop : {}
|
71
|
+
app = args.first || block
|
72
|
+
Onesie.new app, { :log => false }.merge(options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def make_env extras = {}
|
76
|
+
{
|
77
|
+
"rack.session" => {},
|
78
|
+
"rack.url_scheme" => "http",
|
79
|
+
}.merge extras
|
80
|
+
end
|
81
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: onesie
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- John Barnette
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-08-24 00:00:00 -07: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: 11
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 2
|
33
|
+
version: "1.2"
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: hoe
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 21
|
45
|
+
segments:
|
46
|
+
- 2
|
47
|
+
- 6
|
48
|
+
- 1
|
49
|
+
version: 2.6.1
|
50
|
+
type: :development
|
51
|
+
version_requirements: *id002
|
52
|
+
description: |-
|
53
|
+
A Rack middleware to make URLs in one-page webapps easier.
|
54
|
+
|
55
|
+
In a couple of recent projects, I've needed to avoid full page
|
56
|
+
refreshes as much as possible. In the first, I wanted to keep an
|
57
|
+
embedded music player active while the user was browsing. In the
|
58
|
+
second, I just wanted fancier transitions between pages.
|
59
|
+
|
60
|
+
It's possible to do this in an ad-hoc way, but I very quickly got
|
61
|
+
tired of hacking things together. Enter Onesie.
|
62
|
+
|
63
|
+
Onesie congealed from these requirements:
|
64
|
+
|
65
|
+
* I want a one-page web app,
|
66
|
+
* But I want the back button to work,
|
67
|
+
* And I want search engines to still index some stuff,
|
68
|
+
* And I (mostly) don't want to change the way I write a Rails/Sinatra app.
|
69
|
+
|
70
|
+
If someone visits <tt>http://example.org/meta/contact</tt>, I want
|
71
|
+
them to be redirected to <tt>http://example.org/blah/#/meta/contact</tt>,
|
72
|
+
but after the redirection I still want the original route to be
|
73
|
+
rendered for search engine indexing, etc.
|
74
|
+
|
75
|
+
When Onesie gets a request, it looks to see if under your preferred
|
76
|
+
one-page app path ("blah" in the example above). If it's not, Onesie
|
77
|
+
sets the current request's path in the session and redirects to your
|
78
|
+
app path.
|
79
|
+
|
80
|
+
If a request is under the one-page app path, the "real" request's path
|
81
|
+
is retrieved from the session and used for subsequent routing and
|
82
|
+
rendering. This means that, as above, a request for
|
83
|
+
|
84
|
+
http://example.org/meta/contact
|
85
|
+
|
86
|
+
Will be redirected to
|
87
|
+
|
88
|
+
http://example.org/blah/#/meta/contact
|
89
|
+
|
90
|
+
But still render the correct action in the wrapped app, even though
|
91
|
+
URL fragments aren't passed to the server.
|
92
|
+
|
93
|
+
This is a terrible explanation. I'll write a sample app or something
|
94
|
+
soon.
|
95
|
+
email:
|
96
|
+
- code@jbarnette.com
|
97
|
+
executables: []
|
98
|
+
|
99
|
+
extensions: []
|
100
|
+
|
101
|
+
extra_rdoc_files:
|
102
|
+
- Manifest.txt
|
103
|
+
- CHANGELOG.rdoc
|
104
|
+
- README.rdoc
|
105
|
+
files:
|
106
|
+
- .autotest
|
107
|
+
- CHANGELOG.rdoc
|
108
|
+
- Manifest.txt
|
109
|
+
- README.rdoc
|
110
|
+
- Rakefile
|
111
|
+
- lib/onesie.rb
|
112
|
+
- test/test_onesie.rb
|
113
|
+
has_rdoc: true
|
114
|
+
homepage: http://github.com/jbarnette/onesie
|
115
|
+
licenses: []
|
116
|
+
|
117
|
+
post_install_message:
|
118
|
+
rdoc_options:
|
119
|
+
- --main
|
120
|
+
- README.rdoc
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
hash: 3
|
129
|
+
segments:
|
130
|
+
- 0
|
131
|
+
version: "0"
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
hash: 3
|
138
|
+
segments:
|
139
|
+
- 0
|
140
|
+
version: "0"
|
141
|
+
requirements: []
|
142
|
+
|
143
|
+
rubyforge_project: onesie
|
144
|
+
rubygems_version: 1.3.7
|
145
|
+
signing_key:
|
146
|
+
specification_version: 3
|
147
|
+
summary: A Rack middleware to make URLs in one-page webapps easier
|
148
|
+
test_files:
|
149
|
+
- test/test_onesie.rb
|