imw 0.1.1 → 0.2.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 (143) hide show
  1. data/.gitignore +4 -1
  2. data/Rakefile +10 -0
  3. data/TODO +18 -0
  4. data/VERSION +1 -1
  5. data/bin/imw +1 -1
  6. data/etc/imwrc.rb +0 -50
  7. data/examples/dataset.rb +12 -0
  8. data/lib/imw/boot.rb +55 -9
  9. data/lib/imw/dataset/paths.rb +15 -24
  10. data/lib/imw/dataset/workflow.rb +131 -72
  11. data/lib/imw/dataset.rb +94 -186
  12. data/lib/imw/parsers/html_parser.rb +1 -1
  13. data/lib/imw/parsers.rb +1 -1
  14. data/lib/imw/repository.rb +3 -27
  15. data/lib/imw/resource.rb +190 -0
  16. data/lib/imw/resources/archive.rb +97 -0
  17. data/lib/imw/resources/archives_and_compressed/bz2.rb +18 -0
  18. data/lib/imw/resources/archives_and_compressed/gz.rb +18 -0
  19. data/lib/imw/resources/archives_and_compressed/rar.rb +23 -0
  20. data/lib/imw/resources/archives_and_compressed/tar.rb +23 -0
  21. data/lib/imw/resources/archives_and_compressed/tarbz2.rb +78 -0
  22. data/lib/imw/resources/archives_and_compressed/targz.rb +78 -0
  23. data/lib/imw/resources/archives_and_compressed/zip.rb +57 -0
  24. data/lib/imw/resources/archives_and_compressed.rb +32 -0
  25. data/lib/imw/resources/compressed_file.rb +89 -0
  26. data/lib/imw/resources/compressible.rb +77 -0
  27. data/lib/imw/resources/formats/delimited.rb +92 -0
  28. data/lib/imw/resources/formats/excel.rb +125 -0
  29. data/lib/imw/resources/formats/json.rb +53 -0
  30. data/lib/imw/resources/formats/sgml.rb +72 -0
  31. data/lib/imw/resources/formats/yaml.rb +53 -0
  32. data/lib/imw/resources/formats.rb +32 -0
  33. data/lib/imw/resources/local.rb +198 -0
  34. data/lib/imw/resources/remote.rb +110 -0
  35. data/lib/imw/resources/schemes/hdfs.rb +242 -0
  36. data/lib/imw/resources/schemes/http.rb +161 -0
  37. data/lib/imw/resources/schemes/s3.rb +137 -0
  38. data/lib/imw/resources/schemes.rb +19 -0
  39. data/lib/imw/resources.rb +118 -0
  40. data/lib/imw/runner.rb +5 -4
  41. data/lib/imw/transforms/archiver.rb +215 -0
  42. data/lib/imw/transforms/transferer.rb +103 -0
  43. data/lib/imw/transforms.rb +8 -0
  44. data/lib/imw/utils/error.rb +26 -30
  45. data/lib/imw/utils/extensions/array.rb +5 -15
  46. data/lib/imw/utils/extensions/hash.rb +6 -16
  47. data/lib/imw/utils/extensions/hpricot.rb +0 -14
  48. data/lib/imw/utils/extensions/string.rb +5 -15
  49. data/lib/imw/utils/extensions/symbol.rb +0 -13
  50. data/lib/imw/utils/extensions.rb +65 -0
  51. data/lib/imw/utils/log.rb +14 -13
  52. data/lib/imw/utils/misc.rb +0 -6
  53. data/lib/imw/utils/paths.rb +101 -42
  54. data/lib/imw/utils/version.rb +8 -9
  55. data/lib/imw/utils.rb +2 -18
  56. data/lib/imw.rb +92 -17
  57. data/spec/data/sample.csv +1 -1
  58. data/spec/data/sample.json +1 -0
  59. data/spec/data/sample.tsv +1 -1
  60. data/spec/data/sample.txt +1 -1
  61. data/spec/data/sample.xml +1 -1
  62. data/spec/data/sample.yaml +1 -1
  63. data/spec/imw/dataset/paths_spec.rb +32 -0
  64. data/spec/imw/dataset/workflow_spec.rb +41 -0
  65. data/spec/imw/resource_spec.rb +79 -0
  66. data/spec/imw/resources/archive_spec.rb +69 -0
  67. data/spec/imw/resources/archives_and_compressed/bz2_spec.rb +15 -0
  68. data/spec/imw/resources/archives_and_compressed/gz_spec.rb +15 -0
  69. data/spec/imw/resources/archives_and_compressed/rar_spec.rb +16 -0
  70. data/spec/imw/resources/archives_and_compressed/tar_spec.rb +16 -0
  71. data/spec/imw/resources/archives_and_compressed/tarbz2_spec.rb +24 -0
  72. data/spec/imw/resources/archives_and_compressed/targz_spec.rb +21 -0
  73. data/spec/imw/resources/archives_and_compressed/zip_spec.rb +16 -0
  74. data/spec/imw/resources/compressed_file_spec.rb +48 -0
  75. data/spec/imw/resources/compressible_spec.rb +36 -0
  76. data/spec/imw/resources/formats/delimited_spec.rb +33 -0
  77. data/spec/imw/resources/formats/json_spec.rb +32 -0
  78. data/spec/imw/resources/formats/sgml_spec.rb +24 -0
  79. data/spec/imw/resources/formats/yaml_spec.rb +41 -0
  80. data/spec/imw/resources/local_spec.rb +98 -0
  81. data/spec/imw/resources/remote_spec.rb +35 -0
  82. data/spec/imw/resources/schemes/hdfs_spec.rb +61 -0
  83. data/spec/imw/resources/schemes/http_spec.rb +19 -0
  84. data/spec/imw/resources/schemes/s3_spec.rb +19 -0
  85. data/spec/imw/transforms/archiver_spec.rb +120 -0
  86. data/spec/imw/transforms/transferer_spec.rb +113 -0
  87. data/spec/imw/utils/paths_spec.rb +5 -33
  88. data/spec/imw/utils/shared_paths_spec.rb +29 -0
  89. data/spec/spec_helper.rb +5 -5
  90. data/spec/support/paths_matcher.rb +67 -0
  91. data/spec/support/random.rb +39 -36
  92. metadata +88 -75
  93. data/lib/imw/dataset/task.rb +0 -41
  94. data/lib/imw/files/archive.rb +0 -113
  95. data/lib/imw/files/basicfile.rb +0 -122
  96. data/lib/imw/files/binary.rb +0 -28
  97. data/lib/imw/files/compressed_file.rb +0 -93
  98. data/lib/imw/files/compressed_files_and_archives.rb +0 -334
  99. data/lib/imw/files/compressible.rb +0 -103
  100. data/lib/imw/files/csv.rb +0 -113
  101. data/lib/imw/files/directory.rb +0 -62
  102. data/lib/imw/files/excel.rb +0 -84
  103. data/lib/imw/files/json.rb +0 -41
  104. data/lib/imw/files/sgml.rb +0 -46
  105. data/lib/imw/files/text.rb +0 -68
  106. data/lib/imw/files/yaml.rb +0 -46
  107. data/lib/imw/files.rb +0 -125
  108. data/lib/imw/packagers/archiver.rb +0 -126
  109. data/lib/imw/packagers/s3_mover.rb +0 -36
  110. data/lib/imw/packagers.rb +0 -8
  111. data/lib/imw/utils/components.rb +0 -61
  112. data/lib/imw/utils/config.rb +0 -46
  113. data/lib/imw/utils/extensions/class/attribute_accessors.rb +0 -8
  114. data/lib/imw/utils/extensions/core.rb +0 -27
  115. data/lib/imw/utils/extensions/dir.rb +0 -24
  116. data/lib/imw/utils/extensions/file_core.rb +0 -64
  117. data/lib/imw/utils/extensions/typed_struct.rb +0 -22
  118. data/lib/imw/utils/extensions/uri.rb +0 -59
  119. data/lib/imw/utils/view/dump_csv.rb +0 -112
  120. data/lib/imw/utils/view/dump_csv_older.rb +0 -117
  121. data/lib/imw/utils/view.rb +0 -113
  122. data/spec/imw/dataset/datamapper/uri_spec.rb +0 -43
  123. data/spec/imw/dataset/datamapper_spec_helper.rb +0 -11
  124. data/spec/imw/files/archive_spec.rb +0 -118
  125. data/spec/imw/files/basicfile_spec.rb +0 -121
  126. data/spec/imw/files/bz2_spec.rb +0 -32
  127. data/spec/imw/files/compressed_file_spec.rb +0 -96
  128. data/spec/imw/files/compressible_spec.rb +0 -100
  129. data/spec/imw/files/file_spec.rb +0 -144
  130. data/spec/imw/files/gz_spec.rb +0 -32
  131. data/spec/imw/files/rar_spec.rb +0 -33
  132. data/spec/imw/files/tar_spec.rb +0 -31
  133. data/spec/imw/files/text_spec.rb +0 -23
  134. data/spec/imw/files/zip_spec.rb +0 -31
  135. data/spec/imw/files_spec.rb +0 -38
  136. data/spec/imw/packagers/archiver_spec.rb +0 -125
  137. data/spec/imw/packagers/s3_mover_spec.rb +0 -7
  138. data/spec/imw/utils/extensions/file_core_spec.rb +0 -72
  139. data/spec/imw/utils/extensions/find_spec.rb +0 -113
  140. data/spec/imw/workflow/rip/local_spec.rb +0 -89
  141. data/spec/imw/workflow/rip_spec.rb +0 -27
  142. data/spec/support/archive_contents_matcher.rb +0 -94
  143. data/spec/support/directory_contents_matcher.rb +0 -61
