typingpool 0.7.0 → 0.7.1
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/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
|