wright 0.0.1 → 0.1.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 +7 -0
- data/LICENSE +2 -2
- data/NEWS +8 -0
- data/README.md +70 -0
- data/Rakefile +19 -0
- data/lib/wright/config.rb +64 -0
- data/lib/wright/dry_run.rb +33 -0
- data/lib/wright/dsl.rb +63 -0
- data/lib/wright/logger.rb +73 -0
- data/lib/wright/provider/directory.rb +78 -0
- data/lib/wright/provider/file.rb +104 -0
- data/lib/wright/provider/package/apt.rb +93 -0
- data/lib/wright/provider/package.rb +38 -0
- data/lib/wright/provider/symlink.rb +86 -0
- data/lib/wright/provider.rb +30 -0
- data/lib/wright/resource/directory.rb +67 -0
- data/lib/wright/resource/file.rb +67 -0
- data/lib/wright/resource/package.rb +70 -0
- data/lib/wright/resource/symlink.rb +47 -0
- data/lib/wright/resource.rb +157 -0
- data/lib/wright/util/color.rb +51 -0
- data/lib/wright/util/file.rb +258 -0
- data/lib/wright/util/file_permissions.rb +129 -0
- data/lib/wright/util/recursive_autoloader.rb +91 -0
- data/lib/wright/util/stolen_from_activesupport.rb +202 -0
- data/lib/wright/util/user.rb +75 -0
- data/lib/wright/util.rb +72 -0
- data/lib/wright/version.rb +3 -2
- data/lib/wright.rb +10 -1
- data/spec/config_spec.rb +37 -0
- data/spec/dsl_spec.rb +65 -0
- data/spec/logger_spec.rb +65 -0
- data/spec/provider/directory_spec.rb +114 -0
- data/spec/provider/file_spec.rb +130 -0
- data/spec/provider/package/apt/apt-get_install_-qy_abcde=2.5.4-1.return +1 -0
- data/spec/provider/package/apt/apt-get_install_-qy_abcde=2.5.4-1.stderr +0 -0
- data/spec/provider/package/apt/apt-get_install_-qy_abcde=2.5.4-1.stdout +15 -0
- data/spec/provider/package/apt/apt-get_install_-qy_htop.return +1 -0
- data/spec/provider/package/apt/apt-get_install_-qy_htop.stderr +0 -0
- data/spec/provider/package/apt/apt-get_install_-qy_htop.stdout +19 -0
- data/spec/provider/package/apt/apt-get_install_-qy_unknown-package.return +1 -0
- data/spec/provider/package/apt/apt-get_install_-qy_unknown-package.stderr +1 -0
- data/spec/provider/package/apt/apt-get_install_-qy_unknown-package.stdout +3 -0
- data/spec/provider/package/apt/apt-get_remove_-qy_abcde.return +1 -0
- data/spec/provider/package/apt/apt-get_remove_-qy_abcde.stderr +0 -0
- data/spec/provider/package/apt/apt-get_remove_-qy_abcde.stdout +14 -0
- data/spec/provider/package/apt/dpkg-query_-s_abcde.return +1 -0
- data/spec/provider/package/apt/dpkg-query_-s_abcde.stderr +0 -0
- data/spec/provider/package/apt/dpkg-query_-s_abcde.stdout +23 -0
- data/spec/provider/package/apt/dpkg-query_-s_htop.return +1 -0
- data/spec/provider/package/apt/dpkg-query_-s_htop.stderr +0 -0
- data/spec/provider/package/apt/dpkg-query_-s_htop.stdout +19 -0
- data/spec/provider/package/apt/dpkg-query_-s_unknown-package.return +1 -0
- data/spec/provider/package/apt/dpkg-query_-s_unknown-package.stderr +3 -0
- data/spec/provider/package/apt/dpkg-query_-s_unknown-package.stdout +0 -0
- data/spec/provider/package/apt/dpkg-query_-s_vlc.return +1 -0
- data/spec/provider/package/apt/dpkg-query_-s_vlc.stderr +3 -0
- data/spec/provider/package/apt/dpkg-query_-s_vlc.stdout +0 -0
- data/spec/provider/package/apt_spec.rb +297 -0
- data/spec/provider/package_spec.rb +63 -0
- data/spec/provider/symlink_spec.rb +122 -0
- data/spec/provider_spec.rb +19 -0
- data/spec/recursive_autoloader/foo/bar/baz.rb +12 -0
- data/spec/recursive_autoloader/identically_named_dir_and_file/this_should_not_be_loaded.rb +1 -0
- data/spec/recursive_autoloader/identically_named_dir_and_file.rb +8 -0
- data/spec/recursive_autoloader/loaded_on_demand.rb +8 -0
- data/spec/recursive_autoloader/raises_exception.rb +1 -0
- data/spec/recursive_autoloader_spec.rb +41 -0
- data/spec/resource/directory_spec.rb +187 -0
- data/spec/resource/file_spec.rb +213 -0
- data/spec/resource/symlink_spec.rb +117 -0
- data/spec/resource_spec.rb +184 -0
- data/spec/spec_helper.rb +35 -0
- data/spec/spec_helpers/fake_capture3.rb +47 -0
- data/spec/util/activesupport_spec.rb +24 -0
- data/spec/util/file_permissions_spec.rb +141 -0
- data/spec/util/file_spec.rb +127 -0
- data/spec/util/user_spec.rb +46 -0
- data/spec/util_spec.rb +80 -0
- metadata +253 -20
- data/.gitignore +0 -17
- data/Gemfile +0 -3
- data/wright.gemspec +0 -16
@@ -0,0 +1,258 @@
|
|
1
|
+
# The file mode conversion functions in this file are based on those
|
2
|
+
# of Ruby's FileUtils, more specifically some methods found in
|
3
|
+
# lib/fileutils.rb in MRI 2.1.1.
|
4
|
+
#
|
5
|
+
# The following is a verbatim copy of the original license:
|
6
|
+
#
|
7
|
+
# Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
|
8
|
+
#
|
9
|
+
# Redistribution and use in source and binary forms, with or without
|
10
|
+
# modification, are permitted provided that the following conditions
|
11
|
+
# are met:
|
12
|
+
# 1. Redistributions of source code must retain the above copyright
|
13
|
+
# notice, this list of conditions and the following disclaimer.
|
14
|
+
# 2. Redistributions in binary form must reproduce the above copyright
|
15
|
+
# notice, this list of conditions and the following disclaimer in the
|
16
|
+
# documentation and/or other materials provided with the distribution.
|
17
|
+
#
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
19
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
20
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
21
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
22
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
23
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
24
|
+
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
25
|
+
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
26
|
+
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
27
|
+
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
28
|
+
# SUCH DAMAGE.
|
29
|
+
|
30
|
+
module Wright #:nodoc:
|
31
|
+
module Util
|
32
|
+
# Internal: Various file methods.
|
33
|
+
module File
|
34
|
+
USER_MAP = { #:nodoc:
|
35
|
+
'u' => 04700,
|
36
|
+
'g' => 02070,
|
37
|
+
'o' => 01007,
|
38
|
+
'a' => 07777
|
39
|
+
}
|
40
|
+
private_constant :USER_MAP
|
41
|
+
|
42
|
+
def self.user_mask(target)
|
43
|
+
target.chars.reduce(0) { |a, e| a | USER_MAP[e] }
|
44
|
+
end
|
45
|
+
private_class_method :user_mask
|
46
|
+
|
47
|
+
MODE_MAP = { #:nodoc:
|
48
|
+
'r' => 0444,
|
49
|
+
'w' => 0222,
|
50
|
+
'x' => 0111,
|
51
|
+
's' => 06000,
|
52
|
+
't' => 01000
|
53
|
+
}
|
54
|
+
private_constant :MODE_MAP
|
55
|
+
|
56
|
+
def self.mode_mask(mode, is_directory)
|
57
|
+
mode.gsub!('X', 'x') if is_directory
|
58
|
+
mode.chars.reduce(0) { |a, e| a | MODE_MAP[e].to_i }
|
59
|
+
end
|
60
|
+
private_class_method :mode_mask
|
61
|
+
|
62
|
+
# Internal: Convert a symbolic mode string to an integer mode
|
63
|
+
# value.
|
64
|
+
#
|
65
|
+
# mode - The symbolic mode string.
|
66
|
+
# base_mode - The integer base mode.
|
67
|
+
# filetype - The filetype. Defaults to :file.
|
68
|
+
#
|
69
|
+
# Examples
|
70
|
+
#
|
71
|
+
# Wright::Util::File.symbolic_mode_to_i('u=rw,go=r', 0400).to_s(8)
|
72
|
+
# # => "644"
|
73
|
+
#
|
74
|
+
# Wright::Util::File.symbolic_mode_to_i('u=rw,g+r', 0200).to_s(8)
|
75
|
+
# # => "640"
|
76
|
+
#
|
77
|
+
# Returns the integer mode.
|
78
|
+
def self.symbolic_mode_to_i(mode, base_mode, filetype = :file)
|
79
|
+
is_directory = (filetype == :directory)
|
80
|
+
unless symbolic_mode?(mode)
|
81
|
+
fail ArgumentError, "Invalid file mode \"#{mode}\""
|
82
|
+
end
|
83
|
+
mode_i = base_mode
|
84
|
+
mode.split(/,/).each do |mode_clause|
|
85
|
+
mode_i = mode_clause_to_i(mode_clause, mode_i, is_directory)
|
86
|
+
end
|
87
|
+
mode_i
|
88
|
+
end
|
89
|
+
|
90
|
+
# Internal: Convert a single symbolic mode clause to an integer
|
91
|
+
# mode value.
|
92
|
+
#
|
93
|
+
# mode_clause - The symbolic mode clause.
|
94
|
+
# base_mode_i - The integer base mode.
|
95
|
+
# is_directory - Denotes whether the mode_clause should be
|
96
|
+
# treated as a symbolic directory mode clause.
|
97
|
+
#
|
98
|
+
# Examples
|
99
|
+
#
|
100
|
+
# Wright::Util::File.mode_clause_to_i('g+r', 0600, false).to_s(8)
|
101
|
+
# # => "640"
|
102
|
+
#
|
103
|
+
# Wright::Util::File.mode_clause_to_i('+rw', 0600, false).to_s(8)
|
104
|
+
# # => "666"
|
105
|
+
#
|
106
|
+
# Returns the mode clause as an integer.
|
107
|
+
def self.mode_clause_to_i(mode_clause, base_mode_i, is_directory)
|
108
|
+
mode_clause = "a#{mode_clause}" if mode_clause =~ /\A[+-=]/
|
109
|
+
who, op, perm = mode_clause.split(/([+-=])/)
|
110
|
+
perm ||= ''
|
111
|
+
user_mask = user_mask(who)
|
112
|
+
mode_mask = mode_mask(perm, is_directory)
|
113
|
+
apply_user_mode_masks(base_mode_i, user_mask, op, mode_mask)
|
114
|
+
end
|
115
|
+
private_class_method :mode_clause_to_i
|
116
|
+
|
117
|
+
def self.apply_user_mode_masks(base_mode_i, user_mask, op, mode_mask)
|
118
|
+
case op
|
119
|
+
when '='
|
120
|
+
(base_mode_i & ~user_mask) | (user_mask & mode_mask)
|
121
|
+
when '+'
|
122
|
+
base_mode_i | (user_mask & mode_mask)
|
123
|
+
when '-'
|
124
|
+
base_mode_i & ~(user_mask & mode_mask)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
private_class_method :apply_user_mode_masks
|
128
|
+
|
129
|
+
# Internal: Convert a numeric mode string to an integer mode.
|
130
|
+
#
|
131
|
+
# mode - The numeric mode string.
|
132
|
+
#
|
133
|
+
# Examples
|
134
|
+
#
|
135
|
+
# Wright::Util::File.numeric_mode_to_i('0600').to_s(8)
|
136
|
+
# # => "600"
|
137
|
+
#
|
138
|
+
# Wright::Util::File.numeric_mode_to_i('644').to_s(8)
|
139
|
+
# # => "644"
|
140
|
+
#
|
141
|
+
# Wright::Util::File.numeric_mode_to_i(0644).to_s(8)
|
142
|
+
# # => "644"
|
143
|
+
#
|
144
|
+
# Wright::Util::File.numeric_mode_to_i('invalid_mode').to_s(8)
|
145
|
+
# # => nil
|
146
|
+
#
|
147
|
+
# Returns the mode in integer form or nil if the mode could not
|
148
|
+
# be converted.
|
149
|
+
def self.numeric_mode_to_i(mode)
|
150
|
+
return mode.to_i unless mode.is_a?(String)
|
151
|
+
mode =~ /\A[0-7]{3,4}\Z/ ? mode.to_i(8) : nil
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.symbolic_mode?(mode_str)
|
155
|
+
return true if mode_str.empty?
|
156
|
+
mode_fragment = /([augo]*[+-=][rwxXst]*)/
|
157
|
+
mode_re = /\A#{mode_fragment}(,#{mode_fragment})*\Z/
|
158
|
+
!(mode_str =~ mode_re).nil?
|
159
|
+
end
|
160
|
+
private_class_method :symbolic_mode?
|
161
|
+
|
162
|
+
# Internal: Get a file's current mode.
|
163
|
+
#
|
164
|
+
# path - The file's path.
|
165
|
+
#
|
166
|
+
# Examples
|
167
|
+
#
|
168
|
+
# FileUtils.touch('foo')
|
169
|
+
# FileUtils.chmod(0644, 'foo')
|
170
|
+
# Wright::Util::File.file_mode('foo').to_s(8)
|
171
|
+
# # => "644"
|
172
|
+
#
|
173
|
+
# Returns the file mode as an integer or nil if the file does
|
174
|
+
# not exist.
|
175
|
+
def self.file_mode(path)
|
176
|
+
::File.exist?(path) ? (::File.stat(path).mode & 07777) : nil
|
177
|
+
end
|
178
|
+
|
179
|
+
# Internal: Get a file's owner.
|
180
|
+
#
|
181
|
+
# path - The file's path.
|
182
|
+
#
|
183
|
+
# Examples
|
184
|
+
#
|
185
|
+
# FileUtils.touch('foo')
|
186
|
+
# FileUtils.chown(0, 0, 'foo')
|
187
|
+
# Wright::Util::File.file_owner('foo')
|
188
|
+
# # => 0
|
189
|
+
#
|
190
|
+
# Wright::Util::File.file_owner('nonexistent')
|
191
|
+
# # => nil
|
192
|
+
#
|
193
|
+
# Returns the file owner's uid or nil if the file does not
|
194
|
+
# exist.
|
195
|
+
def self.file_owner(path)
|
196
|
+
::File.exist?(path) ? ::File.stat(path).uid : nil
|
197
|
+
end
|
198
|
+
|
199
|
+
# Internal: Get a file's owner.
|
200
|
+
#
|
201
|
+
# path - The file's path.
|
202
|
+
#
|
203
|
+
# Examples
|
204
|
+
#
|
205
|
+
# FileUtils.touch('foo')
|
206
|
+
# FileUtils.chown(0, 0, 'foo')
|
207
|
+
# Wright::Util::File.file_group('foo')
|
208
|
+
# # => 0
|
209
|
+
#
|
210
|
+
# Wright::Util::File.file_group('nonexistent')
|
211
|
+
# # => nil
|
212
|
+
#
|
213
|
+
# Returns the file owner's uid or nil if the file does not
|
214
|
+
# exist.
|
215
|
+
def self.file_group(path)
|
216
|
+
::File.exist?(path) ? ::File.stat(path).gid : nil
|
217
|
+
end
|
218
|
+
|
219
|
+
# Internal: Expand tilde symbols in file paths. Path elements
|
220
|
+
# other than the first one are left alone.
|
221
|
+
#
|
222
|
+
# path - The file path.
|
223
|
+
#
|
224
|
+
# Examples
|
225
|
+
#
|
226
|
+
# Wright::Util::File.expand_tilde_path('~root/foo')
|
227
|
+
# # => "/root/foo"
|
228
|
+
#
|
229
|
+
# Wright::Util::File.expand_tilde_path('~root/foo/..')
|
230
|
+
# # => "/root/foo/.."
|
231
|
+
#
|
232
|
+
# Wright::Util::File.expand_tilde_path('../foo/bar')
|
233
|
+
# # => "../foo/bar"
|
234
|
+
#
|
235
|
+
# Returns the expanded String path.
|
236
|
+
def self.expand_tilde_path(path)
|
237
|
+
return path unless path.start_with?('~')
|
238
|
+
|
239
|
+
first, *rest = path.split(::File::SEPARATOR)
|
240
|
+
::File.join(::File.expand_path(first), rest)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Internal: Creates a link named link_name to target.
|
244
|
+
#
|
245
|
+
# If the file denoted by link_name is a symlink to a directory,
|
246
|
+
# ln_sfn does not descend into it. Behaves similar to GNU ln(1) or
|
247
|
+
# OpenBSD ln(1) when using "ln -sfn to link_name".
|
248
|
+
#
|
249
|
+
# Returns nothing.
|
250
|
+
def self.ln_sfn(target, link_name)
|
251
|
+
if ::File.symlink?(link_name) && ::File.directory?(link_name)
|
252
|
+
FileUtils.rm(link_name)
|
253
|
+
end
|
254
|
+
FileUtils.ln_sf(target, link_name)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'wright/util/file'
|
2
|
+
require 'wright/util/user'
|
3
|
+
|
4
|
+
module Wright
|
5
|
+
module Util
|
6
|
+
# Internal: Helper class to manage file permissions.
|
7
|
+
class FilePermissions
|
8
|
+
# Internal: Create a FilePermissions from a Wright::Resource.
|
9
|
+
#
|
10
|
+
# resource - The resource object.
|
11
|
+
# filetype - The file's type, typically :file or :directory.
|
12
|
+
#
|
13
|
+
# Returns a Wright::Util::FilePermissions object.
|
14
|
+
def self.create_from_resource(resource, filetype)
|
15
|
+
filepath = ::File.expand_path(resource.name)
|
16
|
+
p = Wright::Util::FilePermissions.new(filepath, filetype)
|
17
|
+
p.owner = resource.owner
|
18
|
+
p.group = resource.group
|
19
|
+
p.mode = resource.mode
|
20
|
+
p
|
21
|
+
end
|
22
|
+
|
23
|
+
# Internal: Get/Set the target file's name.
|
24
|
+
attr_accessor :filename
|
25
|
+
|
26
|
+
# Internal: Get/Set the file's target group.
|
27
|
+
attr_accessor :group
|
28
|
+
|
29
|
+
# Internal: Get/Set the file's target mode.
|
30
|
+
attr_accessor :mode
|
31
|
+
|
32
|
+
# Internal: Get the file's target owner.
|
33
|
+
attr_reader :owner
|
34
|
+
|
35
|
+
# Internal: Supported filetypes.
|
36
|
+
VALID_FILETYPES = [:file, :directory]
|
37
|
+
|
38
|
+
# Internal: Initialize a FilePermissions object.
|
39
|
+
#
|
40
|
+
# filename - The file's name.
|
41
|
+
# filetype - The file's type, typically :file or :directory.
|
42
|
+
def initialize(filename, filetype)
|
43
|
+
unless VALID_FILETYPES.include?(filetype)
|
44
|
+
fail ArgumentError, "Invalid filetype '#{filetype}'"
|
45
|
+
end
|
46
|
+
@filename = filename
|
47
|
+
@filetype = filetype
|
48
|
+
end
|
49
|
+
|
50
|
+
# Internal: Set the file's owner.
|
51
|
+
def owner=(owner)
|
52
|
+
@owner = Util::User.user_to_uid(owner)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Internal: Set the file's group
|
56
|
+
def group=(group)
|
57
|
+
@group = Util::User.group_to_gid(group)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Internal: Set the file's mode.
|
61
|
+
def mode=(mode)
|
62
|
+
if mode.nil?
|
63
|
+
@mode = nil
|
64
|
+
return
|
65
|
+
end
|
66
|
+
|
67
|
+
mode_i = File.numeric_mode_to_i(mode)
|
68
|
+
unless mode_i
|
69
|
+
base_mode_i = ::File.exist?(@filename) ? current_mode : default_mode
|
70
|
+
mode_i = File.symbolic_mode_to_i(mode, base_mode_i, @filetype)
|
71
|
+
end
|
72
|
+
@mode = mode_i
|
73
|
+
end
|
74
|
+
|
75
|
+
# Internal: Check if the file's owner, group and mode are up-to-date.
|
76
|
+
def uptodate?
|
77
|
+
if ::File.exist?(@filename)
|
78
|
+
owner_uptodate? && group_uptodate? && mode_uptodate?
|
79
|
+
else
|
80
|
+
false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Internal: Update the file's owner, group and mode.
|
85
|
+
def update
|
86
|
+
::File.chmod(@mode, @filename) if @mode
|
87
|
+
::File.chown(@owner, @group, @filename) if @owner || @group
|
88
|
+
end
|
89
|
+
|
90
|
+
# Internal: Get the file's current mode.
|
91
|
+
def current_mode
|
92
|
+
Wright::Util::File.file_mode(@filename)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Internal: Get the file's current owner.
|
96
|
+
def current_owner
|
97
|
+
Wright::Util::File.file_owner(@filename)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Internal: Get the file's current group.
|
101
|
+
def current_group
|
102
|
+
Wright::Util::File.file_group(@filename)
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def owner_uptodate?
|
108
|
+
@owner.nil? || current_owner == @owner
|
109
|
+
end
|
110
|
+
|
111
|
+
def group_uptodate?
|
112
|
+
@group.nil? || current_group == @group
|
113
|
+
end
|
114
|
+
|
115
|
+
def mode_uptodate?
|
116
|
+
@mode.nil? || current_mode == @mode
|
117
|
+
end
|
118
|
+
|
119
|
+
def default_mode
|
120
|
+
case @filetype
|
121
|
+
when :file
|
122
|
+
~::File.umask & 0666
|
123
|
+
when :directory
|
124
|
+
~::File.umask & 0777
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'wright/util'
|
2
|
+
|
3
|
+
module Wright
|
4
|
+
module Util
|
5
|
+
# Internal: Recursive autoloader, recursively adds autoloads for
|
6
|
+
# all files in a directory.
|
7
|
+
module RecursiveAutoloader
|
8
|
+
# Internal: Adds autoloads for all files in a directory to a
|
9
|
+
# parent class.
|
10
|
+
#
|
11
|
+
# Registers all files in directory to be autoloaded the
|
12
|
+
# first time ParentClass::CamelCased::FileName is accessed.
|
13
|
+
#
|
14
|
+
# directory - The path of the directory containing the files to
|
15
|
+
# be autoloaded.
|
16
|
+
#
|
17
|
+
# parent_class - The parent class to add the autoloads to.
|
18
|
+
#
|
19
|
+
# Examples
|
20
|
+
#
|
21
|
+
# require 'fileutils'
|
22
|
+
#
|
23
|
+
# # set up a test directory
|
24
|
+
# FileUtils.cd '/tmp'
|
25
|
+
# FileUtils.mkdir_p 'somedir/foo'
|
26
|
+
# File.write('somedir/foo/bar_baz.rb', 'class Root::Foo::BarBaz; end')
|
27
|
+
#
|
28
|
+
# # define a root class, add an autoload
|
29
|
+
# class Root; end
|
30
|
+
# Wright::Util::RecursiveAutoloader.add_autoloads('somedir', 'Root')
|
31
|
+
#
|
32
|
+
# # Root::Foo::BarBaz is going to be autoloaded
|
33
|
+
# Root::Foo.autoload? 'BarBaz'
|
34
|
+
# # => "/tmp/somedir/foo/bar_baz.rb"
|
35
|
+
#
|
36
|
+
# # instantiate Root::Foo::BarBaz, somedir/foo/bar_baz.rb is loaded
|
37
|
+
# foo_bar_baz = Root::Foo::BarBaz.new
|
38
|
+
# foo_bar_baz.class
|
39
|
+
# # => Root::Foo::BarBaz
|
40
|
+
#
|
41
|
+
# # at this point, somedir/foo/bar_baz.rb has already been loaded
|
42
|
+
# Root::Foo.autoload? 'BarBaz'
|
43
|
+
# # => nil
|
44
|
+
#
|
45
|
+
# Returns nothing.
|
46
|
+
# Raises ArgumentError if the parent class cannot be resolved.
|
47
|
+
def self.add_autoloads(directory, parent_class)
|
48
|
+
unless class_exists?(parent_class)
|
49
|
+
fail ArgumentError, "Can't resolve parent_class #{parent_class}"
|
50
|
+
end
|
51
|
+
add_autoloads_unsafe(directory, parent_class)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.add_autoloads_for_current_dir(parent_class)
|
55
|
+
klass = Wright::Util::ActiveSupport.constantize(parent_class)
|
56
|
+
Dir['*.rb'].each do |filename|
|
57
|
+
classname = "#{Wright::Util.filename_to_classname(filename)}"
|
58
|
+
klass.autoload classname, ::File.expand_path(filename)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
private_class_method :add_autoloads_for_current_dir
|
62
|
+
|
63
|
+
def self.ensure_subclass_exists(classname, subclass)
|
64
|
+
complete_classname = "#{classname}::#{subclass}"
|
65
|
+
return true if class_exists?(complete_classname)
|
66
|
+
klass = Wright::Util::ActiveSupport.constantize(classname)
|
67
|
+
klass.const_set(subclass, Class.new)
|
68
|
+
end
|
69
|
+
private_class_method :ensure_subclass_exists
|
70
|
+
|
71
|
+
def self.class_exists?(classname)
|
72
|
+
!Wright::Util::ActiveSupport.safe_constantize(classname).nil?
|
73
|
+
end
|
74
|
+
private_class_method :class_exists?
|
75
|
+
|
76
|
+
def self.add_autoloads_unsafe(directory, parent_class)
|
77
|
+
Dir.chdir(directory) do
|
78
|
+
add_autoloads_for_current_dir(parent_class)
|
79
|
+
Dir['*/'].each do |dir|
|
80
|
+
subclass = Wright::Util.filename_to_classname(dir)
|
81
|
+
ensure_subclass_exists(parent_class, subclass)
|
82
|
+
new_parent = Wright::Util.filename_to_classname(
|
83
|
+
::File.join(parent_class, dir))
|
84
|
+
add_autoloads_unsafe(dir, new_parent)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
private_class_method :add_autoloads_unsafe
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# The functions in this file are based on Ruby on Rails' ActiveSupport
|
2
|
+
# toolkit, more specifically the Inflector module of Rails 4 found in
|
3
|
+
# activesupport/lib/active_support/inflector/methods.rb.
|
4
|
+
#
|
5
|
+
# The following is a verbatim copy of the original license:
|
6
|
+
#
|
7
|
+
# Copyright (c) 2005-2014 David Heinemeier Hansson
|
8
|
+
#
|
9
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
10
|
+
# a copy of this software and associated documentation files (the
|
11
|
+
# "Software"), to deal in the Software without restriction, including
|
12
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
13
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
14
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
15
|
+
# the following conditions:
|
16
|
+
#
|
17
|
+
# The above copyright notice and this permission notice shall be
|
18
|
+
# included in all copies or substantial portions of the Software.
|
19
|
+
#
|
20
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
21
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
22
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
23
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
24
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
25
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
27
|
+
|
28
|
+
module Wright #:nodoc:
|
29
|
+
module Util
|
30
|
+
# Internal: Various methods copied verbatim from ActiveSupport in
|
31
|
+
# order to keep dependencies to a minimum.
|
32
|
+
module ActiveSupport
|
33
|
+
# Internal: Convert a CamelCased String to underscored,
|
34
|
+
# lowercase form.
|
35
|
+
#
|
36
|
+
# Changes '::' to '/' to convert namespaces to paths.
|
37
|
+
#
|
38
|
+
# camel_cased_word - The String to be underscored.
|
39
|
+
#
|
40
|
+
# Examples
|
41
|
+
#
|
42
|
+
# underscore("ActiveModel")
|
43
|
+
# # => "active_model"
|
44
|
+
#
|
45
|
+
# underscore("ActiveModel::Errors")
|
46
|
+
# # => "active_model/errors"
|
47
|
+
#
|
48
|
+
# As a rule of thumb you can think of underscore as the inverse
|
49
|
+
# of camelize, though there are cases where that does not hold:
|
50
|
+
#
|
51
|
+
# camelize(underscore("SSLError"))
|
52
|
+
# # => "SslError"
|
53
|
+
#
|
54
|
+
# Returns the underscored String.
|
55
|
+
def self.underscore(camel_cased_word)
|
56
|
+
word = camel_cased_word.to_s.dup
|
57
|
+
word.gsub!(/::/, '/')
|
58
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
|
59
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
60
|
+
word.tr!('-', '_')
|
61
|
+
word.downcase!
|
62
|
+
word
|
63
|
+
end
|
64
|
+
|
65
|
+
# Internal: Convert an underscored_string to UpperCamelCase.
|
66
|
+
#
|
67
|
+
# camelize will also convert '/' to '::' which is useful for
|
68
|
+
# converting paths to namespaces.
|
69
|
+
#
|
70
|
+
# s - The String to be camelized.
|
71
|
+
#
|
72
|
+
# Examples
|
73
|
+
#
|
74
|
+
# camelize("active_record")
|
75
|
+
# # => "ActiveRecord"
|
76
|
+
#
|
77
|
+
# camelize("active_record/errors")
|
78
|
+
# # => "ActiveRecord::Errors"
|
79
|
+
#
|
80
|
+
# As a rule of thumb you can think of camelize as the inverse of
|
81
|
+
# underscore, though there are cases where that does not hold:
|
82
|
+
#
|
83
|
+
# camelize(underscore("SSLError"))
|
84
|
+
# # => "SslError"
|
85
|
+
#
|
86
|
+
# Returns the camelized String.
|
87
|
+
def self.camelize(s)
|
88
|
+
s.to_s
|
89
|
+
.gsub(/\/(.?)/) { "::#{Regexp.last_match[1].upcase}" }
|
90
|
+
.gsub(/(?:^|_)(.)/) { Regexp.last_match[1].upcase }
|
91
|
+
end
|
92
|
+
|
93
|
+
# Internal: Find a constant with the name specified in the
|
94
|
+
# argument string.
|
95
|
+
#
|
96
|
+
# camel_cased_word - The String name of the constant to find.
|
97
|
+
#
|
98
|
+
# Examples
|
99
|
+
#
|
100
|
+
# constantize("Module")
|
101
|
+
# # => Module
|
102
|
+
#
|
103
|
+
# constantize("Test::Unit")
|
104
|
+
# # => Test::Unit
|
105
|
+
#
|
106
|
+
# The name is assumed to be the one of a top-level constant, no
|
107
|
+
# matter whether it starts with "::" or not. No lexical context
|
108
|
+
# is taken into account:
|
109
|
+
#
|
110
|
+
# C = 'outside'
|
111
|
+
# module M
|
112
|
+
# C = 'inside'
|
113
|
+
# C # => 'inside'
|
114
|
+
# constantize("C") # => 'outside', same as ::C
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# Returns the constant.
|
118
|
+
# Raises NameError if the constant name is not in CamelCase or
|
119
|
+
# the constant is unknown.
|
120
|
+
def self.constantize(camel_cased_word)
|
121
|
+
names = camel_cased_word.split('::')
|
122
|
+
names.shift if names.empty? || names.first.empty?
|
123
|
+
|
124
|
+
c = Object
|
125
|
+
names.each do |name|
|
126
|
+
const_defined = c.const_defined?(name, false)
|
127
|
+
c = const_defined ? c.const_get(name) : c.const_missing(name)
|
128
|
+
end
|
129
|
+
c
|
130
|
+
end
|
131
|
+
|
132
|
+
# Internal: Find a constant with the name specified in the
|
133
|
+
# argument string.
|
134
|
+
#
|
135
|
+
# camel_cased_word - The String name of the constant to find.
|
136
|
+
#
|
137
|
+
# Examples
|
138
|
+
#
|
139
|
+
# constantize("Module")
|
140
|
+
# # => Module
|
141
|
+
#
|
142
|
+
# constantize("Test::Unit")
|
143
|
+
# # => Test::Unit
|
144
|
+
#
|
145
|
+
# The name is assumed to be the one of a top-level constant, no
|
146
|
+
# matter whether it starts with "::" or not. No lexical context
|
147
|
+
# is taken into account:
|
148
|
+
#
|
149
|
+
# C = 'outside'
|
150
|
+
# module M
|
151
|
+
# C = 'inside'
|
152
|
+
# C # => 'inside'
|
153
|
+
# constantize("C") # => 'outside', same as ::C
|
154
|
+
# end
|
155
|
+
#
|
156
|
+
# nil is returned when the name is not in CamelCase or the
|
157
|
+
# constant (or part of it) is unknown.
|
158
|
+
#
|
159
|
+
# safe_constantize("blargle")
|
160
|
+
# # => nil
|
161
|
+
#
|
162
|
+
# safe_constantize("UnknownModule")
|
163
|
+
# # => nil
|
164
|
+
#
|
165
|
+
# safe_constantize("UnknownModule::Foo::Bar")
|
166
|
+
# # => nil
|
167
|
+
#
|
168
|
+
# Returns the constant or nil if the name is not in CamelCase or
|
169
|
+
# the constant is unknown.
|
170
|
+
def self.safe_constantize(camel_cased_word)
|
171
|
+
constantize(camel_cased_word)
|
172
|
+
rescue NameError => e
|
173
|
+
error_re = /(uninitialized constant|wrong constant name)/
|
174
|
+
const_re = const_regexp(camel_cased_word)
|
175
|
+
message_re = /#{error_re} #{const_re}$/
|
176
|
+
uninitialized_constant_exception =
|
177
|
+
e.message =~ message_re || e.name.to_s == camel_cased_word.to_s
|
178
|
+
raise unless uninitialized_constant_exception
|
179
|
+
end
|
180
|
+
|
181
|
+
# Internal: Construct a regular expression that will match a
|
182
|
+
# constant part by part.
|
183
|
+
#
|
184
|
+
# camel_cased_word - The CamelCased String constant name.
|
185
|
+
#
|
186
|
+
# Examples
|
187
|
+
#
|
188
|
+
# const_regexp("Foo::Bar::Baz")
|
189
|
+
# # => "Foo(::Bar(::Baz)?)?"
|
190
|
+
#
|
191
|
+
# Returns the String to be used as a regular expression.
|
192
|
+
def self.const_regexp(camel_cased_word)
|
193
|
+
parts = camel_cased_word.split('::')
|
194
|
+
last = parts.pop
|
195
|
+
|
196
|
+
parts.reverse.reduce(last) do |acc, part|
|
197
|
+
part.empty? ? acc : "#{part}(::#{acc})?"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|