data/lib/imw/runner.rb CHANGED
@@ -18,7 +18,7 @@ module IMW
18
18
  def initialize *args
19
19
  @args = args
20
20
  @options = DEFAULT_OPTIONS.dup
21
- parser.parse!(args) # will trim options from args
21
+ parser.parse!(args)
22
22
  end
23
23
 
24
24
  def parser
@@ -67,11 +67,12 @@ EOF
67
67
  end
68
68
 
69
69
  def handles
70
+ require 'set'
70
71
  matched_handles = Set.new
71
72
  if options[:selectors].blank?
72
- matched_handles += IMW::REPOSITORY.keys
73
+ matched_handles += IMW.repository.keys
73
74
  else
74
- keys = IMW::REPOSITORY.keys
75
+ keys = IMW.repository.keys
75
76
  unless keys.empty?
76
77
  options[:selectors].each do |selector|
77
78
  matched_handles += keys.find_all { |key| key =~ Regexp.new(selector) }
@@ -82,7 +83,7 @@ EOF
82
83
  end
83
84
 
84
85
  def datasets
85
- handles.map { |handle| IMW::REPOSITORY[handle] }
86
+ handles.map { |handle| IMW.repository[handle] }
86
87
  end
87
88
 
88
89
  def list!
@@ -0,0 +1,215 @@
1
+ require 'imw/resource'
2
+
3
+ module IMW
4
+ module Transforms
5
+
6
+ # Packages an Array of input files into a single output archive.
7
+ # When the archive is extracted, all the input files given will be
8
+ # in a single directory with a chosen name. The path to the output
9
+ # archive determines both the name of the archive and its type (tar,
10
+ # tar.bz2, zip, &c.).
11
+ #
12
+ # If any of the input files are themselves archives, they will first
13
+ # be extracted, with only their contents winding up in the final
14
+ # directory (the file hierarchy of the archive will be preserved).
15
+ # If any of the input files are compressed, they will first be
16
+ # uncompressed before being added to the directory.
17
+ #
18
+ # Both local and remote files can be archived. An exmaple:
19
+ #
20
+ # archiver = IMW::Transforms::Archiver.new 'my_archive', '/path/to/my/regular_file.tsv', '/path/to/an/archive.tar.bz2', '/path/to/my_compressed_file.gz', 'http://mywebsite.com/index.html'
21
+ # archiver.package! '/path/to/my_archive.zip'
22
+ #
23
+ # This will create a ZIP archive at
24
+ # <tt>/path/to/my_archive.zip</tt>. When the ZIP archive is
25
+ # extracted its contents will look like
26
+ #
27
+ # my_archive
28
+ # |-- regular_file.tsv
29
+ # |-- archive_file1
30
+ # |-- archive_dir
31
+ # | |-- archive_file2
32
+ # | `-- archive_file3
33
+ # |-- archive_file3
34
+ # |-- my_compressed_file
35
+ # `-- index.html
36
+ #
37
+ # Notice that
38
+ #
39
+ # - the name of the extracted directory is given by the first
40
+ # argument to the Archiver when it was instantiated.
41
+ #
42
+ # - all files wind up in the top-level of this extracted directory
43
+ # when possible (<tt>regular_file.tsv</tt>, <tt>index.html</tt>)
44
+ #
45
+ # - /path/to/archive.tar.bz2 was not directly included, but its
46
+ # contents (<tt>archive_file1</tt>,
47
+ # <tt>archive_dir/archive_file2</tt>,
48
+ # <tt>archive_dir/archive_file3</tt>) were included instead.
49
+ #
50
+ # - /path/to/my_compressed_file.gz was first uncompressed before
51
+ # being added to the archive.
52
+ #
53
+ # - the remote file <tt>http://mywebsite.com/index.html</tt> was
54
+ # downloaded and included
55
+ #
56
+ # This process can take a while when the constituent files are
57
+ # large because there is quite a lot of preparation done to the
58
+ # files to make this nice output structure in the final archive.
59
+ # Further calls to <tt>package!</tt> on the same instance of
60
+ # Archiver will skip the preparation step (the intermediate
61
+ # results of which are sitting in IMW's temporary directory) and
62
+ # directly create the package, saving time when attempting to
63
+ # create multiple package formats from the same input data.
64
+ class Archiver
65
+
66
+ attr_accessor :name, :local_inputs, :remote_inputs
67
+
68
+ def initialize name, raw_inputs
69
+ @name = name
70
+ self.inputs = raw_inputs
71
+ end
72
+
73
+ # Set the inputs for this archiver.
74
+ #
75
+ # @param [String, IMW::Resource] new_inputs the inputs to archive, local or remote
76
+ def inputs= new_inputs
77
+ @local_inputs, @remote_inputs = [], []
78
+ new_inputs.each do |obj|
79
+ input = obj.is_a?(IMW::Resource) ? obj : IMW.open(obj) # take either paths/URIs or IMW::Resource objects
80
+ if input.is_local?
81
+ @local_inputs << (input.directory? ? input.resources : input) # recurse through directories
82
+ else
83
+ @remote_inputs << input
84
+ end
85
+ end
86
+ @local_inputs.flatten!
87
+ end
88
+
89
+ # Return a list of error messages for this archiver.
90
+ #
91
+ # @return [Array] the error messages
92
+ def errors
93
+ @errors ||= []
94
+ end
95
+
96
+ # Was this archiver successful (did it not have any errors)?
97
+ #
98
+ # @return [true, false]
99
+ def success?
100
+ errors.empty?
101
+ end
102
+
103
+ # A temporary directory to work in. Its contents will
104
+ # ultimately consist of a directory named for the package
105
+ # containing all the input files.
106
+ #
107
+ # @return [String]
108
+ def tmp_dir
109
+ @tmp_dir ||= File.join(IMW.path_to(:tmp_root, 'packager'), (Time.now.to_i.to_s + "-" + $$.to_s)) # guaranteed unique on a node
110
+ end
111
+
112
+ # A directory which will contain all the content being packaged,
113
+ # including the contents of any archives that were included in
114
+ # the list of files to process.
115
+ #
116
+ # @return [String]
117
+ def dir
118
+ @dir ||= File.join(tmp_dir, name.to_s)
119
+ end
120
+
121
+ # Remove the +tmp_dir+ entirely, getting rid of all temporary
122
+ # files.
123
+ def clean!
124
+ FileUtils.rm_rf(tmp_dir)
125
+ end
126
+
127
+ # Copy, decompress, or extract the input paths to the temporary
128
+ # directory, readying them for packaging.
129
+ def prepare!
130
+ FileUtils.mkdir_p dir unless File.exist?(dir)
131
+
132
+ local_inputs.each do |existing_file|
133
+ new_path = File.join(dir, existing_file.basename)
134
+ case
135
+ when existing_file.is_archive?
136
+ FileUtils.cd(dir) do
137
+ existing_file.extract
138
+ end
139
+ when existing_file.is_compressed?
140
+ existing_file.cp(new_path).decompress!
141
+ else
142
+ existing_file.cp(new_path)
143
+ end
144
+ end
145
+
146
+ remote_inputs.each do |remote_input|
147
+ remote_input.cp(File.join(dir, remote_input.effective_basename))
148
+ end
149
+ end
150
+
151
+ # Checks to see if all expected files exist in the temporary
152
+ # directory for this packager.
153
+ #
154
+ # @return [true, false]
155
+ def prepared?
156
+ local_inputs.each do |existing_file|
157
+ case
158
+ when existing_file.is_archive?
159
+ existing_file.contents.each do |archived_file_path|
160
+ return false unless File.exist?(File.join(dir, archived_file_path))
161
+ end
162
+ when existing_file.is_compressed?
163
+ return false unless File.exist?(File.join(dir, existing_file.decompressed_basename))
164
+ else
165
+ return false unless File.exist?(File.join(dir, existing_file.basename))
166
+ end
167
+ end
168
+
169
+ remote_inputs.each do |remote_input|
170
+ return false unless File.exist?(File.join(dir, remote_input.effective_basename))
171
+ end
172
+
173
+ true
174
+ end
175
+
176
+ # Package the contents of the temporary directory to an archive
177
+ # at +output+ but return exceptions instead of raising them.
178
+ #
179
+ # @param [String, IMW::Resource] output the path to the output package
180
+ # @param [Hash] options
181
+ # @return [RuntimeError, IMW::Resource] either the completed package or the error which was raised
182
+ def package output, options={}
183
+ begin
184
+ package! output, options={}
185
+ rescue => e
186
+ return e
187
+ end
188
+ end
189
+
190
+ # Package the contents of the temporary directory to an archive
191
+ # at +output+. The extension of +output+ determines the kind of
192
+ # archive.
193
+ #
194
+ # @param [String, IMW::Resource] output the path to the output package
195
+ # @param [Hash] options
196
+ # @return [IMW::Resource] the completed package
197
+ def package! output, options={}
198
+ prepare! unless prepared?
199
+ output = IMW.open(output)
200
+ FileUtils.mkdir_p(output.dirname) unless File.exist?(output.dirname)
201
+ output.rm! if output.exist?
202
+ FileUtils.cd(tmp_dir) { IMW.open(output.basename).create(*Dir["#{name}/**/*"]).mv(output.path) }
203
+ add_processing_error "Archiver: couldn't create archive #{output.path}" unless output.exists?
204
+ output
205
+ end
206
+
207
+ protected
208
+ def add_processing_error error # :nodoc:
209
+ IMW.logger.warn error
210
+ errors << error
211
+ end
212
+
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,103 @@
1
+ module IMW
2
+ module Transforms
3
+ class Transferer
4
+
5
+ attr_accessor :action, :source, :destination
6
+
7
+ def initialize action, source, destination
8
+ @action = normalize_action(action)
9
+ @source = IMW.open(source)
10
+ @destination = IMW.open(destination)
11
+ raise IMW::PathError.new("Source and destination have the same URI: #{@source.uri}") if @source.uri.to_s == @destination.uri.to_s
12
+ end
13
+
14
+ def transfer!
15
+ if source.is_local?
16
+ source.should_exist!("Cannot copy") # don't bother checking for remote resources
17
+ source_scheme = 'file' # make sure it isn't blank
18
+ else
19
+ source_scheme = source.scheme
20
+ end
21
+ destination_scheme = destination.is_local? ? 'file' : destination.scheme
22
+ method = "#{source_scheme}_to_#{destination_scheme}"
23
+ if respond_to?(method)
24
+ send(method)
25
+ else
26
+ raise IMW::NoMethodError.new("Do not know how to #{action} #{source.uri} => #{destination.uri} (#{source_scheme.inspect} => #{destination_scheme.inspect})")
27
+ end
28
+ destination.reopen
29
+ end
30
+
31
+ protected
32
+
33
+ def normalize_action action # :nodoc:
34
+ case action.to_sym
35
+ when :cp, :copy then :cp
36
+ when :mv, :move, :mv! then :mv
37
+ else raise IMW::ArgumentError.new("action (#{action}) must be one of `cp' (or `copy') or `mv' (or `move' or `mv!'")
38
+ end
39
+ end
40
+
41
+ #
42
+ # Purely local file
43
+ #
44
+
45
+ def file_to_file
46
+ FileUtils.send(action, source.path, destination.path)
47
+ end
48
+
49
+ #
50
+ # HTTP
51
+ #
52
+
53
+ def http_to_file
54
+ File.open(destination.path, 'w') { |f| f.write(source.read) }
55
+ end
56
+
57
+ #
58
+ # S3
59
+ #
60
+
61
+ def file_to_s3
62
+ IMW::Resources::Schemes::S3.put(source, destination)
63
+ end
64
+
65
+ def http_to_s3
66
+ IMW::Resources::Schemes::S3.put(source, destination)
67
+ end
68
+
69
+ def s3_to_file
70
+ IMW::Resources::Schemes::S3.get(source, destination)
71
+ end
72
+
73
+ def s3_to_s3
74
+ IMW::Resources::Schemes::S3.copy(source, destination)
75
+ end
76
+
77
+ #
78
+ # HDFS
79
+ #
80
+
81
+ def hdfs_to_hdfs
82
+ IMW::Resources::Schemes::HDFS.fs(action, source.path, destination.path)
83
+ end
84
+
85
+ def file_to_hdfs
86
+ IMW::Resources::Schemes::HDFS.fs(:put, source.path, destination.path)
87
+ end
88
+
89
+ def hdfs_to_file
90
+ IMW::Resources::Schemes::HDFS.fs(:get, source.path, destination.path)
91
+ end
92
+
93
+ def s3_to_hdfs
94
+ IMW::Resources::Schemes::HDFS.fs(action, source.s3n_url, destination.path)
95
+ end
96
+
97
+ def hdfs_to_s3
98
+ IMW::Resources::Schemes::HDFS.fs(action, source.path, destination.s3n_url)
99
+ end
100
+
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,8 @@
1
+ module IMW
2
+ module Transforms
3
+ autoload :Archiver, 'imw/transforms/archiver'
4
+ autoload :Transferer, 'imw/transforms/transferer'
5
+ end
6
+ end
7
+
8
+
@@ -1,54 +1,50 @@
1
- #
2
- # h2. lib/imw/utils/error -- errors
3
- #
4
- # == About
5
- #
6
- # Error objects for IMW.
7
- #
8
- # Author:: (Philip flip Kromer, Dhruv Bansal) for Infinite Monkeywrench Project (mailto:coders@infochimps.org)
9
- # Copyright:: Copyright (c) 2008 infochimps.org
10
- # License:: GPL 3.0
11
- # Website:: http://infinitemonkeywrench.org/
12
- #
13
-
14
1
  module IMW
