automate-it 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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