automate-it 0.9.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 (137) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.hgignore +10 -0
  4. data/.loadpath +5 -0
  5. data/.project +17 -0
  6. data/CHANGES.txt +314 -0
  7. data/Hoe.rake +40 -0
  8. data/Manifest.txt +164 -0
  9. data/README.txt +40 -0
  10. data/Rakefile +256 -0
  11. data/TESTING.txt +57 -0
  12. data/TODO.txt +50 -0
  13. data/TUTORIAL.txt +391 -0
  14. data/automate-it.gemspec +25 -0
  15. data/bin/ai +3 -0
  16. data/bin/aifield +75 -0
  17. data/bin/aissh +93 -0
  18. data/bin/aitag +134 -0
  19. data/bin/automateit +133 -0
  20. data/docs/friendly_errors.txt +50 -0
  21. data/docs/previews.txt +86 -0
  22. data/examples/basic/Rakefile +26 -0
  23. data/examples/basic/config/automateit_env.rb +16 -0
  24. data/examples/basic/config/fields.yml +3 -0
  25. data/examples/basic/config/tags.yml +7 -0
  26. data/examples/basic/dist/README.txt +9 -0
  27. data/examples/basic/dist/myapp_server.erb +30 -0
  28. data/examples/basic/install.log +15 -0
  29. data/examples/basic/lib/README.txt +10 -0
  30. data/examples/basic/recipes/README.txt +4 -0
  31. data/examples/basic/recipes/install.rb +61 -0
  32. data/examples/basic/recipes/uninstall.rb +6 -0
  33. data/gpl.txt +674 -0
  34. data/helpers/cpan_wrapper.pl +220 -0
  35. data/helpers/which.cmd +7 -0
  36. data/lib/automateit.rb +55 -0
  37. data/lib/automateit/account_manager.rb +114 -0
  38. data/lib/automateit/account_manager/base.rb +138 -0
  39. data/lib/automateit/account_manager/etc.rb +128 -0
  40. data/lib/automateit/account_manager/nscd.rb +33 -0
  41. data/lib/automateit/account_manager/passwd_expect.rb +40 -0
  42. data/lib/automateit/account_manager/passwd_pty.rb +69 -0
  43. data/lib/automateit/account_manager/posix.rb +138 -0
  44. data/lib/automateit/address_manager.rb +88 -0
  45. data/lib/automateit/address_manager/base.rb +171 -0
  46. data/lib/automateit/address_manager/bsd.rb +28 -0
  47. data/lib/automateit/address_manager/freebsd.rb +59 -0
  48. data/lib/automateit/address_manager/linux.rb +42 -0
  49. data/lib/automateit/address_manager/openbsd.rb +66 -0
  50. data/lib/automateit/address_manager/portable.rb +37 -0
  51. data/lib/automateit/address_manager/sunos.rb +34 -0
  52. data/lib/automateit/cli.rb +85 -0
  53. data/lib/automateit/common.rb +65 -0
  54. data/lib/automateit/constants.rb +35 -0
  55. data/lib/automateit/download_manager.rb +48 -0
  56. data/lib/automateit/edit_manager.rb +321 -0
  57. data/lib/automateit/error.rb +10 -0
  58. data/lib/automateit/field_manager.rb +103 -0
  59. data/lib/automateit/interpreter.rb +631 -0
  60. data/lib/automateit/package_manager.rb +257 -0
  61. data/lib/automateit/package_manager/apt.rb +27 -0
  62. data/lib/automateit/package_manager/cpan.rb +101 -0
  63. data/lib/automateit/package_manager/dpkg.rb +54 -0
  64. data/lib/automateit/package_manager/egg.rb +64 -0
  65. data/lib/automateit/package_manager/gem.rb +201 -0
  66. data/lib/automateit/package_manager/pear.rb +95 -0
  67. data/lib/automateit/package_manager/pecl.rb +80 -0
  68. data/lib/automateit/package_manager/portage.rb +69 -0
  69. data/lib/automateit/package_manager/yum.rb +65 -0
  70. data/lib/automateit/platform_manager.rb +49 -0
  71. data/lib/automateit/platform_manager/darwin.rb +30 -0
  72. data/lib/automateit/platform_manager/debian.rb +26 -0
  73. data/lib/automateit/platform_manager/freebsd.rb +29 -0
  74. data/lib/automateit/platform_manager/gentoo.rb +26 -0
  75. data/lib/automateit/platform_manager/lsb.rb +44 -0
  76. data/lib/automateit/platform_manager/openbsd.rb +28 -0
  77. data/lib/automateit/platform_manager/struct.rb +80 -0
  78. data/lib/automateit/platform_manager/sunos.rb +39 -0
  79. data/lib/automateit/platform_manager/uname.rb +29 -0
  80. data/lib/automateit/platform_manager/windows.rb +40 -0
  81. data/lib/automateit/plugin.rb +7 -0
  82. data/lib/automateit/plugin/base.rb +32 -0
  83. data/lib/automateit/plugin/driver.rb +256 -0
  84. data/lib/automateit/plugin/manager.rb +224 -0
  85. data/lib/automateit/project.rb +493 -0
  86. data/lib/automateit/root.rb +17 -0
  87. data/lib/automateit/service_manager.rb +93 -0
  88. data/lib/automateit/service_manager/chkconfig.rb +39 -0
  89. data/lib/automateit/service_manager/rc_update.rb +37 -0
  90. data/lib/automateit/service_manager/sysv.rb +139 -0
  91. data/lib/automateit/service_manager/update_rcd.rb +35 -0
  92. data/lib/automateit/shell_manager.rb +316 -0
  93. data/lib/automateit/shell_manager/base_link.rb +67 -0
  94. data/lib/automateit/shell_manager/link.rb +24 -0
  95. data/lib/automateit/shell_manager/portable.rb +523 -0
  96. data/lib/automateit/shell_manager/symlink.rb +32 -0
  97. data/lib/automateit/shell_manager/which_base.rb +30 -0
  98. data/lib/automateit/shell_manager/which_unix.rb +16 -0
  99. data/lib/automateit/shell_manager/which_windows.rb +20 -0
  100. data/lib/automateit/tag_manager.rb +127 -0
  101. data/lib/automateit/tag_manager/struct.rb +121 -0
  102. data/lib/automateit/tag_manager/tag_parser.rb +93 -0
  103. data/lib/automateit/tag_manager/yaml.rb +29 -0
  104. data/lib/automateit/template_manager.rb +56 -0
  105. data/lib/automateit/template_manager/base.rb +181 -0
  106. data/lib/automateit/template_manager/erb.rb +17 -0
  107. data/lib/ext/metaclass.rb +17 -0
  108. data/lib/ext/object.rb +18 -0
  109. data/lib/ext/shell_escape.rb +7 -0
  110. data/lib/hashcache.rb +22 -0
  111. data/lib/helpful_erb.rb +63 -0
  112. data/lib/inactive_support.rb +53 -0
  113. data/lib/inactive_support/basic_object.rb +6 -0
  114. data/lib/inactive_support/clean_logger.rb +127 -0
  115. data/lib/inactive_support/core_ext/array/extract_options.rb +19 -0
  116. data/lib/inactive_support/core_ext/blank.rb +50 -0
  117. data/lib/inactive_support/core_ext/class/attribute_accessors.rb +48 -0
  118. data/lib/inactive_support/core_ext/class/inheritable_attributes.rb +140 -0
  119. data/lib/inactive_support/core_ext/enumerable.rb +63 -0
  120. data/lib/inactive_support/core_ext/hash/keys.rb +54 -0
  121. data/lib/inactive_support/core_ext/module/aliasing.rb +70 -0
  122. data/lib/inactive_support/core_ext/numeric/time.rb +91 -0
  123. data/lib/inactive_support/core_ext/string/inflections.rb +153 -0
  124. data/lib/inactive_support/core_ext/symbol.rb +14 -0
  125. data/lib/inactive_support/core_ext/time/conversions.rb +96 -0
  126. data/lib/inactive_support/duration.rb +96 -0
  127. data/lib/inactive_support/inflections.rb +53 -0
  128. data/lib/inactive_support/inflector.rb +282 -0
  129. data/lib/nested_error.rb +33 -0
  130. data/lib/nitpick.rb +33 -0
  131. data/lib/queued_logger.rb +68 -0
  132. data/lib/tempster.rb +250 -0
  133. data/misc/index_gem_repository.rb +304 -0
  134. data/misc/setup_egg.rb +12 -0
  135. data/misc/setup_gem_dependencies.sh +6 -0
  136. data/misc/setup_rubygems.sh +21 -0
  137. metadata +279 -0
