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.
@@ -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