15
2
 
16
- # A generic error class.
17
- class Error < StandardError
18
- end
3
+ # Base error class which all IMW errors subclass.
4
+ Error = Class.new(StandardError)
19
5
 
20
- class TypeError < TypeError
21
- end
6
+ # Method undefined.
7
+ NoMethodError = Class.new(Error)
22
8
 
23
- class ArgumentError < ArgumentError
24
- end
9
+ # Type error.
10
+ TypeError = Class.new(Error)
25
11
 
26
- class NotImplementedError < NotImplementedError
27
- end
12
+ # Not implemented (typically because user needs to define a method
13
+ # when subclassing a base class).
14
+ NotImplementedError = Class.new(Error)
28
15
 
29
- class ParseError < Error
30
- end
16
+ # Error during parsing.
17
+ ParseError = Class.new(Error)
18
+
19
+ # Error with a non-existing, invalid, or inaccessible path.
20
+ PathError = Class.new(Error)
21
+
22
+ # Error communicating with a remote entity.
23
+ NetworkError = Class.new(Error)
24
+
25
+ # Error communicating with a remote entity.
26
+ ArgumentError = Class.new(Error)
31
27
 
32
28
  # An error meant to be used when a system call goes awry. It will
33
29
  # report exit status and the process id of the offending call.
