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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +2 -2
  3. data/NEWS +8 -0
  4. data/README.md +70 -0
  5. data/Rakefile +19 -0
  6. data/lib/wright/config.rb +64 -0
  7. data/lib/wright/dry_run.rb +33 -0
  8. data/lib/wright/dsl.rb +63 -0
  9. data/lib/wright/logger.rb +73 -0
  10. data/lib/wright/provider/directory.rb +78 -0
  11. data/lib/wright/provider/file.rb +104 -0
  12. data/lib/wright/provider/package/apt.rb +93 -0
  13. data/lib/wright/provider/package.rb +38 -0
  14. data/lib/wright/provider/symlink.rb +86 -0
  15. data/lib/wright/provider.rb +30 -0
  16. data/lib/wright/resource/directory.rb +67 -0
  17. data/lib/wright/resource/file.rb +67 -0
  18. data/lib/wright/resource/package.rb +70 -0
  19. data/lib/wright/resource/symlink.rb +47 -0
  20. data/lib/wright/resource.rb +157 -0
  21. data/lib/wright/util/color.rb +51 -0
  22. data/lib/wright/util/file.rb +258 -0
  23. data/lib/wright/util/file_permissions.rb +129 -0
  24. data/lib/wright/util/recursive_autoloader.rb +91 -0
  25. data/lib/wright/util/stolen_from_activesupport.rb +202 -0
  26. data/lib/wright/util/user.rb +75 -0
  27. data/lib/wright/util.rb +72 -0
  28. data/lib/wright/version.rb +3 -2
  29. data/lib/wright.rb +10 -1
  30. data/spec/config_spec.rb +37 -0
  31. data/spec/dsl_spec.rb +65 -0
  32. data/spec/logger_spec.rb +65 -0
  33. data/spec/provider/directory_spec.rb +114 -0
  34. data/spec/provider/file_spec.rb +130 -0
  35. data/spec/provider/package/apt/apt-get_install_-qy_abcde=2.5.4-1.return +1 -0
  36. data/spec/provider/package/apt/apt-get_install_-qy_abcde=2.5.4-1.stderr +0 -0
  37. data/spec/provider/package/apt/apt-get_install_-qy_abcde=2.5.4-1.stdout +15 -0
  38. data/spec/provider/package/apt/apt-get_install_-qy_htop.return +1 -0
  39. data/spec/provider/package/apt/apt-get_install_-qy_htop.stderr +0 -0
  40. data/spec/provider/package/apt/apt-get_install_-qy_htop.stdout +19 -0
  41. data/spec/provider/package/apt/apt-get_install_-qy_unknown-package.return +1 -0
  42. data/spec/provider/package/apt/apt-get_install_-qy_unknown-package.stderr +1 -0
  43. data/spec/provider/package/apt/apt-get_install_-qy_unknown-package.stdout +3 -0
  44. data/spec/provider/package/apt/apt-get_remove_-qy_abcde.return +1 -0
  45. data/spec/provider/package/apt/apt-get_remove_-qy_abcde.stderr +0 -0
  46. data/spec/provider/package/apt/apt-get_remove_-qy_abcde.stdout +14 -0
  47. data/spec/provider/package/apt/dpkg-query_-s_abcde.return +1 -0
  48. data/spec/provider/package/apt/dpkg-query_-s_abcde.stderr +0 -0
  49. data/spec/provider/package/apt/dpkg-query_-s_abcde.stdout +23 -0
  50. data/spec/provider/package/apt/dpkg-query_-s_htop.return +1 -0
  51. data/spec/provider/package/apt/dpkg-query_-s_htop.stderr +0 -0
  52. data/spec/provider/package/apt/dpkg-query_-s_htop.stdout +19 -0
  53. data/spec/provider/package/apt/dpkg-query_-s_unknown-package.return +1 -0
  54. data/spec/provider/package/apt/dpkg-query_-s_unknown-package.stderr +3 -0
  55. data/spec/provider/package/apt/dpkg-query_-s_unknown-package.stdout +0 -0
  56. data/spec/provider/package/apt/dpkg-query_-s_vlc.return +1 -0
  57. data/spec/provider/package/apt/dpkg-query_-s_vlc.stderr +3 -0
  58. data/spec/provider/package/apt/dpkg-query_-s_vlc.stdout +0 -0
  59. data/spec/provider/package/apt_spec.rb +297 -0
  60. data/spec/provider/package_spec.rb +63 -0
  61. data/spec/provider/symlink_spec.rb +122 -0
  62. data/spec/provider_spec.rb +19 -0
  63. data/spec/recursive_autoloader/foo/bar/baz.rb +12 -0
  64. data/spec/recursive_autoloader/identically_named_dir_and_file/this_should_not_be_loaded.rb +1 -0
  65. data/spec/recursive_autoloader/identically_named_dir_and_file.rb +8 -0
  66. data/spec/recursive_autoloader/loaded_on_demand.rb +8 -0
  67. data/spec/recursive_autoloader/raises_exception.rb +1 -0
  68. data/spec/recursive_autoloader_spec.rb +41 -0
  69. data/spec/resource/directory_spec.rb +187 -0
  70. data/spec/resource/file_spec.rb +213 -0
  71. data/spec/resource/symlink_spec.rb +117 -0
  72. data/spec/resource_spec.rb +184 -0
  73. data/spec/spec_helper.rb +35 -0
  74. data/spec/spec_helpers/fake_capture3.rb +47 -0
  75. data/spec/util/activesupport_spec.rb +24 -0
  76. data/spec/util/file_permissions_spec.rb +141 -0
  77. data/spec/util/file_spec.rb +127 -0
  78. data/spec/util/user_spec.rb +46 -0
  79. data/spec/util_spec.rb +80 -0
  80. metadata +253 -20
  81. data/.gitignore +0 -17
  82. data/Gemfile +0 -3
  83. 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