utopia 0.12.6 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -2
- data/Gemfile +6 -0
- data/README.md +48 -14
- data/Rakefile +5 -0
- data/bin/utopia +132 -15
- data/lib/utopia.rb +13 -10
- data/lib/utopia/content.rb +140 -0
- data/lib/utopia/content/link.rb +124 -0
- data/lib/utopia/content/links.rb +228 -0
- data/lib/utopia/content/node.rb +387 -0
- data/lib/utopia/content/processor.rb +128 -0
- data/lib/utopia/content/tag.rb +102 -0
- data/lib/utopia/controller.rb +137 -0
- data/lib/utopia/controller/action.rb +112 -0
- data/lib/utopia/controller/base.rb +174 -0
- data/lib/utopia/{middleware/controller → controller}/variables.rb +36 -38
- data/lib/utopia/exception_handler.rb +79 -0
- data/lib/utopia/extensions/array.rb +2 -2
- data/lib/utopia/localization.rb +143 -0
- data/lib/utopia/mail_exceptions.rb +136 -0
- data/lib/utopia/middleware.rb +7 -22
- data/lib/utopia/path.rb +150 -60
- data/lib/utopia/redirector.rb +152 -0
- data/lib/utopia/{extensions/hash.rb → session.rb} +4 -6
- data/lib/utopia/session/encrypted_cookie.rb +46 -48
- data/lib/utopia/{middleware/directory_index.rb → session/lazy_hash.rb} +44 -27
- data/lib/utopia/static.rb +255 -0
- data/lib/utopia/tags/deferred.rb +12 -8
- data/lib/utopia/tags/environment.rb +18 -6
- data/lib/utopia/tags/node.rb +12 -8
- data/lib/utopia/tags/override.rb +12 -12
- data/lib/utopia/version.rb +1 -1
- data/setup/.bowerrc +3 -0
- data/{lib/utopia/setup → setup}/Gemfile +1 -1
- data/setup/Rakefile +4 -0
- data/{lib/utopia/setup → setup}/cache/head/readme.txt +0 -0
- data/{lib/utopia/setup → setup}/cache/meta/readme.txt +0 -0
- data/setup/config.ru +64 -0
- data/{lib/utopia/setup → setup}/lib/readme.txt +0 -0
- data/{lib/utopia/setup → setup}/pages/_heading.xnode +0 -0
- data/{lib/utopia/setup → setup}/pages/_page.xnode +1 -1
- data/{lib/utopia/setup → setup}/pages/_static/icon.png +0 -0
- data/setup/pages/_static/site.css +70 -0
- data/{lib/utopia/setup → setup}/pages/errors/exception.xnode +0 -0
- data/{lib/utopia/setup → setup}/pages/errors/file-not-found.xnode +0 -0
- data/{lib/utopia/setup → setup}/pages/links.yaml +0 -0
- data/setup/pages/welcome/index.xnode +17 -0
- data/{lib/utopia/setup → setup}/public/readme.txt +0 -0
- data/spec/utopia/content/link_spec.rb +108 -0
- data/spec/utopia/content/links/foo/index.xnode +0 -0
- data/spec/utopia/content/links/foo/links.yaml +2 -0
- data/spec/utopia/content/links/foo/test.de.xnode +0 -0
- data/spec/utopia/content/links/foo/test.en.xnode +0 -0
- data/spec/utopia/content/links/links.yaml +9 -0
- data/spec/utopia/content/links/welcome.xnode +0 -0
- data/spec/utopia/content/localized/five/index.en.xnode +0 -0
- data/spec/utopia/content/localized/four/index.en.xnode +0 -0
- data/spec/utopia/content/localized/four/index.zh.xnode +0 -0
- data/spec/utopia/content/localized/four/links.yaml +4 -0
- data/spec/utopia/content/localized/links.yaml +16 -0
- data/spec/utopia/content/localized/one.xnode +0 -0
- data/spec/utopia/content/localized/three/index.xnode +0 -0
- data/spec/utopia/content/localized/two.en.xnode +0 -0
- data/spec/utopia/content/localized/two.zh.xnode +0 -0
- data/spec/utopia/content/node/ordered/first.xnode +0 -0
- data/spec/utopia/content/node/ordered/index.xnode +0 -0
- data/spec/utopia/content/node/ordered/links.yaml +4 -0
- data/spec/utopia/content/node/ordered/second.xnode +0 -0
- data/spec/utopia/content/node/related/foo.en.xnode +0 -0
- data/spec/utopia/content/node/related/foo.ja.xnode +0 -0
- data/spec/utopia/content/node/related/links.yaml +4 -0
- data/spec/utopia/content/node_spec.rb +63 -0
- data/spec/utopia/{middleware/content_spec.rb → content/processor_spec.rb} +34 -23
- data/spec/utopia/content_spec.rb +87 -0
- data/spec/utopia/content_spec.ru +10 -0
- data/spec/utopia/{middleware/controller_spec.rb → controller_spec.rb} +61 -16
- data/spec/utopia/controller_spec.ru +4 -0
- data/spec/utopia/extensions_spec.rb +6 -17
- data/spec/utopia/localization_spec.rb +60 -0
- data/spec/utopia/localization_spec.ru +11 -0
- data/{lib/utopia/tags.rb → spec/utopia/middleware_spec.rb} +8 -14
- data/spec/utopia/{middleware/content_root → pages}/_heading.xnode +0 -0
- data/spec/utopia/pages/content/_show-value.xnode +1 -0
- data/spec/utopia/pages/content/test-partial.xnode +1 -0
- data/spec/utopia/pages/controller/controller.rb +28 -0
- data/spec/utopia/pages/controller/index.xnode +1 -0
- data/spec/utopia/pages/controller/nested/controller.rb +4 -0
- data/spec/utopia/{middleware/content_root → pages}/index.xnode +0 -0
- data/spec/utopia/pages/localized.de.txt +1 -0
- data/spec/utopia/pages/localized.en.txt +1 -0
- data/spec/utopia/pages/localized.jp.txt +1 -0
- data/spec/utopia/pages/node/index.xnode +1 -0
- data/spec/utopia/pages/test.txt +1 -0
- data/spec/utopia/path_spec.rb +109 -0
- data/spec/utopia/rack_spec.rb +2 -0
- data/spec/utopia/session_spec.rb +82 -0
- data/spec/utopia/session_spec.ru +20 -0
- data/spec/utopia/spec_helper.rb +16 -0
- data/{lib/utopia/extensions/string.rb → spec/utopia/static_spec.rb} +24 -15
- data/spec/utopia/static_spec.ru +4 -0
- data/utopia.gemspec +3 -3
- metadata +138 -54
- data/lib/utopia/extensions/regexp.rb +0 -33
- data/lib/utopia/link.rb +0 -288
- data/lib/utopia/middleware/all.rb +0 -33
- data/lib/utopia/middleware/content.rb +0 -157
- data/lib/utopia/middleware/content/node.rb +0 -386
- data/lib/utopia/middleware/content/processor.rb +0 -123
- data/lib/utopia/middleware/controller.rb +0 -130
- data/lib/utopia/middleware/controller/action.rb +0 -121
- data/lib/utopia/middleware/controller/base.rb +0 -184
- data/lib/utopia/middleware/exception_handler.rb +0 -80
- data/lib/utopia/middleware/localization.rb +0 -147
- data/lib/utopia/middleware/localization/name.rb +0 -69
- data/lib/utopia/middleware/mail_exceptions.rb +0 -138
- data/lib/utopia/middleware/redirector.rb +0 -146
- data/lib/utopia/middleware/requester.rb +0 -126
- data/lib/utopia/middleware/static.rb +0 -295
- data/lib/utopia/setup.rb +0 -60
- data/lib/utopia/setup/config.ru +0 -47
- data/lib/utopia/setup/pages/_static/background.png +0 -0
- data/lib/utopia/setup/pages/_static/site.css +0 -48
- data/lib/utopia/setup/pages/welcome/index.xnode +0 -7
- data/lib/utopia/tag.rb +0 -105
- data/lib/utopia/tags/all.rb +0 -34
@@ -0,0 +1,136 @@
|
|
1
|
+
# Copyright, 2013, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require 'net/smtp'
|
22
|
+
require 'mail'
|
23
|
+
|
24
|
+
module Utopia
|
25
|
+
# Catches all exceptions raised from the app it wraps and sends a useful email with the exception, stacktrace, and contents of the environment.
|
26
|
+
class MailExceptions
|
27
|
+
# A basic local non-authenticated SMTP server.
|
28
|
+
LOCAL_SMTP = [:smtp, {
|
29
|
+
:address => "localhost",
|
30
|
+
:port => 25,
|
31
|
+
:enable_starttls_auto => false
|
32
|
+
}]
|
33
|
+
|
34
|
+
def initialize(app, config = {})
|
35
|
+
@app = app
|
36
|
+
|
37
|
+
@to = config[:to] || "postmaster"
|
38
|
+
@from = config.fetch(:from) {(ENV['USER'] || 'rack') + "@localhost"}
|
39
|
+
@subject = config[:subject] || '%{exception} [PID %{pid} : %{cwd}]'
|
40
|
+
@delivery_method = config.fetch(:delivery_method, LOCAL_SMTP)
|
41
|
+
|
42
|
+
@dump_environment = config.fetch(:dump_environment, false)
|
43
|
+
end
|
44
|
+
|
45
|
+
def call(env)
|
46
|
+
begin
|
47
|
+
return @app.call(env)
|
48
|
+
rescue => exception
|
49
|
+
send_notification exception, env
|
50
|
+
|
51
|
+
raise
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
REQUEST_KEYS = [:ip, :referrer, :path, :user_agent]
|
58
|
+
|
59
|
+
def generate_body(exception, env)
|
60
|
+
io = StringIO.new
|
61
|
+
|
62
|
+
# Dump out useful rack environment variables:
|
63
|
+
request = Rack::Request.new(env)
|
64
|
+
|
65
|
+
io.puts "#{request.request_method} #{request.url}"
|
66
|
+
|
67
|
+
io.puts
|
68
|
+
|
69
|
+
REQUEST_KEYS.each do |key|
|
70
|
+
value = request.send(key)
|
71
|
+
io.puts "request.#{key}: #{value.inspect}"
|
72
|
+
end
|
73
|
+
|
74
|
+
request.params.each do |key, value|
|
75
|
+
io.puts "request.params.#{key}: #{value.inspect}"
|
76
|
+
end
|
77
|
+
|
78
|
+
io.puts
|
79
|
+
|
80
|
+
io.puts "#{exception.class.name}: #{exception.to_s}"
|
81
|
+
|
82
|
+
if exception.respond_to?(:backtrace)
|
83
|
+
io.puts exception.backtrace
|
84
|
+
else
|
85
|
+
io.puts exception.to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
return io.string
|
89
|
+
end
|
90
|
+
|
91
|
+
def generate_mail(exception, env)
|
92
|
+
attributes = {
|
93
|
+
exception: exception.class.name,
|
94
|
+
pid: $$,
|
95
|
+
cwd: Dir.getwd,
|
96
|
+
}
|
97
|
+
|
98
|
+
mail = Mail.new(
|
99
|
+
:from => @from,
|
100
|
+
:to => @to,
|
101
|
+
:subject => @subject % attributes
|
102
|
+
)
|
103
|
+
|
104
|
+
mail.text_part = Mail::Part.new
|
105
|
+
mail.text_part.body = generate_body(exception, env)
|
106
|
+
|
107
|
+
if body = extract_body(env) and body.size > 0
|
108
|
+
mail.attachments['body.bin'] = body
|
109
|
+
end
|
110
|
+
|
111
|
+
if @dump_environment
|
112
|
+
mail.attachments['environment.yaml'] = YAML::dump(env)
|
113
|
+
end
|
114
|
+
|
115
|
+
return mail
|
116
|
+
end
|
117
|
+
|
118
|
+
def send_notification(exception, env)
|
119
|
+
mail = generate_mail(exception, env)
|
120
|
+
|
121
|
+
mail.delivery_method(*@delivery_method) if @delivery_method
|
122
|
+
|
123
|
+
mail.deliver
|
124
|
+
rescue => mail_exception
|
125
|
+
$stderr.puts mail_exception.to_s
|
126
|
+
$stderr.puts mail_exception.backtrace
|
127
|
+
end
|
128
|
+
|
129
|
+
def extract_body(env)
|
130
|
+
if io = env['rack.input']
|
131
|
+
io.rewind if io.respond_to?(:rewind)
|
132
|
+
io.read
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/lib/utopia/middleware.rb
CHANGED
@@ -18,34 +18,19 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
require 'pathname'
|
22
21
|
require 'logger'
|
23
22
|
|
24
|
-
|
23
|
+
require_relative 'http'
|
24
|
+
require_relative 'path'
|
25
25
|
|
26
|
-
|
26
|
+
require_relative 'extensions/rack'
|
27
27
|
|
28
28
|
module Utopia
|
29
29
|
LOG = Logger.new($stderr)
|
30
|
-
LOG.level = Logger::DEBUG
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
def self.failure(status = 500, message = "Non-specific error")
|
38
|
-
body = "#{HTTP::STATUS_DESCRIPTIONS[status] || status.to_s}: #{message}"
|
39
|
-
|
40
|
-
return [status, {
|
41
|
-
"Content-Type" => "text/plain",
|
42
|
-
"Content-Length" => body.size.to_s,
|
43
|
-
"X-Cascade" => "pass"
|
44
|
-
}, [body]]
|
45
|
-
end
|
31
|
+
PAGES_PATH = 'pages'.freeze
|
32
|
+
|
33
|
+
def self.default_root(subdirectory = PAGES_PATH, pwd = Dir.pwd)
|
34
|
+
File.expand_path(subdirectory, pwd)
|
46
35
|
end
|
47
36
|
end
|
48
|
-
|
49
|
-
require 'utopia/path'
|
50
|
-
require 'utopia/tag'
|
51
|
-
|
data/lib/utopia/path.rb
CHANGED
@@ -19,19 +19,88 @@
|
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
21
|
module Utopia
|
22
|
+
class Basename
|
23
|
+
# A basename represents a file name with an optional extension. You can specify a specific extension to identify or specify true to select any extension after the last trailing dot.
|
24
|
+
def initialize(name, extension = false)
|
25
|
+
if extension
|
26
|
+
if extension == true
|
27
|
+
offset = name.rindex('.')
|
28
|
+
else
|
29
|
+
offset = name.rindex(extension) - 1
|
30
|
+
end
|
31
|
+
|
32
|
+
@name = name[0...offset]
|
33
|
+
@extension = name[offset+1..-1]
|
34
|
+
else
|
35
|
+
@name = name
|
36
|
+
@extension = nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def rename(name)
|
41
|
+
copy = self.dup
|
42
|
+
|
43
|
+
copy.send(:instance_variable_set, :@name, name)
|
44
|
+
|
45
|
+
return copy
|
46
|
+
end
|
47
|
+
|
48
|
+
attr :name
|
49
|
+
attr :extension
|
50
|
+
|
51
|
+
def parts
|
52
|
+
@parts ||= @name.split('.')
|
53
|
+
end
|
54
|
+
|
55
|
+
def variant
|
56
|
+
parts.last if parts.size > 1
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_str
|
60
|
+
"#{name}#{extension}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_s
|
64
|
+
to_str
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
22
68
|
class Path
|
23
69
|
SEPARATOR = "/"
|
24
70
|
|
25
71
|
include Comparable
|
26
72
|
|
27
73
|
def initialize(components = [])
|
28
|
-
|
29
|
-
@components = components.dup.freeze
|
74
|
+
@components = components
|
30
75
|
end
|
31
76
|
|
32
|
-
|
33
|
-
|
34
|
-
|
77
|
+
def freeze
|
78
|
+
@components.freeze
|
79
|
+
|
80
|
+
super
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns the length of the prefix which is shared by two strings.
|
84
|
+
def self.prefix_length(a, b)
|
85
|
+
[a.size, b.size].min.times{|i| return i if a[i] != b[i]}
|
86
|
+
end
|
87
|
+
|
88
|
+
# Return the shortest relative path to get to path from root:
|
89
|
+
def self.shortest_path(path, root)
|
90
|
+
path = self.create(path)
|
91
|
+
root = self.create(root).dirname
|
92
|
+
|
93
|
+
# Find the common prefix:
|
94
|
+
i = prefix_length(path.components, root.components) || 0
|
95
|
+
|
96
|
+
# The difference between the root path and the required path, taking into account the common prefix:
|
97
|
+
up = root.components.size - i
|
98
|
+
|
99
|
+
return self.create([".."] * up + path.components[i..-1])
|
100
|
+
end
|
101
|
+
|
102
|
+
def shortest_path(root)
|
103
|
+
self.class.shortest_path(self, root)
|
35
104
|
end
|
36
105
|
|
37
106
|
def self.unescape(string)
|
@@ -40,22 +109,20 @@ module Utopia
|
|
40
109
|
}
|
41
110
|
end
|
42
111
|
|
112
|
+
def self.[] path
|
113
|
+
self.create(path)
|
114
|
+
end
|
115
|
+
|
43
116
|
def self.create(path)
|
44
117
|
case path
|
45
118
|
when Path
|
46
119
|
return path
|
47
120
|
when Array
|
48
|
-
return
|
121
|
+
return self.new(path)
|
49
122
|
when String
|
50
|
-
|
51
|
-
# Ends with SEPARATOR
|
52
|
-
if path[-1,1] == SEPARATOR
|
53
|
-
return Path.new(path.split(SEPARATOR) << "")
|
54
|
-
else
|
55
|
-
return Path.new(path.split(SEPARATOR))
|
56
|
-
end
|
123
|
+
return self.new(unescape(path).split(SEPARATOR, -1))
|
57
124
|
when Symbol
|
58
|
-
return
|
125
|
+
return self.new([path])
|
59
126
|
end
|
60
127
|
end
|
61
128
|
|
@@ -81,7 +148,7 @@ module Utopia
|
|
81
148
|
if absolute?
|
82
149
|
return self
|
83
150
|
else
|
84
|
-
return
|
151
|
+
return self.class.new([""] + @components)
|
85
152
|
end
|
86
153
|
end
|
87
154
|
|
@@ -97,12 +164,12 @@ module Utopia
|
|
97
164
|
to_str
|
98
165
|
end
|
99
166
|
|
100
|
-
def
|
101
|
-
@components
|
167
|
+
def join(other)
|
168
|
+
self.class.new(@components + other).simplify
|
102
169
|
end
|
103
170
|
|
104
|
-
def
|
105
|
-
|
171
|
+
def expand(root)
|
172
|
+
root + self
|
106
173
|
end
|
107
174
|
|
108
175
|
def +(other)
|
@@ -131,13 +198,13 @@ module Utopia
|
|
131
198
|
i += 1
|
132
199
|
end
|
133
200
|
|
134
|
-
return
|
201
|
+
return self.class.new(@components[i,@components.size])
|
135
202
|
end
|
136
203
|
|
137
204
|
def simplify
|
138
205
|
result = absolute? ? [""] : []
|
139
206
|
|
140
|
-
components.each do |bit|
|
207
|
+
@components.each do |bit|
|
141
208
|
if bit == ".."
|
142
209
|
result.pop
|
143
210
|
elsif bit != "." && bit != ""
|
@@ -146,35 +213,22 @@ module Utopia
|
|
146
213
|
end
|
147
214
|
|
148
215
|
result << "" if directory?
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
def basename(ext = nil)
|
153
|
-
if ext == true
|
154
|
-
File.basename(components.last, extension)
|
155
|
-
elsif String === ext
|
156
|
-
File.basename(components.last, ext)
|
157
|
-
else
|
158
|
-
components.last
|
159
|
-
end
|
216
|
+
|
217
|
+
return self.class.new(result)
|
160
218
|
end
|
161
219
|
|
162
|
-
def
|
163
|
-
|
164
|
-
components.last.split(".").last
|
165
|
-
else
|
166
|
-
nil
|
167
|
-
end
|
220
|
+
def basename(*args)
|
221
|
+
Basename.new(@components.last, *args)
|
168
222
|
end
|
169
|
-
|
223
|
+
|
170
224
|
def dirname(count = 1)
|
171
|
-
path =
|
225
|
+
path = self.class.new(@components[0...-count])
|
172
226
|
|
173
227
|
return absolute? ? path.to_absolute : path
|
174
228
|
end
|
175
229
|
|
176
|
-
def to_local_path
|
177
|
-
components.join(
|
230
|
+
def to_local_path(separator = File::SEPARATOR)
|
231
|
+
@components.join(separator)
|
178
232
|
end
|
179
233
|
|
180
234
|
def descend(&block)
|
@@ -182,10 +236,10 @@ module Utopia
|
|
182
236
|
|
183
237
|
parent_path = []
|
184
238
|
|
185
|
-
components.each do |component|
|
239
|
+
@components.each do |component|
|
186
240
|
parent_path << component
|
187
241
|
|
188
|
-
yield self.class.new(parent_path)
|
242
|
+
yield self.class.new(parent_path.dup)
|
189
243
|
end
|
190
244
|
end
|
191
245
|
|
@@ -209,7 +263,7 @@ module Utopia
|
|
209
263
|
end
|
210
264
|
|
211
265
|
if at
|
212
|
-
return [
|
266
|
+
return [self.class.new(@components[0...at]), self.class.new(@components[at+1..-1])]
|
213
267
|
else
|
214
268
|
return nil
|
215
269
|
end
|
@@ -231,38 +285,74 @@ module Utopia
|
|
231
285
|
end
|
232
286
|
end
|
233
287
|
|
234
|
-
def
|
288
|
+
def start_with? other
|
235
289
|
other.components.each_with_index do |part, index|
|
236
290
|
return false if @components[index] != part
|
237
291
|
end
|
238
292
|
|
239
293
|
return true
|
240
294
|
end
|
241
|
-
|
295
|
+
|
242
296
|
def hash
|
243
297
|
@components.hash
|
244
298
|
end
|
245
|
-
|
299
|
+
|
300
|
+
def [] index
|
301
|
+
return @components[component_offset(index)]
|
302
|
+
end
|
303
|
+
|
304
|
+
# Replaces a named component, indexing as per
|
305
|
+
def []= index, value
|
306
|
+
return @components[component_offset(index)] = value
|
307
|
+
end
|
308
|
+
|
309
|
+
def delete_at(index)
|
310
|
+
@components.delete_at(component_offset(index))
|
311
|
+
end
|
312
|
+
|
313
|
+
def first
|
314
|
+
if absolute?
|
315
|
+
@components[1]
|
316
|
+
else
|
317
|
+
@components[0]
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
246
321
|
def last
|
247
322
|
if directory?
|
248
|
-
components[-2]
|
323
|
+
@components[-2]
|
249
324
|
else
|
250
|
-
components[-1]
|
325
|
+
@components[-1]
|
251
326
|
end
|
252
327
|
end
|
253
|
-
|
254
|
-
def
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
328
|
+
|
329
|
+
def extension
|
330
|
+
basename(true).extension
|
331
|
+
end
|
332
|
+
|
333
|
+
private
|
334
|
+
|
335
|
+
# We adjust the index slightly so that indices reference path components rather than the directory markers at the start and end of the path components array.
|
336
|
+
def component_offset(index)
|
337
|
+
if Range === index
|
338
|
+
Range.new(adjust_index(index.first), adjust_index(index.last), index.exclude_end?)
|
339
|
+
else
|
340
|
+
adjust_index(index)
|
259
341
|
end
|
260
|
-
|
261
|
-
name.split(".")[1..-1].join(".")
|
262
342
|
end
|
263
343
|
|
264
|
-
def
|
265
|
-
|
344
|
+
def adjust_index(index)
|
345
|
+
if index < 0
|
346
|
+
index -= 1 if directory?
|
347
|
+
else
|
348
|
+
index += 1 if absolute?
|
349
|
+
end
|
350
|
+
|
351
|
+
return index
|
266
352
|
end
|
267
353
|
end
|
354
|
+
|
355
|
+
def Path(path)
|
356
|
+
Path.create(path)
|
357
|
+
end
|
268
358
|
end
|