34
30
  class SystemCallError < IMW::Error
35
31
 
36
- def initialize(message)
32
+ attr_reader :status, :message
33
+
34
+ def initialize(status, message)
35
+ @status = status
37
36
  @message = message
38
37
  end
39
38
 
40
39
  def display
41
- "(error code: #{$?.exitstatus}, pid: #{$?.pid}) #{@message}"
40
+ "(error code: #{status.exitstatus}, pid: #{status.pid}) #{message}"
42
41
  end
43
42
 
44
43
  def to_s
45
- "(error code: #{$?.exitstatus}, pid: #{$?.pid}) #{@message}"
44
+ "(error code: #{status.exitstatus}, pid: #{status.pid}) #{message}"
46
45
  end
47
46
 
48
47
  end
49
48
 
50
- # A error for improperly specified, inappropriate, or broken paths.
51
- class PathError < IMW::Error
52
- end
53
49
 
54
50
  end
@@ -1,15 +1,3 @@
1
- #
2
- # h2. lib/imw/utils/extensions/array.rb -- array extensions
3
- #
4
- # == About
5
- #
6
- # Extensions to the +Array+ class.
7
- #
8
- # Author:: (Philip flip Kromer, Dhruv Bansal) for Infinite Monkeywrench Project (mailto:coders@infochimps.org)
9
- # Copyright:: Copyright (c) 2008 infochimps.org
10
- # License:: GPL 3.0
11
- # Website:: http://infinitemonkeywrench.org/
12
- #
13
1
  require 'active_support/core_ext/array/extract_options'
