cotta 1.0.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.
data/README ADDED
@@ -0,0 +1,75 @@
1
+ Welcome to the Ruby version of the {Cotta file API}[http://cotta.rubyforge.org]
2
+
3
+ = Introduction
4
+
5
+ Cotta project is created to provide a lightweight, simple and sensible API to file operation and testing.
6
+ See {Cotta Power}[http://cotta.sourceforge.net/power.html] for its motivation
7
+
8
+
9
+ = Install It
10
+
11
+ The easiest way to install the install cotta using RubyGems:
12
+
13
+ sudo gem install cotta
14
+
15
+ = Features
16
+
17
+ Cotta is just a plain Ruby API, so you can use it wherever you can use Ruby.
18
+
19
+ To used the new API just require the client driver:
20
+
21
+ require "rubygems"
22
+ require "cotta"
23
+
24
+ For a fully backward compatible API you can start with:
25
+
26
+ require "rubygems"
27
+ gem "cotta"
28
+ require "cotta"
29
+
30
+ For instance
31
+ to write a little Ruby script using cotta you could write something like:
32
+
33
+ #!/usr/bin/env ruby
34
+ #
35
+ # Sample Ruby script using the Cotta API
36
+ #
37
+ require "rubygems"
38
+ gem "cotta", ">=1.0.0"
39
+ require "cotta"
40
+
41
+ #system implementation is injected here
42
+ cotta = Cotta.physical
43
+ file = cotta.file('dir/file.txt')
44
+ file.should_not be_exists
45
+ # parent directories are created automatically
46
+ file.save('my content')
47
+ file2 = cotta.file('dir/file2.txt')
48
+ file2.should_not be_exists
49
+ file.copy_to(file2)
50
+ file2.should be_exists
51
+ file2.load.should == 'my content'
52
+ file2.read {|file| puts file.gets}
53
+
54
+ = Writing Tests
55
+
56
+ To test your code that uses Cotta API, you just need to pass in a Cotta instance that is backed by an in-memory
57
+ file system:
58
+
59
+ cotta = Cotta.in_memory
60
+
61
+ = Resources
62
+
63
+ * Source Code at http://github.com/wolfdancer/cotta
64
+ * Report bugs at http://github.com/wolfdancer/cotta/issues
65
+ * Browse API at http://cotta.rubyforge.org
66
+ * Discuss at http://groups.google.com/group/cotta
67
+
68
+ = Reports
69
+
70
+ * rSpec: {rspec}[link:rspec/index.html]
71
+ * code coverage:{rcov}[link:rcov/index.html]
72
+
73
+ = Team
74
+
75
+ * Shane Duan
@@ -0,0 +1,12 @@
1
+ require 'pathname'
2
+ require File.dirname(__FILE__) + '/cotta/cotta'
3
+ require File.dirname(__FILE__) + '/cotta/cotta_dir'
4
+ require File.dirname(__FILE__) + '/cotta/cotta_file'
5
+ require File.dirname(__FILE__) + '/cotta/impl/cotta_pathname'
6
+ require File.dirname(__FILE__) + '/cotta/impl/in_memory_system'
7
+ require File.dirname(__FILE__) + '/cotta/impl/physical_system'
8
+ require File.dirname(__FILE__) + '/cotta/impl/command_error'
9
+ require File.dirname(__FILE__) + '/cotta/impl/command_interface'
10
+ require File.dirname(__FILE__) + '/cotta/file_not_found_error'
11
+ require File.dirname(__FILE__) + '/cotta/impl/command_runner'
12
+ require File.dirname(__FILE__) + '/cotta/file_factory'
@@ -0,0 +1,37 @@
1
+ # Cotta module that contains all the classes used for file operations
2
+ # see link:files/README.html
3
+ # There are also four methods on the module for the most common ways to
4
+ # start with Cotta API
5
+ module Cotta
6
+ # Creates CottaFile repersenting physical file
7
+ def self::file(path)
8
+ FileFactory.file(path)
9
+ end
10
+
11
+ # Creates CottaDir representing physical directory
12
+ def self::dir(path)
13
+ FileFactory.dir(path)
14
+ end
15
+
16
+ # Creates CottaDir that is the parent of the path
17
+ # This is typically used with __FILE__
18
+ # e.g. dir = Cotta.parent_dir(__FILE__)
19
+ def self.parent_dir(path)
20
+ FileFactory.parent_dir(path)
21
+ end
22
+
23
+ # Returns the file facotry backed by physical system
24
+ def self::physical
25
+ FileFactory.physical
26
+ end
27
+
28
+ # Returns the file factory backed by in-memory system
29
+ def self::in_memory
30
+ FileFactory.in_memory
31
+ end
32
+
33
+ # returns the file factory backed by the given system
34
+ def self::factory(system)
35
+ FileFactory.new(system)
36
+ end
37
+ end
@@ -0,0 +1,184 @@
1
+ module Cotta
2
+ # This class represents a directory
3
+ class CottaDir
4
+ # Path of the directory
5
+ attr_reader :path
6
+
7
+ # file factory of the directory
8
+ attr_reader :factory
9
+
10
+ # Create an instance of CottaDir that is on
11
+ # the given path and
12
+ # backed by the given system
13
+ def initialize(factory, path)
14
+ @path = path
15
+ @factory = factory
16
+ @name = @path.basename.to_s
17
+ end
18
+
19
+ # name of the directory
20
+ def name
21
+ name = nil
22
+ if root?
23
+ name = path.to_s
24
+ else
25
+ name = @path.basename.to_s
26
+ end
27
+ return name
28
+ end
29
+
30
+ # returns true if this directory is the root directory
31
+ def root?
32
+ parent.nil?
33
+ end
34
+
35
+ # returns true if this directory exists
36
+ def exists?
37
+ factory.system.dir_exists?(@path)
38
+ end
39
+
40
+ # returns the stat of the current directory
41
+ def stat
42
+ factory.system.dir_stat(@path)
43
+ end
44
+
45
+ # returns the parent directory of this directory
46
+ # or nil if this is root
47
+ def parent
48
+ parent_path = @path.cotta_parent
49
+ return nil unless parent_path
50
+ candidate = CottaDir.new(factory, parent_path)
51
+ if (block_given?)
52
+ candidate = candidate.parent until candidate.nil? or yield candidate
53
+ end
54
+ candidate
55
+ end
56
+
57
+ # returns the relative path from the given file or directory
58
+ def relative_path_from(entry)
59
+ @path.relative_path_from(entry.path)
60
+ end
61
+
62
+ # returns the sub-directory with the given name
63
+ def dir(name)
64
+ return CottaDir.new(factory, @path.join(name))
65
+ end
66
+
67
+ # returns the file under this directory with the given name
68
+ def file(name)
69
+ return CottaFile.new(factory, @path.join(name))
70
+ end
71
+
72
+ # creates this directory and its parent directory
73
+ def mkdirs
74
+ if (not exists?)
75
+ parent.mkdirs
76
+ factory.system.mkdir @path
77
+ end
78
+ end
79
+
80
+ # deletes this directory and all its children
81
+ def delete
82
+ if (exists?)
83
+ list.each do |children|
84
+ children.delete
85
+ end
86
+ factory.system.delete_dir(@path)
87
+ end
88
+ end
89
+
90
+ # move this directory to target directory
91
+ # this method assumes that this directory and the target directory
92
+ # are backed by the same file system
93
+ def move_to(target)
94
+ target.parent.mkdirs
95
+ factory.system.move_dir(@path, target.path)
96
+ end
97
+
98
+ # move this directory to target path
99
+ # this method assumes that this directory and the target directory
100
+ # are backed by the same file system
101
+ def move_to_path(target_path)
102
+ move_to(cotta.dir(target_path))
103
+ end
104
+
105
+ # copy this directory to target directory
106
+ # this method assumes that this directory and the target directory
107
+ # are backed by the same file system
108
+ def copy_to(target)
109
+ target.parent.mkdirs
110
+ factory.system.copy_dir(@path, target.path)
111
+ end
112
+
113
+ # archive this directory and call the given block
114
+ # to determine if a file or directory should be included
115
+ def archive(target = nil, &block)
116
+ require 'rubygems/package'
117
+ unless target
118
+ target = parent.file("#{name}.tar")
119
+ end
120
+ target.write_binary do |io|
121
+ writer = Gem::Package::TarWriter.new(io) do |tar_io|
122
+ archive_dir(tar_io, self, &block)
123
+ end
124
+ end
125
+ target
126
+ end
127
+
128
+ def archive_dir(tar_io, dir, &block)
129
+ dir.list.each do |child|
130
+ if (block_given? and not yield child)
131
+ next
132
+ end
133
+ stat = child.stat
134
+ entry_name = child.relative_path_from(self).to_s
135
+ mode = stat.mode
136
+ if (stat.file?)
137
+ tar_io.add_file(entry_name, mode) do |entry_output|
138
+ child.read_binary do |input|
139
+ CottaFile.copy_io(input, entry_output)
140
+ end
141
+ end
142
+ elsif (stat.directory?)
143
+ tar_io.mkdir(entry_name, mode)
144
+ archive_dir(tar_io, child, &block)
145
+ end
146
+ end
147
+ end
148
+
149
+ private :archive_dir
150
+
151
+ def copy_to_path(target_path)
152
+ copy_to(cotta.dir(target_path))
153
+ end
154
+
155
+ # returns the content of this directory
156
+ # as an array of CottaFile and CottaDirectory
157
+ def list
158
+ factory.system.list(@path).collect do |item|
159
+ candidate = dir(item)
160
+ if (not candidate.exists?)
161
+ candidate = file(item)
162
+ end
163
+ candidate
164
+ end
165
+ end
166
+
167
+ def chdir(&block)
168
+ factory.system.chdir(@path, &block)
169
+ end
170
+
171
+ def ==(other)
172
+ return @path == other.path && factory.system == other.factory.system
173
+ end
174
+
175
+ def inspect
176
+ return "#{self.class}:#{self.object_id}-#@path"
177
+ end
178
+
179
+ def to_s
180
+ @path.to_s
181
+ end
182
+
183
+ end
184
+ end
@@ -0,0 +1,253 @@
1
+ require 'fileutils'
2
+
3
+ module Cotta
4
+ # This class represents a file
5
+ class CottaFile
6
+ # factory with the backing system
7
+ attr_reader :factory
8
+ # path of this file
9
+ attr_reader :path
10
+ # stats of this file
11
+ attr_reader :stat
12
+
13
+ # creates an instance of file with the given factory and path
14
+ def initialize(factory, path)
15
+ @path = path
16
+ @factory = factory
17
+ end
18
+
19
+ # name of this file
20
+ def name
21
+ return @path.basename.to_s
22
+ end
23
+
24
+ # extension of this file, with '.'
25
+ def extname
26
+ return @path.extname
27
+ end
28
+
29
+ # basename of this file
30
+ def basename
31
+ return @path.basename(extname).to_s
32
+ end
33
+
34
+ # stats of this file
35
+ def stat
36
+ factory.system.file_stat(@path)
37
+ end
38
+
39
+ # returns true if this file is older than the given file
40
+ def older_than?(file)
41
+ (stat <=> file.stat) == -1
42
+ end
43
+
44
+ # returns true if this file exists
45
+ def exists?
46
+ return factory.system.file_exists?(@path)
47
+ end
48
+
49
+ # returns the relative path from the given file or directory
50
+ def relative_path_from(entry)
51
+ path.relative_path_from(entry.path)
52
+ end
53
+
54
+ # returns the parent directory
55
+ def parent
56
+ return CottaDir.new(factory, @path.parent)
57
+ end
58
+
59
+ # copy this file to the target file
60
+ def copy_to(target_file)
61
+ target_file.parent.mkdirs
62
+ factory.system.copy_file(path, target_file.path)
63
+ target_file
64
+ end
65
+
66
+ # copy this file to the target path
67
+ def copy_to_path(target_path)
68
+ copy_to(cotta.file(target_path))
69
+ end
70
+
71
+ # move this file to the target file
72
+ def move_to(target_file)
73
+ target_file.parent.mkdirs
74
+ factory.system.move_file(path, target_file.path)
75
+ end
76
+
77
+ # move this file to the target path
78
+ def move_to_path(target_path)
79
+ move_to(cotta.file(target_path))
80
+ end
81
+
82
+ # save conent to this file
83
+ # this will create the parent directory if necessary
84
+ def save(content = '')
85
+ write {|file| file.printf content.to_s}
86
+ self
87
+ end
88
+
89
+ # Calls open with 'w' argument and makes sure that the
90
+ # parent directory of the file exists
91
+ def write(&block)
92
+ parent.mkdirs
93
+ open('w', &block)
94
+ end
95
+
96
+ # Calls open with 'a' argument and make sure that the
97
+ # parent directory of the file exists
98
+ def append(&block)
99
+ parent.mkdirs
100
+ open('a', &block)
101
+ end
102
+
103
+ # Calls open with 'wb' argument, sets the io to binmode
104
+ # and make sure that the parent directory of the file exists
105
+ def write_binary(&block)
106
+ parent.mkdirs
107
+ if (block_given?)
108
+ open('wb') do |io|
109
+ io.binmode
110
+ yield io
111
+ end
112
+ else
113
+ io = open('wb')
114
+ io.binmode
115
+ io
116
+ end
117
+ end
118
+
119
+ =begin rdoc
120
+ Loading the file full total. This is used generally for loading
121
+ an ascii file content. It does not work with binary file on
122
+ windows system because it does no put the system on binary mode
123
+ =end
124
+ def load
125
+ content = nil
126
+ size = stat.size
127
+ read do |io|
128
+ content = io.read
129
+ end
130
+ return content
131
+ end
132
+
133
+ # calls open with 'r' as argument.
134
+ def read(&block)
135
+ open('r', &block)
136
+ end
137
+
138
+ # Calls open with 'r' as argument and sets the io to binary mode
139
+ def read_binary
140
+ if block_given?
141
+ open('r') do |io|
142
+ io.binmode
143
+ yield io
144
+ end
145
+ else
146
+ io = open('r')
147
+ io.binmode
148
+ io
149
+ end
150
+ end
151
+
152
+ # reads the file and calls back for each line
153
+ def foreach()
154
+ open('r') do |file|
155
+ file.each {|line| yield line}
156
+ end
157
+ end
158
+
159
+ # opens the file with the g iven arguments
160
+ def open(*args)
161
+ result = f = factory.system.io(@path, *args)
162
+ if block_given?
163
+ begin
164
+ result = yield f
165
+ ensure
166
+ f.close unless f.closed?
167
+ end
168
+ end
169
+ result
170
+ end
171
+
172
+ # deletes the file
173
+ def delete
174
+ factory.system.delete_file(@path)
175
+ end
176
+
177
+ # reads this file as an archive and extracts the content
178
+ # to the target directory. If the target directory
179
+ # is missing, it will create a directory in the same
180
+ # directory as this file with the same basename
181
+ def extract(directory = nil)
182
+ require 'rubygems/package'
183
+ directory = parent.dir(basename) unless directory
184
+ read_binary do |io|
185
+ reader = Gem::Package::TarReader.new(io)
186
+ reader.each do |entry|
187
+ full_name = entry.full_name
188
+ if (entry.file?)
189
+ directory.file(full_name).write_binary do |output|
190
+ CottaFile.copy_io(entry, output)
191
+ end
192
+ elsif (entry.directory?)
193
+ directory.dir(full_name).mkdirs
194
+ end
195
+ end
196
+ end
197
+ directory
198
+ end
199
+
200
+ # Create the zip file from current file
201
+ # When target is nil, the output will be the name of the current file appended with ".zip"
202
+ def zip(target = nil, level=nil, strategy=nil)
203
+ target = parent.file("#{name}.zip") unless target
204
+ read_binary do |read_io|
205
+ target.write_binary do |write_io|
206
+ gz = Zlib::GzipWriter.new(write_io, level, strategy)
207
+ CottaFile.copy_io(read_io, gz)
208
+ gz.close
209
+ end
210
+ end
211
+ target
212
+ end
213
+
214
+ # unzips the file
215
+ def unzip
216
+ name = basename
217
+ if (extname.length == 0)
218
+ name = "#{name}.unzip"
219
+ end
220
+ target = parent.file(name)
221
+ read_binary do |read_io|
222
+ target.write_binary do |write_io|
223
+ gz = Zlib::GzipReader.new(read_io)
224
+ CottaFile.copy_io(gz, write_io)
225
+ gz.close
226
+ end
227
+ end
228
+ target
229
+ end
230
+
231
+ # copy from input handle to output handle
232
+ def self::copy_io(input, output)
233
+ # output.write input.read
234
+ while (content = input.read(1024)) do
235
+ output.write content
236
+ end
237
+ end
238
+
239
+ def ==(other)
240
+ return false unless other.kind_of? CottaFile
241
+ return factory.system == other.factory.system && @path == other.path
242
+ end
243
+
244
+ def to_s
245
+ @path.to_s
246
+ end
247
+
248
+ def inspect
249
+ return "#{self.class}:#{self.object_id}-#{factory.system.inspect}-#@path"
250
+ end
251
+
252
+ end
253
+ end