typingpool 0.7.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.markdown +452 -0
- data/lib/typingpool/amazon/hit/assignment/empty.rb +19 -0
- data/lib/typingpool/amazon/hit/assignment.rb +43 -0
- data/lib/typingpool/amazon/hit/full/fromsearchhits.rb +44 -0
- data/lib/typingpool/amazon/hit/full.rb +105 -0
- data/lib/typingpool/amazon/hit.rb +458 -0
- data/lib/typingpool/amazon/question.rb +45 -0
- data/lib/typingpool/amazon.rb +3 -677
- data/lib/typingpool/app/cli/formatter.rb +16 -0
- data/lib/typingpool/app/cli.rb +64 -0
- data/lib/typingpool/app/friendlyexceptions.rb +34 -0
- data/lib/typingpool/app.rb +2 -97
- data/lib/typingpool/config/root.rb +114 -0
- data/lib/typingpool/config.rb +13 -119
- data/lib/typingpool/filer/audio.rb +84 -0
- data/lib/typingpool/filer/csv.rb +57 -0
- data/lib/typingpool/filer/dir.rb +76 -0
- data/lib/typingpool/filer/files/audio.rb +63 -0
- data/lib/typingpool/filer/files.rb +55 -0
- data/lib/typingpool/filer.rb +4 -313
- data/lib/typingpool/project/local.rb +117 -0
- data/lib/typingpool/project/remote/s3.rb +135 -0
- data/lib/typingpool/project/remote/sftp.rb +100 -0
- data/lib/typingpool/project/remote.rb +65 -0
- data/lib/typingpool/project.rb +2 -396
- data/lib/typingpool/template/assignment.rb +17 -0
- data/lib/typingpool/template/env.rb +77 -0
- data/lib/typingpool/template.rb +2 -87
- data/lib/typingpool/test/script.rb +310 -0
- data/lib/typingpool/test.rb +1 -306
- data/lib/typingpool/transcript/chunk.rb +129 -0
- data/lib/typingpool/transcript.rb +1 -125
- data/lib/typingpool/utility/castable.rb +65 -0
- data/lib/typingpool/utility.rb +1 -61
- data/test/test_integration_script_6_tp_finish.rb +1 -0
- metadata +135 -81
@@ -0,0 +1,76 @@
|
|
1
|
+
module Typingpool
|
2
|
+
class Filer
|
3
|
+
|
4
|
+
#Convenience wrapper for basic directory operations and for
|
5
|
+
#casting files to specific filer types (CSV, Audio).
|
6
|
+
class Dir < Files
|
7
|
+
|
8
|
+
#Full expanded path to the dir
|
9
|
+
attr_reader :path
|
10
|
+
|
11
|
+
#Constructor. Takes full expanded path to the dir. Does NOT
|
12
|
+
#create dir in the filesystem.
|
13
|
+
def initialize(path)
|
14
|
+
@path = path
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
|
19
|
+
#Constructor. Takes full expanded path to the dir and creates
|
20
|
+
#the dir in the filesystem. Returns new Filer::Dir.
|
21
|
+
def create(path)
|
22
|
+
FileUtils.mkdir(path)
|
23
|
+
new(path)
|
24
|
+
end
|
25
|
+
|
26
|
+
#Constructor. Takes directory name and full expanded path of
|
27
|
+
#the parent directory. If the so-named directory exists within
|
28
|
+
#the parent directory, returns it. If not, returns nil.
|
29
|
+
def named(name, in_dir)
|
30
|
+
path = File.join(in_dir, name)
|
31
|
+
if File.exists?(path) && File.directory?(path)
|
32
|
+
new(path)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end #class << self
|
36
|
+
|
37
|
+
#Filer::Dir isntances stringify to their path.
|
38
|
+
def to_s
|
39
|
+
@path
|
40
|
+
end
|
41
|
+
alias :to_str :to_s
|
42
|
+
|
43
|
+
#Takes an aribtrary number of path elements relative to the
|
44
|
+
#Filer::Dir instance. So a file in the subdir path/to/file.txt
|
45
|
+
#would be referenced via file('path', 'to', 'file.txt'). Returns
|
46
|
+
#a new Filer instance wrapping the referenced file. Does not
|
47
|
+
#guarantee that the referenced file exists.
|
48
|
+
def file(*relative_path)
|
49
|
+
Filer.new(file_path(*relative_path))
|
50
|
+
end
|
51
|
+
|
52
|
+
#Returns the files in the Filer::Dir directory as Filer
|
53
|
+
#instances. Excludes files whose names start with a dot.
|
54
|
+
def files
|
55
|
+
::Dir.entries(@path).select{|entry| File.file? file_path(entry) }.reject{|entry| entry.match(/^\./) }.map{|entry| self.file(entry) }
|
56
|
+
end
|
57
|
+
|
58
|
+
#Takes relative path elements as params just like the file
|
59
|
+
#method. Returns a new Filer::Dir instance wrapping the
|
60
|
+
#referenced subdir.
|
61
|
+
def subdir(*relative_path)
|
62
|
+
Dir.new(file_path(*relative_path))
|
63
|
+
end
|
64
|
+
|
65
|
+
#OS X specific. Opens the dir in the Finder via the 'open' command.
|
66
|
+
def finder_open
|
67
|
+
system('open', @path)
|
68
|
+
end
|
69
|
+
|
70
|
+
def file_path(*relative_path)
|
71
|
+
File.join(@path, *relative_path)
|
72
|
+
end
|
73
|
+
|
74
|
+
end #Dir
|
75
|
+
end #Filer
|
76
|
+
end #Typingpool
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Typingpool
|
2
|
+
class Filer
|
3
|
+
class Files
|
4
|
+
|
5
|
+
#Handler for collection of Filer::Audio instances. Does
|
6
|
+
#everything Filer::Files does, plus can batch convert to mp3 an
|
7
|
+
#can merge the Filer::Audio instances into a single audio file,
|
8
|
+
#provided they are in mp3 format.
|
9
|
+
class Audio < Files
|
10
|
+
|
11
|
+
#Constructor. Takes an array of Filer or Filer subclass instances.
|
12
|
+
def initialize(files)
|
13
|
+
@files = files.map{|file| self.file(file.path) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def file(path)
|
17
|
+
Filer::Audio.new(path)
|
18
|
+
end
|
19
|
+
|
20
|
+
#Batch convert Filer::Audio instances to mp3 format.
|
21
|
+
# ==== Params
|
22
|
+
# [dest_dir] Filer::Dir instance corresponding to directory
|
23
|
+
# into which mp3 file versions will be created.
|
24
|
+
# [bitrate] See documentation for Filer::Audio#bitrate.
|
25
|
+
# ==== Returns
|
26
|
+
# Filer::Files::Audio instance corresponding to new mp3
|
27
|
+
# versions of the original files or, in the case where the
|
28
|
+
# original file was already in mp3 format, corresponding to
|
29
|
+
# the original files themselves.
|
30
|
+
def to_mp3(dest_dir, bitrate=nil)
|
31
|
+
mp3s = self.map do |file|
|
32
|
+
if file.mp3?
|
33
|
+
file
|
34
|
+
else
|
35
|
+
yield(file) if block_given?
|
36
|
+
file.to_mp3(dest_dir.file("#{File.basename(file.path, '.*') }.mp3"), bitrate)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
self.class.new(mp3s)
|
40
|
+
end
|
41
|
+
|
42
|
+
#Merge Filer::Audio instances into a single new file, provided
|
43
|
+
#they are all in mp3 format.
|
44
|
+
# ==== Params
|
45
|
+
#[into_file] Filer or Filer subclass instance corresponding to
|
46
|
+
#the location of the new, merged file that should be created.
|
47
|
+
# ==== Returns
|
48
|
+
# Filer::Audio instance corresponding to the new, merged file.
|
49
|
+
def merge(into_file)
|
50
|
+
raise Error::Argument, "No files to merge" if self.to_a.empty?
|
51
|
+
if self.count > 1
|
52
|
+
Utility.system_quietly('mp3wrap', into_file, *self.to_a)
|
53
|
+
written = File.join(into_file.dir, "#{File.basename(into_file.path, '.*') }_MP3WRAP.mp3")
|
54
|
+
FileUtils.mv(written, into_file)
|
55
|
+
else
|
56
|
+
FileUtils.cp(self.first, into_file)
|
57
|
+
end
|
58
|
+
self.file(into_file.path)
|
59
|
+
end
|
60
|
+
end #Audio
|
61
|
+
end #Files
|
62
|
+
end #Filer
|
63
|
+
end #Typingpool
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Typingpool
|
2
|
+
class Filer
|
3
|
+
|
4
|
+
#Handler for collection of Filer instances. Makes them enumerable,
|
5
|
+
#Allows easy re-casting to Filer::Files subclasses,
|
6
|
+
#and provides various other convenience methods.
|
7
|
+
class Files
|
8
|
+
include Enumerable
|
9
|
+
include Utility::Castable
|
10
|
+
require 'fileutils'
|
11
|
+
require 'typingpool/filer/files/audio'
|
12
|
+
|
13
|
+
#Array of Filer instances included in the collection
|
14
|
+
attr_reader :files
|
15
|
+
|
16
|
+
#Constructor. Takes array of Filer instances.
|
17
|
+
def initialize(files)
|
18
|
+
@files = files
|
19
|
+
end
|
20
|
+
|
21
|
+
#Enumerate through Filer instances.
|
22
|
+
def each
|
23
|
+
files.each do |file|
|
24
|
+
yield file
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
#Cast this collection into a new Filer::Files subtype,
|
29
|
+
#e.g. Filer::Files::Audio.
|
30
|
+
# ==== Params
|
31
|
+
# [sym] Symbol corresponding to Filer::Files subclass to cast
|
32
|
+
# into. For example, passing :audio will cast into a
|
33
|
+
# Filer::Files::Audio.
|
34
|
+
# ==== Returns
|
35
|
+
# Instance of new Filer::Files subclass
|
36
|
+
def as(sym)
|
37
|
+
#super calls into Utility::Castable mixin
|
38
|
+
super(sym, files)
|
39
|
+
end
|
40
|
+
|
41
|
+
#Returns array of IO streams created by calling to_stream on
|
42
|
+
#each Filer instance in the collection.
|
43
|
+
def to_streams
|
44
|
+
self.map{|file| file.to_stream }
|
45
|
+
end
|
46
|
+
|
47
|
+
#Calls mv! on each Filer instance in the collection. See
|
48
|
+
#documentation for Filer#mv! for definition of "to" param and
|
49
|
+
#for return value.
|
50
|
+
def mv!(to)
|
51
|
+
files.map{|file| file.mv! to }
|
52
|
+
end
|
53
|
+
end #Files
|
54
|
+
end #Filer
|
55
|
+
end #Typingpool
|
data/lib/typingpool/filer.rb
CHANGED
@@ -79,318 +79,9 @@ module Typingpool
|
|
79
79
|
#super calls into Utility::Castable mixin
|
80
80
|
super(sym, @path)
|
81
81
|
end
|
82
|
-
|
83
|
-
|
84
|
-
#Convenience wrapper for CSV files. Makes them Enumerable, so you
|
85
|
-
#can iterate through rows with each, map, select, etc. You can
|
86
|
-
#also modify in place with each!. See Filer base class for other
|
87
|
-
#methods.
|
88
|
-
class CSV < Filer
|
89
|
-
include Enumerable
|
90
|
-
require 'csv'
|
91
|
-
|
92
|
-
#Reads into an array of hashes, with hash keys determined by the
|
93
|
-
#first row of the CSV file. Parsing rules are the default for
|
94
|
-
#CSV.parse.
|
95
|
-
def read
|
96
|
-
raw = super or return []
|
97
|
-
rows = ::CSV.parse(raw.to_s)
|
98
|
-
headers = rows.shift or raise Error::File, "No CSV at #{@path}"
|
99
|
-
rows.map{|row| Utility.array_to_hash(row, headers) }
|
100
|
-
end
|
101
|
-
|
102
|
-
#Takes array of hashes followed by optional list of keys (by
|
103
|
-
#default keys are determined by looking at all the
|
104
|
-
#hashes). Lines are written per the defaults of
|
105
|
-
#CSV.generate_line.
|
106
|
-
def write(hashes, headers=hashes.map{|h| h.keys}.flatten.uniq)
|
107
|
-
super(
|
108
|
-
::CSV.generate_line(headers, :encoding => @encoding) +
|
109
|
-
hashes.map do |hash|
|
110
|
-
::CSV.generate_line(headers.map{|header| hash[header] }, :encoding => @encoding)
|
111
|
-
end.join
|
112
|
-
)
|
113
|
-
end
|
114
|
-
|
115
|
-
#Takes an array of arrays, corresponding to the rows, and a list
|
116
|
-
#of headers/keys to write at the top.
|
117
|
-
def write_arrays(arrays, headers)
|
118
|
-
write(arrays.map{|array| Utility.array_to_hash(array, headers) }, headers)
|
119
|
-
end
|
120
|
-
|
121
|
-
#Enumerate through the rows, with each row represented by a
|
122
|
-
#hash.
|
123
|
-
def each
|
124
|
-
read.each do |row|
|
125
|
-
yield row
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
#Same as each, but any changes to the rows will be written back
|
130
|
-
#out to the underlying CSV file.
|
131
|
-
def each!
|
132
|
-
#each_with_index doesn't return the array, so we have to use each
|
133
|
-
write(each{|hash| yield(hash) })
|
134
|
-
end
|
135
|
-
end #CSV
|
136
|
-
|
137
|
-
#Convenience wrapper for audio files.You can convert to mp3s,
|
138
|
-
#split into multiple files, and dynamically read the bitrate.
|
139
|
-
class Audio < Filer
|
140
|
-
require 'open3'
|
141
|
-
|
142
|
-
#Does the file have a '.mp3' extension?
|
143
|
-
def mp3?
|
144
|
-
File.extname(@path).downcase.eql?('.mp3')
|
145
|
-
end
|
146
|
-
|
147
|
-
#Convert to mp3 via ffmpeg.
|
148
|
-
# ==== Params
|
149
|
-
# [dest] Filer object corresponding to the path the mp3 version
|
150
|
-
# should end up at.
|
151
|
-
# [bitrate] If passed, bitrate should be an integer
|
152
|
-
# corresponding to kb/s. If not, we use the bitrate
|
153
|
-
# from the current file or, if that can't be read,
|
154
|
-
# default to 192kbps. Does not check if the file is
|
155
|
-
# already an mp3. Returns a new Filer::Audio
|
156
|
-
# representing the new mp3 file.
|
157
|
-
# ==== Returns
|
158
|
-
# Filer::Audio containing the new mp3.
|
159
|
-
def to_mp3(dest=self.dir.file("#{File.basename(@path, '.*') }.mp3"), bitrate=nil)
|
160
|
-
bitrate ||= self.bitrate || 192
|
161
|
-
Utility.system_quietly('ffmpeg', '-i', @path, '-acodec', 'libmp3lame', '-ab', "#{bitrate}k", '-ac', '2', dest)
|
162
|
-
File.exists?(dest) or raise Error::Shell, "Could not found output from `ffmpeg` on #{path}"
|
163
|
-
self.class.new(dest.path)
|
164
|
-
end
|
165
|
-
|
166
|
-
#Reads the bitrate of the audio file via ffmpeg. Returns an
|
167
|
-
#integer corresponding to kb/s, or nil if the bitrate could not
|
168
|
-
#be determined.
|
169
|
-
def bitrate
|
170
|
-
out, err, status = Open3.capture3('ffmpeg', '-i', @path)
|
171
|
-
bitrate = err.match(/(\d+) kb\/s/)
|
172
|
-
return bitrate ? bitrate[1].to_i : nil
|
173
|
-
end
|
174
|
-
|
175
|
-
#Splits an mp3 into smaller files.
|
176
|
-
# ==== Params
|
177
|
-
# [interval_in_min_dot_seconds] Split the file into chunks this
|
178
|
-
# large. The interval should be of the format
|
179
|
-
# minute.seconds, for example 2 minutes 15 seconds
|
180
|
-
# would be written as "2.15". For further details on
|
181
|
-
# interval format, consult the documentation for
|
182
|
-
# mp3split, a command-line unix utility.
|
183
|
-
# [basename] Name the new chunks using this base. Default is the
|
184
|
-
# basename of the original file.
|
185
|
-
# [dest] Destination directory for the new chunks as a
|
186
|
-
# Filer::Dir. Default is the same directory as the
|
187
|
-
# original file.
|
188
|
-
# ==== Returns
|
189
|
-
# Filer::Files containing the new files.
|
190
|
-
def split(interval_in_min_dot_seconds, basename=File.basename(path, '.*'), dest=dir)
|
191
|
-
#We have to cd into the wrapfile directory and do everything
|
192
|
-
#there because old/packaged versions of mp3splt were
|
193
|
-
#retarded at handling absolute directory paths
|
194
|
-
::Dir.chdir(dir.path) do
|
195
|
-
Utility.system_quietly('mp3splt', '-t', interval_in_min_dot_seconds, '-o', "#{basename}.@m.@s", File.basename(path))
|
196
|
-
end
|
197
|
-
files = Filer::Files::Audio.new(dir.select{|file| File.basename(file.path).match(/^#{Regexp.escape(basename) }\.\d+\.\d+\.mp3$/) })
|
198
|
-
if files.to_a.empty?
|
199
|
-
raise Error::Shell, "Could not find output from `mp3splt` on #{path}"
|
200
|
-
end
|
201
|
-
if dest.path != dir.path
|
202
|
-
files.mv!(dest)
|
203
|
-
end
|
204
|
-
files.sort
|
205
|
-
end
|
206
|
-
|
207
|
-
#Extracts from the filename the offset time of the chunk
|
208
|
-
#relative to the original from which it was split. Format is
|
209
|
-
#minute.seconds. Suitable for use on files created by 'split'
|
210
|
-
#method.
|
211
|
-
def offset
|
212
|
-
match = File.basename(@path).match(/\d+\.\d\d\b/)
|
213
|
-
return match[0] if match
|
214
|
-
end
|
215
|
-
end #Audio
|
216
|
-
|
217
|
-
#Handler for collection of Filer instances. Makes them enumerable,
|
218
|
-
#Allows easy re-casting to Filer::Files subclasses,
|
219
|
-
#and provides various other convenience methods.
|
220
|
-
class Files
|
221
|
-
include Enumerable
|
222
|
-
include Utility::Castable
|
223
|
-
require 'fileutils'
|
224
|
-
|
225
|
-
#Array of Filer instances included in the collection
|
226
|
-
attr_reader :files
|
227
|
-
|
228
|
-
#Constructor. Takes array of Filer instances.
|
229
|
-
def initialize(files)
|
230
|
-
@files = files
|
231
|
-
end
|
232
|
-
|
233
|
-
#Enumerate through Filer instances.
|
234
|
-
def each
|
235
|
-
files.each do |file|
|
236
|
-
yield file
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
#Cast this collection into a new Filer::Files subtype,
|
241
|
-
#e.g. Filer::Files::Audio.
|
242
|
-
# ==== Params
|
243
|
-
# [sym] Symbol corresponding to Filer::Files subclass to cast
|
244
|
-
# into. For example, passing :audio will cast into a
|
245
|
-
# Filer::Files::Audio.
|
246
|
-
# ==== Returns
|
247
|
-
# Instance of new Filer::Files subclass
|
248
|
-
def as(sym)
|
249
|
-
#super calls into Utility::Castable mixin
|
250
|
-
super(sym, files)
|
251
|
-
end
|
252
|
-
|
253
|
-
#Returns array of IO streams created by calling to_stream on
|
254
|
-
#each Filer instance in the collection.
|
255
|
-
def to_streams
|
256
|
-
self.map{|file| file.to_stream }
|
257
|
-
end
|
258
|
-
|
259
|
-
#Calls mv! on each Filer instance in the collection. See
|
260
|
-
#documentation for Filer#mv! for definition of "to" param and
|
261
|
-
#for return value.
|
262
|
-
def mv!(to)
|
263
|
-
files.map{|file| file.mv! to }
|
264
|
-
end
|
265
|
-
|
266
|
-
#Handler for collection of Filer::Audio instances. Does
|
267
|
-
#everything Filer::Files does, plus can batch convert to mp3 an
|
268
|
-
#can merge the Filer::Audio instances into a single audio file,
|
269
|
-
#provided they are in mp3 format.
|
270
|
-
class Audio < Files
|
271
|
-
|
272
|
-
#Constructor. Takes an array of Filer or Filer subclass instances.
|
273
|
-
def initialize(files)
|
274
|
-
@files = files.map{|file| self.file(file.path) }
|
275
|
-
end
|
276
|
-
|
277
|
-
def file(path)
|
278
|
-
Filer::Audio.new(path)
|
279
|
-
end
|
280
|
-
|
281
|
-
#Batch convert Filer::Audio instances to mp3 format.
|
282
|
-
# ==== Params
|
283
|
-
# [dest_dir] Filer::Dir instance corresponding to directory
|
284
|
-
# into which mp3 file versions will be created.
|
285
|
-
# [bitrate] See documentation for Filer::Audio#bitrate.
|
286
|
-
# ==== Returns
|
287
|
-
# Filer::Files::Audio instance corresponding to new mp3
|
288
|
-
# versions of the original files or, in the case where the
|
289
|
-
# original file was already in mp3 format, corresponding to
|
290
|
-
# the original files themselves.
|
291
|
-
def to_mp3(dest_dir, bitrate=nil)
|
292
|
-
mp3s = self.map do |file|
|
293
|
-
if file.mp3?
|
294
|
-
file
|
295
|
-
else
|
296
|
-
yield(file) if block_given?
|
297
|
-
file.to_mp3(dest_dir.file("#{File.basename(file.path, '.*') }.mp3"), bitrate)
|
298
|
-
end
|
299
|
-
end
|
300
|
-
self.class.new(mp3s)
|
301
|
-
end
|
302
|
-
|
303
|
-
#Merge Filer::Audio instances into a single new file, provided
|
304
|
-
#they are all in mp3 format.
|
305
|
-
# ==== Params
|
306
|
-
#[into_file] Filer or Filer subclass instance corresponding to
|
307
|
-
#the location of the new, merged file that should be created.
|
308
|
-
# ==== Returns
|
309
|
-
# Filer::Audio instance corresponding to the new, merged file.
|
310
|
-
def merge(into_file)
|
311
|
-
raise Error::Argument, "No files to merge" if self.to_a.empty?
|
312
|
-
if self.count > 1
|
313
|
-
Utility.system_quietly('mp3wrap', into_file, *self.to_a)
|
314
|
-
written = File.join(into_file.dir, "#{File.basename(into_file.path, '.*') }_MP3WRAP.mp3")
|
315
|
-
FileUtils.mv(written, into_file)
|
316
|
-
else
|
317
|
-
FileUtils.cp(self.first, into_file)
|
318
|
-
end
|
319
|
-
self.file(into_file.path)
|
320
|
-
end
|
321
|
-
end #Audio
|
322
|
-
end #Files
|
323
|
-
|
324
|
-
#Convenience wrapper for basic directory operations and for
|
325
|
-
#casting files to specific filer types (CSV, Audio).
|
326
|
-
class Dir < Files
|
327
|
-
|
328
|
-
#Full expanded path to the dir
|
329
|
-
attr_reader :path
|
330
|
-
|
331
|
-
#Constructor. Takes full expanded path to the dir. Does NOT
|
332
|
-
#create dir in the filesystem.
|
333
|
-
def initialize(path)
|
334
|
-
@path = path
|
335
|
-
end
|
336
|
-
|
337
|
-
class << self
|
338
|
-
|
339
|
-
#Constructor. Takes full expanded path to the dir and creates
|
340
|
-
#the dir in the filesystem. Returns new Filer::Dir.
|
341
|
-
def create(path)
|
342
|
-
FileUtils.mkdir(path)
|
343
|
-
new(path)
|
344
|
-
end
|
345
|
-
|
346
|
-
#Constructor. Takes directory name and full expanded path of
|
347
|
-
#the parent directory. If the so-named directory exists within
|
348
|
-
#the parent directory, returns it. If not, returns nil.
|
349
|
-
def named(name, in_dir)
|
350
|
-
path = File.join(in_dir, name)
|
351
|
-
if File.exists?(path) && File.directory?(path)
|
352
|
-
new(path)
|
353
|
-
end
|
354
|
-
end
|
355
|
-
end #class << self
|
356
|
-
|
357
|
-
#Filer::Dir isntances stringify to their path.
|
358
|
-
def to_s
|
359
|
-
@path
|
360
|
-
end
|
361
|
-
alias :to_str :to_s
|
362
|
-
|
363
|
-
#Takes an aribtrary number of path elements relative to the
|
364
|
-
#Filer::Dir instance. So a file in the subdir path/to/file.txt
|
365
|
-
#would be referenced via file('path', 'to', 'file.txt'). Returns
|
366
|
-
#a new Filer instance wrapping the referenced file. Does not
|
367
|
-
#guarantee that the referenced file exists.
|
368
|
-
def file(*relative_path)
|
369
|
-
Filer.new(file_path(*relative_path))
|
370
|
-
end
|
371
|
-
|
372
|
-
#Returns the files in the Filer::Dir directory as Filer
|
373
|
-
#instances. Excludes files whose names start with a dot.
|
374
|
-
def files
|
375
|
-
::Dir.entries(@path).select{|entry| File.file? file_path(entry) }.reject{|entry| entry.match(/^\./) }.map{|entry| self.file(entry) }
|
376
|
-
end
|
377
|
-
|
378
|
-
#Takes relative path elements as params just like the file
|
379
|
-
#method. Returns a new Filer::Dir instance wrapping the
|
380
|
-
#referenced subdir.
|
381
|
-
def subdir(*relative_path)
|
382
|
-
Dir.new(file_path(*relative_path))
|
383
|
-
end
|
384
|
-
|
385
|
-
#OS X specific. Opens the dir in the Finder via the 'open' command.
|
386
|
-
def finder_open
|
387
|
-
system('open', @path)
|
388
|
-
end
|
389
|
-
|
390
|
-
def file_path(*relative_path)
|
391
|
-
File.join(@path, *relative_path)
|
392
|
-
end
|
393
|
-
|
394
|
-
end #Dir
|
395
82
|
end #Filer
|
83
|
+
require 'typingpool/filer/csv'
|
84
|
+
require 'typingpool/filer/audio'
|
85
|
+
require 'typingpool/filer/files'
|
86
|
+
require 'typingpool/filer/dir'
|
396
87
|
end #Typingpool
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Typingpool
|
2
|
+
class Project
|
3
|
+
#Representation of the Project instance in the local
|
4
|
+
#filesystem. Subclass of Filer::Dir; see Filer::Dir docs for
|
5
|
+
#additional details.
|
6
|
+
#
|
7
|
+
#This is basically a local dir with various subdirs and files
|
8
|
+
#containing the canonical representation of the project, including
|
9
|
+
#data on remote resources, the project ID and subtitle, the audio files
|
10
|
+
#themselves, and, when complete, an HTML transcript of that audio,
|
11
|
+
#along with supporting CSS and Javascript files.
|
12
|
+
class Local < Filer::Dir
|
13
|
+
require 'fileutils'
|
14
|
+
require 'securerandom'
|
15
|
+
|
16
|
+
#Returns the dir path.
|
17
|
+
attr_reader :path
|
18
|
+
|
19
|
+
class << self
|
20
|
+
#Constructor. Creates a directory in the filesystem for the
|
21
|
+
#project.
|
22
|
+
#
|
23
|
+
# ==== Params
|
24
|
+
# [name] Name of the associated project.
|
25
|
+
# [base_dir] Path to the local directory into which the project
|
26
|
+
# dir should be placed.
|
27
|
+
# [template_dir] Path to the dir which will be used as a base
|
28
|
+
# template for new projects.
|
29
|
+
# ==== Returns
|
30
|
+
# Project::Local instance.
|
31
|
+
def create(name, base_dir, template_dir)
|
32
|
+
local = super(File.join(base_dir, name))
|
33
|
+
FileUtils.cp_r(File.join(template_dir, '.'), local)
|
34
|
+
local.create_id
|
35
|
+
local
|
36
|
+
end
|
37
|
+
|
38
|
+
#Takes the name of a project and a path. If there's a
|
39
|
+
#directory with a matching name in the given path whose file
|
40
|
+
#layout indicates it is a Project::Local instance (see 'ours?'
|
41
|
+
#docs), returns a corresponding Project::Local instance.
|
42
|
+
def named(string, path)
|
43
|
+
match = super
|
44
|
+
if match && ours?(match)
|
45
|
+
return match
|
46
|
+
end
|
47
|
+
return
|
48
|
+
end
|
49
|
+
|
50
|
+
#Takes a Filer::Dir instance. Returns true or false depending on whether
|
51
|
+
#the file layout inside the dir indicates it is a
|
52
|
+
#Project::Local instance.
|
53
|
+
def ours?(dir)
|
54
|
+
File.exists?(dir.subdir('audio')) && File.exists?(dir.subdir('audio', 'originals'))
|
55
|
+
end
|
56
|
+
|
57
|
+
#Takes the name of a project and returns true if it is a valid
|
58
|
+
#name for a directory in the local filesystem, false if not.
|
59
|
+
def valid_name?(name)
|
60
|
+
Utility.in_temp_dir do |dir|
|
61
|
+
begin
|
62
|
+
FileUtils.mkdir(File.join(dir, name))
|
63
|
+
rescue Errno::ENOENT
|
64
|
+
return false
|
65
|
+
end #begin
|
66
|
+
return File.exists?(File.join(dir, name))
|
67
|
+
end #Utility.in_temp_dir do...
|
68
|
+
end
|
69
|
+
|
70
|
+
#Takes one or more symbols. Adds corresponding getter/setter
|
71
|
+
#and delete method(s) to Project::Local, which read (getter)
|
72
|
+
#and write (setter) and delete corresponding text files in the
|
73
|
+
#data directory.
|
74
|
+
#
|
75
|
+
#So, for example, 'data_file_accessor :name' would allow you
|
76
|
+
#to later create the file 'data/foo.txt' in the project dir by
|
77
|
+
#calling 'project.local.name = "Foo"', read that same file via
|
78
|
+
#'project.local.name', and delete the file via
|
79
|
+
#'project.local.delete_name'
|
80
|
+
def data_file_accessor(*syms)
|
81
|
+
syms.each do |sym|
|
82
|
+
define_method(sym) do
|
83
|
+
file('data',"#{sym.to_s}.txt").read
|
84
|
+
end
|
85
|
+
define_method("#{sym.to_s}=".to_sym) do |value|
|
86
|
+
file('data',"#{sym.to_s}.txt").write(value)
|
87
|
+
end
|
88
|
+
define_method("delete_#{sym.to_s}".to_sym) do
|
89
|
+
if File.exists? file('data',"#{sym.to_s}.txt")
|
90
|
+
File.delete(file('data',"#{sym.to_s}.txt"))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end #class << self
|
96
|
+
|
97
|
+
#Calling 'subtitle' will read 'data/subtitle.txt'; calling
|
98
|
+
#'subtitle=' will write 'data/subtitle.txt'; calling
|
99
|
+
#'delete_subtitle' will delete 'data/subtitle.txt'.
|
100
|
+
data_file_accessor :subtitle
|
101
|
+
|
102
|
+
#Returns the ID of the project, as stored in 'data/id.txt'.
|
103
|
+
def id
|
104
|
+
file('data','id.txt').read
|
105
|
+
end
|
106
|
+
|
107
|
+
#Creates a file storing the canonical ID of the project in
|
108
|
+
#'data/id.txt'. Raises an exception if the file already exists.
|
109
|
+
def create_id
|
110
|
+
if id
|
111
|
+
raise Error, "id already exists"
|
112
|
+
end
|
113
|
+
file('data','id.txt').write(SecureRandom.hex(16))
|
114
|
+
end
|
115
|
+
end #Local
|
116
|
+
end #Project
|
117
|
+
end #Typingpool
|