14
2
  class Array #:nodoc:
15
3
  include ActiveSupport::CoreExtensions::Array::ExtractOptions
@@ -118,8 +106,10 @@ class Array
118
106
  terminals.map! {|terminal| yield terminal } if block
119
107
  terminals
120
108
  end
121
-
122
109
 
123
- end
110
+ # Dump the data in this array to the resource at the given +uri+.
111
+ def dump uri
112
+ IMW.open(uri, :mode => 'w').dump(self)
113
+ end
124
114
 
125
- # puts "#{File.basename(__FILE__)}: I have a loooong list of complaints. Firstly, ..." # at bottom
115
+ end
@@ -1,16 +1,3 @@
1
- #
2
- # h2. lib/imw/utils/extensions/hash.rb -- hash extensions
3
- #
4
- # == About
5
- #
6
- # Extensions to the built-in +Hash+ class.
7
- #
8
- # Author:: (Philip flip Kromer, Dhruv Bansal) for Infinite Monkeywrench Project (mailto:coders@infochimps.org)
9
- # Copyright:: Copyright (c) 2008 infochimps.org
10
- # License:: GPL 3.0
11
- # Website:: http://infinitemonkeywrench.org/
12
- #
13
-
14
1
  require 'active_support/core_ext/hash/reverse_merge'