data/lib/nitpick.rb ADDED
@@ -0,0 +1,33 @@
1
+ module Nitpick
2
+ module ClassMethods
3
+ # Use to manage nitpick message for debugging AutomateIt internals.
4
+ #
5
+ # Arguments:
6
+ # * nil -- Returns boolean of whether nitpick messages will be displayed.
7
+ # * Boolean -- Sets nitpick state.
8
+ # * String or Symbol -- Displays nitpick message if state is on.
9
+ #
10
+ # Example:
11
+ # nitpick true
12
+ # nitpick "I'm nitpicking"
13
+ def nitpick(value=nil)
14
+ case value
15
+ when NilClass
16
+ @nitpick
17
+ when TrueClass, FalseClass
18
+ @nitpick = value
19
+ when String, Symbol
20
+ puts "%% #{value}" if @nitpick
21
+ else
22
+ raise TypeError.new("Unknown nitpick type: #{value.class}")
23
+ end
24
+ end
25
+ end
26
+
27
+ def self.included(receiver)
28
+ receiver.extend(ClassMethods)
29
+ end
30
+
31
+ include ClassMethods
32
+ extend ClassMethods
33
+ end
@@ -0,0 +1,68 @@
1
+ require 'logger'
2
+
3
+ class QueuedLogger < Logger
4
+ def initialize(*args)
5
+ super(*args)
6
+ @queue = []
7
+ @emitted = false
8
+ end
9
+
10
+ def process_queue
11
+ if queued?
12
+ while true
13
+ entry = @queue.shift or break
14
+ severity, message = entry
15
+ raw_method = "#{severity}_without_queue"
16
+ severity_i = self.class.send(:const_get, severity.to_s.upcase)
17
+ send(raw_method, message)
18
+ end
19
+ end
20
+ end
21
+
22
+ def enqueue(level, message)
23
+ @queue << [level, message]
24
+ @emitted = false
25
+ end
26
+
27
+ def dequeue(level=nil, message=nil)
28
+ if queued?
29
+ @queue.clear
30
+ elsif level and message
31
+ send("%s_without_queue"%level, message) if @emitted
32
+ end
33
+ @emitted = false
34
+ end
35
+
36
+ def queued?
37
+ ! @queue.empty?
38
+ end
39
+
40
+ for level in %w(debug info warn error fatal) do
41
+ class_eval <<-EOB
42
+ def #{level}_with_queue(*args)
43
+ severity = self.class.send(:const_get, "#{level.upcase}")
44
+ process_queue if severity >= self.level
45
+ @emitted = true
46
+ #{level}_without_queue(*args)
47
+ end
48
+ alias_method_chain :#{level}, :queue
49
+ EOB
50
+ end
51
+
52
+ end
53
+
54
+ if __FILE__ == $0
55
+ # TODO QueuedLogger -- write a spec
56
+
57
+ q = QueuedLogger.new($stdout)
58
+ q.level = Logger::INFO
59
+ q.info("first message")
60
+ q.enqueue(:info, "queued message that'll be pushed")
61
+ q.info("second message, which pushed out queued message")
62
+ q.enqueue(:error, "ERROR: queued message that'll be discarded")
63
+ q.debug("ERROR: message that'll be discarded and won't push queue")
64
+ q.dequeue(:error, "ERROR: dequeue message that'll be discarded")
65
+ q.enqueue(:info, "queued message that'll be pushed")
66
+ q.info("message that'll push out the queue and dequeue messages")
67
+ q.dequeue(:info, "dequeue message that'll be pushed")
68
+ end
data/lib/tempster.rb ADDED
@@ -0,0 +1,250 @@
1
+ require 'tmpdir'
2
+ require 'fileutils'
3
+
4
+ # == Tempster
5
+ #
6
+ # Tempster is a pure-Ruby library for creating temporary files and directories.
7
+ # Unlike other tools, it can create both temporary files and directories, and
8
+ # is designed to be secure, thread-safe, easy-to-use, powerful thanks to
9
+ # user-configurable options, and user-friendly thanks to good defaults so you
10
+ # don't need to provide any arguments.
11
+ #
12
+ # Why write another library for this? The Tempfile standard library provides no
13
+ # way to create temporary directories and always deletes the files it creates,
14
+ # even if you want to keep them. The MkTemp gem is insecure and fails on
15
+ # collisions. Linux "mktemp" works fine but is platform-specific. Therefore, I
16
+ # had to write something.
17
+ #
18
+ # === WARNING: Using 'cd' and :noop together can be dangerous!
19
+ #
20
+ # Tempster will only *pretend* to make directories in :noop (no-operation)
21
+ # mode. In :noop mode, it will also only *pretend* to change into the directory
22
+ # when using :cd or +mktempdircd+.
23
+ #
24
+ # This can be *disastrous* if you're executing non-AutomateIt commands (e.g.
25
+ # +system+) that use *relative* *paths* and expect to be run inside the
26
+ # newly-created temporary directory because the +chdir+ didn't actually happen.
27
+ #
28
+ # Read previews.txt[link:files/docs/previews_txt.html] for instructions on how
29
+ # to write code that can be safely previewed.
30
+ #
31
+ # == Credits
32
+ # * Random string generator taken from
33
+ # http://pleac.sourceforge.net/pleac_ruby/numbers.html
34
+ class Tempster
35
+ DEFAULT_PREFIX = "tempster"
36
+ DEFAULT_FILE_MODE = 0600
37
+ DEFAULT_DIRECTORY_MODE = 0700
38
+ DEFAULT_ARMOR_LENGTH = 10
39
+ ARMOR_CHARACTERS = ["A".."Z","a".."z","0".."9"].collect{|r| r.to_a}.join
40
+
41
+ # Alias for ::tempster.
42
+ def self._tempster(opts={}, &block) # :nodoc:
43
+ tempster(opts, &block)
44
+ end
45
+
46
+ # Options:
47
+ # * :kind -- Create a :file or :directory, required.
48
+ # * :prefix -- String to prefix the temporary name with, defaults to "tempster".
49
+ # * :suffix -- String to suffix the temporary name with, defaults is no suffix.
50
+ # * :name -- Same as :prefix
51
+ # * :dir -- Base directory to create temporary entries in, uses system-wide temporary directory (e.g., <tt>/tmp</tt>) by default.
52
+ # * :cd -- Change into the newly directory created using +ch+ within the block, and then switch back to the previous directory. Only used when a block is given and the :kind is :directory. Default is false. See WARNING at the top of this class's documentation!
53
+ # * :noop -- no-operation mode, pretends to do actions without actually creating or deleting temporary entries. Default is false. WARNING: See WARNING at the top of this class's documentation!
54
+ # * :verbose -- Print status messages about creating and deleting the temporary entries. Default is false.
55
+ # * :delete -- Delete the temporary entries when exiting block. Default is true when given a block, false otherwise. If you don't use a block, you're responsible for deleting the entries yourself.
56
+ # * :tries -- Number of tries to create a temporary entry, usually it'll succeed on the first try. Default is 10.
57
+ # * :armor -- Length of armor to append to the prefix. These are random characters padding out the temporary entry names to prevent them from using existing files. If you have a very short armor, you're likely to get a collision and the algorithm will have to try again for the specified number of +tries+.
58
+ # * :message_callback -- +lambda+ called when there's a message, e.g., <tt>lambda{|message| puts message}</tt>, regardless of :verbose state. By default :messaging is nil and messages are printed to STDOUT only when :verbose is true.
59
+ # * :message_prefix -- String to put in front of messages, e.g., "# "
60
+ def self.tempster(opts={}, &block)
61
+ kind = opts.delete(:kind) or raise ArgumentError.new("'kind' option not specified")
62
+ prefix = opts.delete(:prefix) || opts.delete(:name) || DEFAULT_PREFIX
63
+ suffix = opts.delete(:suffix) || nil
64
+ dir = opts.delete(:dir) || Dir.tmpdir
65
+ cd = opts.delete(:cd) || false
66
+ noop = opts.delete(:noop) || false
67
+ verbose = opts.delete(:verbose) || false
68
+ delete = opts.delete(:delete) || block ? true : false
69
+ tries = opts.delete(:tries) || 10
70
+ armor = opts.delete(:armor) || DEFAULT_ARMOR_LENGTH
71
+ message_callback = opts.delete(:message_callback) || nil
72
+ message_prefix = opts.delete(:message_prefix) || ""
73
+ mode = opts.delete(:mode) || \
74
+ case kind
75
+ when :file
76
+ DEFAULT_FILE_MODE
77
+ when :directory
78
+ DEFAULT_DIRECTORY_MODE
79
+ else
80
+ raise ArgumentError.new("unknown kind: #{kind}")
81
+ end
82
+
83
+ raise ArgumentError.new("can only use 'delete' option with block") if delete and not block
84
+ raise ArgumentError.new("can only use 'cd' with directories and blocks") if cd and (not dir or not block)
85
+ raise ArgumentError.new("unknown extra options: #{opts.inspect}") unless opts.empty?
86
+
87
+ messager = Messager.new(verbose, message_callback, message_prefix)
88
+
89
+ path = nil
90
+ success = false
91
+ for i in 1..tries
92
+ begin
93
+ name = prefix+"_"+_armor_string(armor)
94
+ name << suffix if suffix
95
+ path = File.join(dir, name)
96
+
97
+ unless noop
98
+ case kind
99
+ when :file
100
+ File.open(path, File::RDWR|File::CREAT|File::EXCL).close
101
+ File.chmod(mode, path)
102
+ when :directory
103
+ Dir.mkdir(path, mode)
104
+ else
105
+ raise ArgumentError.new("unknown kind: #{kind}")
106
+ end
107
+ end
108
+ message = "mktempster --mode=0%o --kind=%s --dir=%s --prefix=%s" % [mode, kind, dir, prefix]
109
+ message << (" --suffix=%s" % suffix) if suffix
110
+ message << (" # => %s" % path) if block
111
+ success = true
112
+ break
113
+ rescue Errno::EEXIST
114
+ # Try again
115
+ end
116
+ end
117
+ raise IOError.new("couldn't create temporary #{kind}, ") unless success
118
+ if block
119
+ previous = Dir.pwd if cd
120
+ begin
121
+ if cd
122
+ Dir.chdir(path) unless noop
123
+ messager.puts("pushd #{path}")
124
+ end
125
+ block.call(path)
126
+ rescue Exception => e
127
+ # Re-throw exception after cleaning up
128
+ raise e
129
+ ensure
130
+ if cd
131
+ Dir.chdir(previous) unless noop
132
+ messager.puts("popd # => #{previous}")
133
+ end
134
+ if delete
135
+ FileUtils.rm_rf(path) unless noop
136
+ messager.puts("rm -rf #{path}")
137
+ end
138
+ end
139
+ return true
140
+ else
141
+ return path
142
+ end
143
+ end
144
+
145
+ # Returns a string of random characters.
146
+ def self._armor_string(length=DEFAULT_ARMOR_LENGTH)
147
+ (1..length).collect{ARMOR_CHARACTERS[rand(ARMOR_CHARACTERS.size)]}.pack("C*")
148
+ end
149
+
150
+ # Creates a temporary file.
151
+ def self.mktemp(opts={}, &block)
152
+ tempster({:kind => :file}.merge(opts), &block)
153
+ end
154
+
155
+ # Creates a temporary directory.
156
+ #
157
+ # *WARNING*: See WARNING text at the top of this class's documentation!
158
+ def self.mktempdir(opts={}, &block)
159
+ tempster({:kind => :directory}.merge(opts), &block)
160
+
161
+ end
162
+
163
+ # Creates a temporary directory and changes into it using +chdir+. This is a
164
+ # shortcut for using +mktempdir+ with the <tt>:cd => true</tt> option.
165
+ #
166
+ # *WARNING*: See WARNING text at the top of this class's documentation!
167
+ def self.mktempdircd(opts={}, &block)
168
+ tempster({:kind => :directory, :cd => true}.merge(opts), &block)
169
+ end
170
+
171
+ class Messager
172
+ def initialize(verbose, callback=nil, prefix="")
173
+ @verbose = verbose
174
+ @callback = callback
175
+ @prefix = prefix
176
+ end
177
+
178
+ def puts(message)
179
+ if @callback
180
+ @callback.call(@prefix+message)
181
+ else
182
+ if @verbose
183
+ STDOUT.puts @prefix+message
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ =begin
191
+ # Mostly recreated this in spec/integration/tempster_spec.rb
192
+
193
+ if __FILE__ == $0
194
+ # TODO Tempster -- write a spec
195
+
196
+ # Show a temp file created
197
+ x = Tempster.mktemp(:verbose => true)
198
+ File.stat(x)
199
+ File.unlink(x)
200
+
201
+ # Show a temp directory created
202
+ x = Tempster.mktempdir(:verbose => true)
203
+ File.directory?(x)
204
+ FileUtils.rm_r(x)
205
+
206
+ # Show a temp file created and removed with block
207
+ path = nil
208
+ Tempster.mktemp(:verbose => true) do |file|
209
+ path = file
210
+ puts file
211
+ end
212
+ begin
213
+ File.unlink(path)
214
+ raise "temporary file wasn't deleted when block ended: #{path}"
215
+ rescue Errno::ENOENT
216
+ # Expect the error
217
+ end
218
+
219
+ # Show temp directory created and removed with block
220
+ path = nil
221
+ Tempster.mktempdir(:verbose => true) do |dir|
222
+ puts dir
223
+ path = dir
224
+ puts File.directory?(path)
225
+ end
226
+ puts File.directory?(path)
227
+
228
+ # Show temp directory created and removed with block and cd
229
+ path = nil
230
+ Tempster.mktempdircd(:verbose => true) do |dir|
231
+ path = dir
232
+ puts "block's arg: "+dir
233
+ puts "block's pwd: "+Dir.pwd
234
+ puts "block's dir exists?: %s" % File.directory?(path)
235
+ end
236
+ puts "after dir exists?: %s" % File.directory?(path)
237
+ puts "after pwd: "+Dir.pwd
238
+
239
+ # Same with message callback
240
+ path = nil
241
+ Tempster.mktempdircd(:message_callback => lambda{|message| puts "$$$ "+message}) do |dir|
242
+ path = dir
243
+ puts "block's arg: "+dir
244
+ puts "block's pwd: "+Dir.pwd
245
+ puts "block's dir exists?: %s" % File.directory?(path)
246
+ end
247
+ puts "after dir exists?: %s" % File.directory?(path)
248
+ puts "after pwd: "+Dir.pwd
249
+ end
250
+ =end
@@ -0,0 +1,304 @@
1
+ #!/usr/bin/env jruby
2
+ # http://svn.codehaus.org/jruby/trunk/jruby/bin/index_gem_repository.rb
3
+ #--
4
+ # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
5
+ # All rights reserved.
6
+ # See LICENSE.txt for permissions.
7
+ #++
8
+
9
+
10
+ # Generate the yaml/yaml.Z index files for a gem server directory.
11
+ #
12
+ # Usage: generate_yaml_index.rb --dir DIR [--verbose]
13
+
14
+ $:.unshift '~/rubygems' if File.exist? "~/rubygems"
15
+
16
+ require 'optparse'
17
+ require 'rubygems'
18
+ require 'rubygems/format' #IK# Fix for RubyGems 0.9.5
19
+ require 'zlib'
20
+ require 'digest/sha2'
21
+ begin
22
+ require 'builder/xchar'
23
+ rescue LoadError
24
+ fail "index_gem_repository requires that the XML Builder library be installed"
25
+ end
26
+
27
+ Gem.manage_gems
28
+
29
+ ######################################################################
30
+ # Mixin that provides a +compress+ method for compressing files on
31
+ # disk.
32
+ #
33
+ module Compressor
34
+ # Compress the given file.
35
+ def compress(filename, ext="rz")
36
+ File.open(filename + ".#{ext}", "w") do |file|
37
+ file.write(zip(File.read(filename)))
38
+ end
39
+ end
40
+
41
+ # Return a compressed version of the given string.
42
+ def zip(string)
43
+ Zlib::Deflate.deflate(string)
44
+ end
45
+
46
+ # Return an uncompressed version of a compressed string.
47
+ def unzip(string)
48
+ Zlib::Inflate.inflate(string)
49
+ end
50
+ end
51
+
52
+ ######################################################################
53
+ # Announcer provides a way of announcing activities to the user.
54
+ #
55
+ module Announcer
56
+ # Announce +msg+ to the user.
57
+ def announce(msg)
58
+ puts msg if @options[:verbose]
59
+ end
60
+ end
61
+
62
+ ######################################################################
63
+ # Abstract base class for building gem indicies. Uses the template
64
+ # pattern with subclass specialization in the +begin_index+,
65
+ # +end_index+ and +cleanup+ methods.
66
+ #
67
+ class AbstractIndexBuilder
68
+ include Compressor
69
+ include Announcer
70
+
71
+ # Build a Gem index. Yields to block to handle the details of the
72
+ # actual building. Calls +begin_index+, # +end_index+ and +cleanup+
73
+ # at appropriate times to customize basic operations.
74
+ def build
75
+ if ! @enabled
76
+ yield
77
+ else
78
+ unless File.exist?(@directory)
79
+ FileUtils.mkdir_p(@directory)
80
+ end
81
+ fail "not a directory: #{@directory}" unless File.directory?(@directory)
82
+ File.open(File.join(@directory, @filename), "w") do |file|
83
+ @file = file
84
+ start_index
85
+ yield
86
+ end_index
87
+ end
88
+ cleanup
89
+ end
90
+ ensure
91
+ @file = nil
92
+ end
93
+
94
+ # Called immediately before the yield in build. The index file is
95
+ # open and availabe as @file.
96
+ def start_index
97
+ end
98
+
99
+ # Called immediately after the yield in build. The index file is
100
+ # still open and available as @file.
101
+ def end_index
102
+ end
103
+
104
+ # Called from within builder after the index file has been closed.
105
+ def cleanup
106
+ end
107
+ end
108
+
109
+ ######################################################################
110
+ # Construct the master Gem index file.
111
+ #
112
+ class MasterIndexBuilder < AbstractIndexBuilder
113
+ def initialize(filename, options)
114
+ @filename = filename
115
+ @options = options
116
+ @directory = options[:directory]
117
+ @enabled = true
118
+ end
119
+
120
+ def start_index
121
+ super
122
+ @file.puts "--- !ruby/object:Gem::Cache"
123
+ @file.puts "gems:"
124
+ end
125
+
126
+ def cleanup
127
+ super
128
+ index_file_name = File.join(@directory, @filename)
129
+ compress(index_file_name, "Z")
130
+ paranoid(index_file_name, "#{index_file_name}.Z")
131
+ end
132
+
133
+ def add(spec)
134
+ @file.puts " #{spec.full_name}: #{nest(spec.to_yaml)}"
135
+ end
136
+
137
+ def nest(yaml_string)
138
+ yaml_string[4..-1].gsub(/\n/, "\n ")
139
+ end
140
+
141
+ private
142
+
143
+ def paranoid(fn, compressed_fn)
144
+ data = File.read(fn)
145
+ compressed_data = File.read(compressed_fn)
146
+ if data != unzip(compressed_data)
147
+ fail "Compressed file #{compressed_fn} does not match uncompressed file #{fn}"
148
+ end
149
+ end
150
+ end
151
+
152
+ ######################################################################
153
+ # Construct a quick index file and all of the individual specs to
154
+ # support incremental loading.
155
+ #
156
+ class QuickIndexBuilder < AbstractIndexBuilder
157
+ def initialize(filename, options)
158
+ @filename = filename
159
+ @options = options
160
+ @directory = options[:quick_directory]
161
+ @enabled = options[:quick]
162
+ end
163
+
164
+ def cleanup
165
+ compress(File.join(@directory, @filename))
166
+ end
167
+
168
+ def add(spec)
169
+ return unless @enabled
170
+ @file.puts spec.full_name
171
+ fn = File.join(@directory, "#{spec.full_name}.gemspec.rz")
172
+ File.open(fn, "w") do |gsfile|
173
+ gsfile.write(zip(spec.to_yaml))
174
+ end
175
+ end
176
+ end
177
+
178
+ ######################################################################
179
+ # Top level class for building the repository index. Initialize with
180
+ # an options hash and call +build_index+.
181
+ #
182
+ class Indexer
183
+ include Compressor
184
+ include Announcer
185
+
186
+ # Create an indexer with the options specified by the options hash.
187
+ def initialize(options)
188
+ @options = options.dup
189
+ @directory = @options[:directory]
190
+ @options[:quick_directory] = File.join(@directory, "quick")
191
+ @master_index = MasterIndexBuilder.new("yaml", @options)
192
+ @quick_index = QuickIndexBuilder.new("index", @options)
193
+ end
194
+
195
+ # Build the index.
196
+ def build_index
197
+ announce "Building Server Index"
198
+ FileUtils.rm_r(@options[:quick_directory]) rescue nil
199
+ @master_index.build do
200
+ @quick_index.build do
201
+ gem_file_list.each do |gemfile|
202
+ spec = Gem::Format.from_file_by_path(gemfile).spec
203
+ abbreviate(spec)
204
+ sanitize(spec)
205
+ announce " ... adding #{spec.full_name}"
206
+ @master_index.add(spec)
207
+ @quick_index.add(spec)
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ # List of gem file names to index.
214
+ def gem_file_list
215
+ Dir.glob(File.join(@directory, "gems", "*.gem"))
216
+ end
217
+
218
+ # Abbreviate the spec for downloading. Abbreviated specs are only
219
+ # used for searching, downloading and related activities and do not
220
+ # need deployment specific information (e.g. list of files). So we
221
+ # abbreviate the spec, making it much smaller for quicker downloads.
222
+ def abbreviate(spec)
223
+ spec.files = []
224
+ spec.test_files = []
225
+ spec.rdoc_options = []
226
+ spec.extra_rdoc_files = []
227
+ spec.cert_chain = []
228
+ spec
229
+ end
230
+
231
+ # Sanitize the descriptive fields in the spec. Sometimes non-ASCII
232
+ # characters will garble the site index. Non-ASCII characters will
233
+ # be replaced by their XML entity equivalent.
234
+ def sanitize(spec)
235
+ spec.summary = sanitize_string(spec.summary)
236
+ spec.description = sanitize_string(spec.description)
237
+ spec.post_install_message = sanitize_string(spec.post_install_message)
238
+ spec.authors = spec.authors.collect { |a| sanitize_string(a) }
239
+ spec
240
+ end
241
+
242
+ # Sanitize a single string.
243
+ def sanitize_string(string)
244
+ string ? string.to_xs : string
245
+ end
246
+ end
247
+
248
+ ######################################################################
249
+ # Top Level Functions
250
+ ######################################################################
251
+
252
+ def handle_options(args)
253
+ # default options
254
+ options = {
255
+ :directory => '.',
256
+ :verbose => false,
257
+ :quick => true,
258
+ }
259
+
260
+ args.options do |opts|
261
+ opts.on_tail("--help", "show this message") do
262
+ puts opts
263
+ exit
264
+ end
265
+ opts.on(
266
+ '-d', '--dir=DIRNAME', '--directory=DIRNAME',
267
+ "repository base dir containing gems subdir",
268
+ String) do |value|
269
+ options[:directory] = value
270
+ end
271
+ opts.on('--[no-]quick', "include quick index") do |value|
272
+ options[:quick] = value
273
+ end
274
+ opts.on('-v', '--verbose', "show verbose output") do |value|
275
+ options[:verbose] = value
276
+ end
277
+ opts.on('-V', '--version',
278
+ "show version") do |value|
279
+ puts Gem::RubyGemsVersion
280
+ exit
281
+ end
282
+ opts.parse!
283
+ end
284
+
285
+ if options[:directory].nil?
286
+ puts "Error, must specify directory name. Use --help"
287
+ exit
288
+ elsif ! File.exist?(options[:directory]) ||
289
+ ! File.directory?(options[:directory])
290
+ puts "Error, unknown directory name #{directory}."
291
+ exit
292
+ end
293
+ options
294
+ end
295
+
296
+ # Main program.
297
+ def main_index(args)
298
+ options = handle_options(args)
299
+ Indexer.new(options).build_index
300
+ end
301
+
302
+ if __FILE__ == $0 then
303
+ main_index(ARGV)
304
+ end