utopia 0.12.6 → 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.
- 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
|