15
2
 
16
3
  class Hash
@@ -169,6 +156,7 @@ class Hash
169
156
  # search(options.slice(:mass, :velocity, :time))
170
157
  # Returns a new hash with only the given keys.
171
158
  def slice(*keys)
159
+ require 'set'
172
160
  allowed = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
173
161
  reject { |key,| !allowed.include?(key) }
174
162
  end
@@ -212,7 +200,9 @@ class Hash
212
200
  terminals.map! {|terminal| yield terminal } if block
213
201
  terminals
214
202
  end
215
-
216
- end
217
203
 
218
- # puts "#{File.basename(__FILE__)}: To each improvement there corresponds another, yes?" # at bottom
204
+ # Dump the data from this Hash into the given +uri+.
205
+ def dump uri
206
+ IMW.open(uri).dump(self)
207
+ end
208
+ end
@@ -1,17 +1,3 @@
1
- #
2
- # h2. lib/imw/utils/extensions/hpricot.rb -- extensions to hpricot
3
- #
4
- # == About
5
- #
6
- # Some IMW extensions for Why's Hpricot library.
7
- #
8
- # Author:: (Philip flip Kromer, Dhruv Bansal) for Infinite Monkeywrench Project (mailto:coders@infochimps.org)
9
- # Copyright:: Copyright (c) 2008 infochimps.org
10
- # License:: GPL 3.0
11
- # Website:: http://infinitemonkeywrench.org/
12
- #
13
- # puts "#{File.basename(__FILE__)}: Something clever" # at bottom
14
-
15
1
  require 'hpricot'
