cotta 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,117 @@
1
+ module Cotta
2
+ # The file factory of Cotta files that handles creation of the CottaFile and CottaDir
3
+ # instances. This class also can be used to start command lines
4
+ class FileFactory
5
+ # file system that is backing the current file factory
6
+ attr_reader :system
7
+ # attr_accessor :command_interface
8
+
9
+ # Creates an instance of the file factory with the given system
10
+ def initialize(system=PhysicalSystem.new)
11
+ @system = system
12
+ # @command_interface = CommandInterface.new
13
+ end
14
+
15
+ =begin not for 1.0 release
16
+ # Invoke the command line through the backing system
17
+ def shell(command_line, &block)
18
+ @system.shell(command_line, &block)
19
+ end
20
+ =end
21
+ # Returns the current directory
22
+ def pwd
23
+ dir(@system.pwd)
24
+ end
25
+
26
+ =begin not for 1.0 release
27
+ # Starts the process. Unlike shell method, this method does not
28
+ # collect the output, thus suitable for starting a server in Ruby
29
+ # and leave it running for a long time
30
+ def start(command_line)
31
+ @system.shell(command_line) do |io|
32
+ if (block_given?)
33
+ yield io
34
+ else
35
+ while (line = io.gets)
36
+ puts line
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ def command_interface=(value)
43
+ if (value)
44
+ @command_interface = value
45
+ else
46
+ @command_interface = CommandInterface.new
47
+ end
48
+ end
49
+ =end
50
+
51
+ # Returns a CottaDir instance with the given path
52
+ def dir(path)
53
+ return nil unless path
54
+ return CottaDir.new(self, Pathname.new(path))
55
+ end
56
+
57
+ # Returns the FileFactory instance that represents the
58
+ # physical file system
59
+ def self::physical
60
+ PHYSICAL
61
+ end
62
+
63
+ # Return the FileFactory instance that represents an
64
+ # in-memory file system
65
+ def self::in_memory
66
+ FileFactory.new(InMemorySystem.new)
67
+ end
68
+
69
+ # Returns a CottDirectory with the PhysicalSystem
70
+ def self::dir(path)
71
+ return nil unless path
72
+ return physical.dir(File.expand_path(path))
73
+ end
74
+
75
+ # Returns a CottaFile in the current system with the path
76
+ def file(path)
77
+ return nil unless path
78
+ return CottaFile.new(self, Pathname.new(path))
79
+ end
80
+
81
+ # Returns a CottaFile with the PhysicalSystem and the given path
82
+ def self::file(path)
83
+ return nil unless path
84
+ return physical.file(File.expand_path(path))
85
+ end
86
+
87
+ # Returns a CottaDir with the PhysicalSystem and is the parent of the given file path
88
+ def self::parent_dir(path)
89
+ return file(path).parent
90
+ end
91
+
92
+ # Creates the entry given a path. This will return either
93
+ # CottaFile or CottaDirectory depending by checking the path
94
+ # passed in, which means that if neither a directory nor a file
95
+ # exists with this name it will raise an error
96
+ def entry(path)
97
+ entry_instance = file(path)
98
+ unless (entry_instance.exists?)
99
+ entry_instance = dir(path)
100
+ raise "#{path} exists as niether file nor directory" unless entry_instance.exists?
101
+ end
102
+ return entry_instance
103
+ end
104
+
105
+ =begin not for release 1.0
106
+ def environment!(variable)
107
+ @system.environment!(variable)
108
+ end
109
+
110
+ def environment(variable, default = '')
111
+ @system.environment(variable, default)
112
+ end
113
+ =end
114
+
115
+ end
116
+ PHYSICAL = FileFactory.new(PhysicalSystem.new)
117
+ end
@@ -0,0 +1,13 @@
1
+ module Cotta
2
+ class FileNotFoundError < StandardError
3
+ attr_reader :pathname
4
+
5
+ def initialize(pathname)
6
+ @pathname = pathname
7
+ end
8
+
9
+ def message
10
+ "file not found: #{pathname}"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Cotta
2
+ # Error class that will be thrown if
3
+ # a system command exit with an error
4
+ # code
5
+ class CommandError < StandardError
6
+ attr_reader :proc_status, :output
7
+
8
+ def initialize(proc_status, output)
9
+ @proc_status = proc_status
10
+ @output = output
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,46 @@
1
+ module Cotta
2
+ # A command line interface tha can
3
+ # be stubbed out by InMemorySystem
4
+ class CommandInterface
5
+ def initialize(io = SystemIo.new)
6
+ @io = io
7
+ end
8
+
9
+ def puts(content)
10
+ @io.puts(content)
11
+ end
12
+
13
+ def prompt(question)
14
+ puts(question)
15
+ gets
16
+ end
17
+
18
+ def prompt_for_choice(question, candidates)
19
+ puts(question)
20
+ 0.upto(candidates.size - 1) do |index|
21
+ puts "[#{index + 1}] #{candidates[index]}"
22
+ end
23
+ answer = gets.to_i
24
+ seletion = nil
25
+ if answer > 0 && answer <= candidates.size
26
+ selection = candidates[answer - 1]
27
+ end
28
+ return selection
29
+ end
30
+
31
+ def gets
32
+ @io.gets
33
+ end
34
+
35
+ end
36
+
37
+ class SystemIo
38
+ def puts(content)
39
+ $stdout.puts(content)
40
+ end
41
+
42
+ def gets
43
+ $stdin.gets
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,55 @@
1
+ =begin
2
+ if RUBY_PLATFORM =~ /mswin|mingw/
3
+ begin
4
+ require 'win32/open3'
5
+ rescue LoadError
6
+ warn "You must 'gem install win32-open3' to use Cotta on Windows"
7
+ exit 1
8
+ end
9
+ else
10
+ require 'open3'
11
+ end
12
+ =end
13
+
14
+ module Cotta
15
+
16
+ # Command runner
17
+ class CommandRunner
18
+ attr_reader :outputs
19
+
20
+ def initialize(command)
21
+ @command = command
22
+ end
23
+
24
+ # executs the command. If a closure is
25
+ # given, it will be called with the io to the process
26
+ # If not, it will print out the log pre-fixed with a random number for the process,
27
+ # collects the output and return the output when the process finishes.
28
+ # This method will also raise CommandError if the process fails.
29
+ def execute
30
+ id = rand(10000)
31
+ puts "[#{id}]$> #{@command}"
32
+ output = nil
33
+ IO.popen(@command) do |io|
34
+ if (block_given?)
35
+ output = yield io
36
+ else
37
+ output = load_output(id, io)
38
+ end
39
+ end
40
+ last_process = $?
41
+ raise CommandError.new(last_process, output), @command unless last_process.exitstatus == 0
42
+ return output
43
+ end
44
+
45
+ private
46
+ def load_output(id, io)
47
+ output = ''
48
+ while (line = io.gets) do
49
+ puts "[#{id}] #{line}"
50
+ output << line
51
+ end
52
+ return output
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,9 @@
1
+ class Pathname
2
+ def cotta_parent
3
+ parent_path = parent
4
+ if (parent_path == self || parent_path.to_s == '..')
5
+ return nil
6
+ end
7
+ return parent_path
8
+ end
9
+ end
@@ -0,0 +1,295 @@
1
+ require 'stringio'
2
+
3
+ module Cotta
4
+ class InMemorySystem
5
+
6
+ attr_reader :executed_commands, :pwd
7
+
8
+ def initialize
9
+ @executed_commands = []
10
+ @content = ""
11
+ @file_system = Hash.new
12
+ @file_system[nil] = DirectoryContent.new('')
13
+ @file_system[Pathname.new('/')] = DirectoryContent.new('/')
14
+ @file_system[Pathname.new('.')] = DirectoryContent.new('.')
15
+ @output_map = Hash.new
16
+ end
17
+
18
+ def shell(command)
19
+ @executed_commands.push(command)
20
+ result = @output_map[command]
21
+ raise "#{command} not found in expectation" unless result
22
+ return result
23
+ end
24
+
25
+ def output_for_command(command, output)
26
+ @output_map[command] = output
27
+ end
28
+
29
+ def expect_command(command)
30
+ Entry.new(command)
31
+ end
32
+
33
+ def dir_exists?(pathname)
34
+ content = path_content(pathname)
35
+ return !content.nil? && content.directory?
36
+ end
37
+
38
+ def file_exists?(pathname)
39
+ content = path_content(pathname)
40
+ return !content.nil? && content.file?
41
+ end
42
+
43
+ def dir_stat(pathname)
44
+ check_dir_exists(pathname)
45
+ path_content(pathname).stat
46
+ end
47
+
48
+ def file_stat(pathname)
49
+ check_file_exists(pathname)
50
+ path_content(pathname).stat
51
+ end
52
+
53
+ def list(pathname)
54
+ check_dir_exists(pathname)
55
+ content = path_content(pathname)
56
+ return content.children.collect {|item| item.name}
57
+ end
58
+
59
+ def check_dir_exists(pathname)
60
+ raise Errno::ENOENT, pathname unless dir_exists? pathname
61
+ end
62
+
63
+ def check_file_exists(pathname)
64
+ raise Errno::ENOENT, pathname unless file_exists? pathname
65
+ end
66
+
67
+ def mkdir(pathname)
68
+ path_content!(pathname.cotta_parent).add(create_dir(pathname))
69
+ end
70
+
71
+ def io(*args)
72
+ file_content = retrieve_file_content(args[0], args[1])
73
+ return StringIO.new(file_content.content, *args[1, args.size - 1])
74
+ end
75
+
76
+ def copy(source, target)
77
+ copy_file(source, target)
78
+ end
79
+
80
+ def copy_file(source, target)
81
+ file_content = retrieve_file_content(source, 'r').content
82
+ create_file(target).content = file_content.clone
83
+ end
84
+
85
+ def move(source, target)
86
+ move_file(source, target)
87
+ end
88
+
89
+ def move_file(source, target)
90
+ copy(source, target)
91
+ delete_file(source)
92
+ end
93
+
94
+ def copy_dir(source, target)
95
+ check_dir_exists(source)
96
+ mkdir(target)
97
+ path_content(source).children.each do |item|
98
+ item.copy_to_dir(self, source, target)
99
+ end
100
+ end
101
+
102
+ def move_dir(source, target)
103
+ copy_dir(source, target)
104
+ delete_dir(source)
105
+ end
106
+
107
+ def delete_file(pathname)
108
+ raise Errno::ENOENT.new(pathname) unless file_exists? pathname
109
+ delete_entry(pathname)
110
+ end
111
+
112
+ def delete_dir(pathname)
113
+ raise Errno::ENOENT.new(pathname) unless dir_exists? pathname
114
+ delete_entry(pathname)
115
+ end
116
+
117
+ def to_s
118
+ return 'InMemorySystem'
119
+ end
120
+
121
+ def chdir(path)
122
+ last_pwd = @pwd
123
+ @pwd = path.to_s
124
+ result = 0
125
+ if (block_given?)
126
+ begin
127
+ result = yield
128
+ ensure
129
+ @pwd = last_pwd
130
+ end
131
+ end
132
+ result
133
+ end
134
+
135
+ private
136
+ def gather_paths_to_create(pathname)
137
+ paths = Array.new
138
+ path_to_create = pathname
139
+ while (! dir_exists?(path_to_create))
140
+ paths.push(path_to_create)
141
+ path_to_create = path_to_create.cotta_parent
142
+ end
143
+ return paths
144
+ end
145
+
146
+ def create_dir(pathname)
147
+ content = DirectoryContent.new(pathname.basename.to_s)
148
+ @file_system[pathname] = content
149
+ return content
150
+ end
151
+
152
+ def create_file(pathname)
153
+ content = FileContent.new(pathname.basename.to_s)
154
+ parent_dir = pathname.cotta_parent
155
+ path_content(parent_dir).add(content)
156
+ @file_system[pathname] = content
157
+ return content
158
+ end
159
+
160
+ def path_content(pathname)
161
+ content = path_content!(pathname)
162
+ if (content.nil? && pathname.cotta_parent.nil?)
163
+ mkdir(pathname)
164
+ content = @file_system[pathname]
165
+ end
166
+ return content
167
+ end
168
+
169
+ def path_content!(pathname)
170
+ @file_system[pathname]
171
+ end
172
+
173
+ def retrieve_file_content(pathname, options)
174
+ file_content = path_content(pathname)
175
+ if (file_content.nil?)
176
+ if (options =~ /r/)
177
+ raise Errno::ENOENT.new(pathname)
178
+ end
179
+ file_content = create_file(pathname)
180
+ end
181
+ if (options =~ /w/)
182
+ file_content.touch
183
+ end
184
+ return file_content
185
+ end
186
+
187
+ def delete_entry(pathname)
188
+ @file_system.delete pathname
189
+ @file_system[pathname.cotta_parent].delete(pathname.basename.to_s)
190
+ end
191
+
192
+ end
193
+
194
+ class DirectoryContent
195
+ attr_reader :name, :children, :stat
196
+
197
+ def initialize(name)
198
+ @name = name
199
+ @children = Array.new
200
+ @stat = ContentStat.new(self)
201
+ end
202
+
203
+ def file?
204
+ return false
205
+ end
206
+
207
+ def directory?
208
+ return true
209
+ end
210
+
211
+ def add(content)
212
+ @children.push(content)
213
+ end
214
+
215
+ def delete(name)
216
+ @children.delete_if {|file_content| file_content.name == name}
217
+ end
218
+
219
+ def copy_to_dir(system, parent_dir, target_dir)
220
+ source_path = parent_dir.join(name)
221
+ target_path = target_dir.join(name)
222
+ system.copy_dir(source_path, target_path)
223
+ end
224
+ end
225
+
226
+ class FileContent
227
+ attr_reader :name, :content, :stat
228
+ attr_writer :content
229
+
230
+ def initialize(name)
231
+ @name = name
232
+ @content = ''
233
+ @stat = ContentStat.new(self)
234
+ end
235
+
236
+ def file?
237
+ return true
238
+ end
239
+
240
+ def directory?
241
+ return false
242
+ end
243
+
244
+ def size
245
+ content.size
246
+ end
247
+
248
+ def copy_to_dir(system, parent_dir, target_dir)
249
+ target_path = target_dir.join(name)
250
+ source_path = parent_dir.join(name)
251
+ system.copy_file(source_path, target_path)
252
+ end
253
+
254
+ def touch
255
+ @stat.touch
256
+ end
257
+ end
258
+
259
+ class ContentStat
260
+ attr_reader :mtime
261
+
262
+ def initialize(content)
263
+ @content = content
264
+ @mtime = Time.new
265
+ end
266
+
267
+ def mode
268
+ '10777'
269
+ end
270
+
271
+ def size
272
+ @content.size
273
+ end
274
+
275
+ def file?
276
+ @content.file?
277
+ end
278
+
279
+ def directory?
280
+ @content.directory?
281
+ end
282
+
283
+ def touch
284
+ @mtime = Time.new
285
+ end
286
+
287
+ def writable?
288
+ true
289
+ end
290
+
291
+ def <=> stat
292
+ mtime <=> stat.mtime
293
+ end
294
+ end
295
+ end