16
2
 
17
3
  module Hpricot::IMWExtensions
@@ -1,16 +1,3 @@
1
- #
2
- # h2. lib/imw/utils/extensions/string.rb -- string extensions
3
- #
4
- # == About
5
- #
6
- # Implements some useful extensions to the +String+ class.
7
- #
8
- # Author:: (Philip flip Kromer, Dhruv Bansal) for Infinite Monkeywrench Project (mailto:coders@infochimps.org)
9
- # Copyright:: Copyright (c) 2008 infochimps.org
10
- # License:: GPL 3.0
11
- # Website:: http://infinitemonkeywrench.org/
12
- #
13
-
14
1
  class String
15
2
 
16
3
  # Does the string end with the specified +suffix+ (stolen from
@@ -44,6 +31,9 @@ class String
44
31
  self.downcase.underscore.to_sym
45
32
  end
46
33
 
47
- end
34
+ # Dump this string into the given +uri+.
35
+ def dump uri
36
+ IMW.open(uri).dump(self)
37
+ end
48
38
 
49
- # puts "#{File.basename(__FILE__)}: You tie a long string to your Monkeywrench, place it on the ground, and hide around the corner with the string in your hand, waiting for passersby to try and make a grab for it." # at bottom
39
+ end
@@ -1,14 +1,3 @@
1
- #
2
- # h2. lib/imw/utils/extensions/symbol.rb -- extensions to symbol class
3
- #
4
- # == About
5
- #
6
- # Author:: (Philip flip Kromer, Dhruv Bansal) for Infinite Monkeywrench Project (mailto:coders@infochimps.org)
7
- # Copyright:: Copyright (c) 2008 infochimps.org
8
- # License:: GPL 3.0
9
- # Website:: http://infinitemonkeywrench.org/
10
- #
11
-
12
1
  class Symbol
13
2
 
14
3
  # Turn the symbol into a simple proc (stolen from
@@ -24,5 +13,3 @@ class Symbol
24
13
  end
25
14
 
26
15
  end
27
-
28
- # puts "#{File.basename(__FILE__)}: You whisper a word of power and smile as the the Ruby Palace thunders with the sound of falling blocks." # at bottom
@@ -0,0 +1,65 @@
1
+ require 'imw/utils/extensions/string'
2
+ require 'imw/utils/extensions/array'
3
+ require 'imw/utils/extensions/hash'
4
+ require 'imw/utils/extensions/struct'
5
+ require 'imw/utils/extensions/symbol'
6
+
7
+ require 'active_support/core_ext/object/blank'
8
+ require 'active_support/core_ext/object/misc'
9
+
10
+
11
+ module IMW
12
+ # A replacement for the standard system call which raises an
13
+ # IMW::SystemCallError if the command fails which prints better
14
+ # debugging info.
15
+ #
16
+ # This function relies upon Kernel.system and obeys the same rules:
17
+ #
18
+ # - if +commands+ has only only a single element then no shell
19
+ # characters or spaces are escaped -- you have to do it yourself
20
+ # or you get to use shell characters, depending on your
21
+ # perspective.
22
+ #
23
+ # - if +commands+ is a list of elements then the second and further
24
+ # elements in the list have their shell characters and spaces
25
+ # escaped
26
+ #
27
+ # But it also has its own rules:
28
+ #
29
+ # - When one of the +commands+ is an empty or blank string,
30
+ # Kernel.system honors it and escapes it properly and sends it
31
+ # along for evaluation. This can be a problem for some programs
32
+ # and so IMW.system excludes blank (as in <tt>blank?</tt>)
33
+ # elements of +commands+.
34
+ #
35
+ # - +commands+ will be flattened (see the gotcha below)
36
+ #
37
+ # Calling out to the shell like this is often brittle. Imagine
38
+ # defining
39
+ #
40
+ # prog = 'some_prog'
41
+ # flags = '-v -f'
42
+ # args = 'file.txt'
43
+ #
44
+ # and later calling
45
+ #
46
+ # IMW.system prog, flags, args
47
+ #
48
+ # The space in the second argument ('-v -f') will be escaped and
49
+ # will therefore not be properly parsed by +some_prog+. Instead try
50
+ #
51
+ # prog = 'some_prog'
52
+ # flags = ['-v', '-f']
53
+ # args = ['file.txt']
54
+ #
55
+ # IMW.system prog, flags, *args
56
+ #
57
+ # which will work fine since +flags+ will automatically be flattend.
58
+ def self.system *commands
59
+ stripped_commands = commands.flatten.map { |command| command.to_s unless command.blank? }.compact
60
+ Kernel.system(*stripped_commands)
61
+ raise IMW::SystemCallError.new($?.dup, commands.join(' ')) unless $?.success?
62
+ end
63
+ end
64
+
65
+