typingpool 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +23 -0
- data/bin/tp-assign +240 -0
- data/bin/tp-collect +50 -0
- data/bin/tp-config +114 -0
- data/bin/tp-finish +101 -0
- data/bin/tp-make +169 -0
- data/bin/tp-review +175 -0
- data/lib/typingpool/amazon.rb +732 -0
- data/lib/typingpool/app.rb +634 -0
- data/lib/typingpool/config.rb +344 -0
- data/lib/typingpool/error.rb +22 -0
- data/lib/typingpool/filer.rb +396 -0
- data/lib/typingpool/project.rb +593 -0
- data/lib/typingpool/template.rb +175 -0
- data/lib/typingpool/templates/assignment/amazon-init.js +38 -0
- data/lib/typingpool/templates/assignment/interview/nameless.html.erb +13 -0
- data/lib/typingpool/templates/assignment/interview/noisy.html.erb +12 -0
- data/lib/typingpool/templates/assignment/interview/partials/voices.html.erb +10 -0
- data/lib/typingpool/templates/assignment/interview/phone.html.erb +12 -0
- data/lib/typingpool/templates/assignment/interview.html.erb +11 -0
- data/lib/typingpool/templates/assignment/main.css +20 -0
- data/lib/typingpool/templates/assignment/partials/entry.html.erb +19 -0
- data/lib/typingpool/templates/assignment/partials/footer.html.erb +3 -0
- data/lib/typingpool/templates/assignment/partials/header.html.erb +11 -0
- data/lib/typingpool/templates/assignment/partials/labeling-example.html.erb +4 -0
- data/lib/typingpool/templates/assignment/partials/labeling.html.erb +5 -0
- data/lib/typingpool/templates/assignment/partials/length-description.html.erb +6 -0
- data/lib/typingpool/templates/assignment/partials/voices.html.erb +10 -0
- data/lib/typingpool/templates/assignment/speech.html.erb +11 -0
- data/lib/typingpool/templates/config.yml +21 -0
- data/lib/typingpool/templates/project/audio/chunks/.empty_directory +0 -0
- data/lib/typingpool/templates/project/audio/originals/.empty_directory +0 -0
- data/lib/typingpool/templates/project/data/.empty_directory +0 -0
- data/lib/typingpool/templates/project/etc/ About these files - read me.txt +8 -0
- data/lib/typingpool/templates/project/etc/audio-compat.js +25 -0
- data/lib/typingpool/templates/project/etc/player/audio-player.js +4 -0
- data/lib/typingpool/templates/project/etc/player/license.txt +19 -0
- data/lib/typingpool/templates/project/etc/player/player.swf +0 -0
- data/lib/typingpool/templates/project/etc/transcript.css +49 -0
- data/lib/typingpool/templates/transcript.html.erb +23 -0
- data/lib/typingpool/test/fixtures/amazon-question-html.html +95 -0
- data/lib/typingpool/test/fixtures/amazon-question-url.txt +1 -0
- data/lib/typingpool/test/fixtures/audio/mp3/interview.1.mp3 +0 -0
- data/lib/typingpool/test/fixtures/audio/mp3/interview.2.mp3 +0 -0
- data/lib/typingpool/test/fixtures/audio/wma/VN620007.WMA +0 -0
- data/lib/typingpool/test/fixtures/audio/wma/VN620052.WMA +0 -0
- data/lib/typingpool/test/fixtures/config-1 +20 -0
- data/lib/typingpool/test/fixtures/config-2 +25 -0
- data/lib/typingpool/test/fixtures/not_yaml.txt +4 -0
- data/lib/typingpool/test/fixtures/template-2.html.erb +10 -0
- data/lib/typingpool/test/fixtures/template-3.html.erb +22 -0
- data/lib/typingpool/test/fixtures/template.html.erb +10 -0
- data/lib/typingpool/test/fixtures/tp_collect_id.txt +1 -0
- data/lib/typingpool/test/fixtures/tp_collect_sandbox-assignment.csv +8 -0
- data/lib/typingpool/test/fixtures/tp_review_id.txt +1 -0
- data/lib/typingpool/test/fixtures/tp_review_sandbox-assignment.csv +8 -0
- data/lib/typingpool/test/fixtures/transcript-chunks.csv +226 -0
- data/lib/typingpool/test/fixtures/utf8_transcript.txt +7 -0
- data/lib/typingpool/test/fixtures/vcr/tp-collect-1.yml +2712 -0
- data/lib/typingpool/test/fixtures/vcr/tp-collect-2.yml +2718 -0
- data/lib/typingpool/test/fixtures/vcr/tp-collect-3.yml +2768 -0
- data/lib/typingpool/test/fixtures/vcr/tp-review-1.yml +570 -0
- data/lib/typingpool/test/fixtures/vcr/tp-review-2.yml +351 -0
- data/lib/typingpool/test.rb +418 -0
- data/lib/typingpool/transcript.rb +181 -0
- data/lib/typingpool/utility.rb +272 -0
- data/lib/typingpool.rb +500 -0
- data/test/make_amazon_question_fixture.rb +24 -0
- data/test/make_tp_collect_fixture_1.rb +26 -0
- data/test/make_tp_collect_fixture_2.rb +16 -0
- data/test/make_tp_collect_fixture_3.rb +15 -0
- data/test/make_tp_collect_fixture_4.rb +17 -0
- data/test/make_tp_review_fixture_1.rb +26 -0
- data/test/make_tp_review_fixture_2.rb +30 -0
- data/test/make_transcript_chunks_fixture.rb +53 -0
- data/test/test_integration_script_1_tp_config.rb +108 -0
- data/test/test_integration_script_2_tp_make.rb +119 -0
- data/test/test_integration_script_3_tp_assign.rb +152 -0
- data/test/test_integration_script_4_tp_review.rb +72 -0
- data/test/test_integration_script_5_tp_collect.rb +44 -0
- data/test/test_integration_script_6_tp_finish.rb +123 -0
- data/test/test_unit_amazon.rb +153 -0
- data/test/test_unit_config.rb +94 -0
- data/test/test_unit_filer.rb +202 -0
- data/test/test_unit_project.rb +168 -0
- data/test/test_unit_project_local.rb +68 -0
- data/test/test_unit_project_remote.rb +157 -0
- data/test/test_unit_template.rb +111 -0
- data/test/test_unit_transcript.rb +77 -0
- metadata +234 -0
@@ -0,0 +1,272 @@
|
|
1
|
+
module Typingpool
|
2
|
+
module Utility
|
3
|
+
require 'open3'
|
4
|
+
require 'uri'
|
5
|
+
require 'tmpdir'
|
6
|
+
require 'set'
|
7
|
+
require 'net/http'
|
8
|
+
|
9
|
+
class << self
|
10
|
+
#Much like Kernel#system, except it doesn't spew STDERR and
|
11
|
+
#STDOUT all over your screen (when called with multiple args,
|
12
|
+
#which with Kernel#systems kills the chance to do shell style
|
13
|
+
#stream redirects like 2>/dev/null). Even more like
|
14
|
+
#Open3.capture3, except it raises an exception on unsuccesful
|
15
|
+
#exit status.
|
16
|
+
#
|
17
|
+
# ==== Params
|
18
|
+
#[cmd] Commands to send to the shell, just as with Kernel#system.
|
19
|
+
#
|
20
|
+
# ==== Returns
|
21
|
+
#On success: STDOUT, or true if STDOUT is empty
|
22
|
+
#On failure: Raises Typingpool::Error::Shell, with STDERR as
|
23
|
+
#error text if available.
|
24
|
+
def system_quietly(*cmd)
|
25
|
+
out, err, status = Open3.capture3(*cmd)
|
26
|
+
if status.success?
|
27
|
+
return out ? out.chomp : true
|
28
|
+
else
|
29
|
+
if err
|
30
|
+
raise Error::Shell, err.chomp
|
31
|
+
else
|
32
|
+
raise Error::Shell
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#Convert config entries like '30s','2d','10h'. etc into number of seconds.
|
38
|
+
#
|
39
|
+
# ==== Params
|
40
|
+
#[timespec] string conforming to format outlined at
|
41
|
+
#http://search.cpan.org/~markstos/CGI-Session-4.48/lib/CGI/Session.pm#expire($param,_$time)
|
42
|
+
# ==== Returns
|
43
|
+
#Number of whole seconds corresponding to the timespec. Raises
|
44
|
+
#Typingpool::Error::Argument::Format on bad input.
|
45
|
+
def timespec_to_seconds(timespec)
|
46
|
+
timespec or return
|
47
|
+
suffix_to_time = {
|
48
|
+
's'=>1,
|
49
|
+
'm'=>60,
|
50
|
+
'h'=>60*60,
|
51
|
+
'd'=>60*60*24,
|
52
|
+
'M'=>60*60*24*30,
|
53
|
+
'y'=>60*60*24*365
|
54
|
+
}
|
55
|
+
match = timespec.to_s.match(/^\+?(\d+(\.\d+)?)\s*([#{suffix_to_time.keys.join}])?$/) or raise Error::Argument::Format, "Can't convert '#{timespec}' to time"
|
56
|
+
suffix = match[3] || 's'
|
57
|
+
return (match[1].to_f * suffix_to_time[suffix].to_i).to_i
|
58
|
+
end
|
59
|
+
|
60
|
+
# ==== Returns
|
61
|
+
#Single hash, with keys corresponding to Headers and values
|
62
|
+
#corresponding to respective entries in Array.
|
63
|
+
def array_to_hash(array, headers)
|
64
|
+
Hash[*headers.zip(array).flatten]
|
65
|
+
end
|
66
|
+
|
67
|
+
#The base file at the root of the URL path.
|
68
|
+
# ==== Params
|
69
|
+
#[url] string url
|
70
|
+
# ==== Returns
|
71
|
+
#File name
|
72
|
+
def url_basename(url)
|
73
|
+
File.basename(URI.parse(url).path)
|
74
|
+
end
|
75
|
+
|
76
|
+
#Converts standard linefeed combos - CRLF, CR, LF - to whatever
|
77
|
+
#the system newline is, aka "\n".
|
78
|
+
# ==== Returns
|
79
|
+
#String with normalized newlines
|
80
|
+
def normalize_newlines(text)
|
81
|
+
text.gsub!("\r\n", "\n")
|
82
|
+
text.gsub!("\r", "\n")
|
83
|
+
text.gsub!("\f", "\n")
|
84
|
+
text
|
85
|
+
end
|
86
|
+
|
87
|
+
#Does a natural-feeling conversion between plain text linebreaks
|
88
|
+
#and HTML P and BR tags, as typical with most web comment forms.
|
89
|
+
# ==== Returns
|
90
|
+
#Text with double newlines converted to P tags, single
|
91
|
+
#newlines converted to BR tags, and the original newlines
|
92
|
+
#restored.
|
93
|
+
def newlines_to_html(text)
|
94
|
+
text.gsub!(/\n\n+/, '<p>')
|
95
|
+
text.gsub!(/\n/, '<br>')
|
96
|
+
text.gsub!(/<p>/, "\n\n<p>")
|
97
|
+
text.gsub!(/<br>/, "\n<br>")
|
98
|
+
text
|
99
|
+
end
|
100
|
+
|
101
|
+
#Takes an array, returns a string with the array elements joined
|
102
|
+
#with a comma, except for the last and second to last items,
|
103
|
+
#which are joined with ' and '. For example: ['foo','bar'] => "foo
|
104
|
+
#and bar"; ['foo','bar','baz'] => "foo, bar and baz"
|
105
|
+
#
|
106
|
+
#Also takes an optional flag which specifies whether to use an
|
107
|
+
#oxford comma. Default is false. If set to true, the last and
|
108
|
+
#second to last items will be joined with ', and'
|
109
|
+
def join_in_english(array, oxford_comma=false)
|
110
|
+
array = array.dup
|
111
|
+
oxford_comma = (oxford_comma && array.count > 2) ? ',' : ''
|
112
|
+
last = array.pop
|
113
|
+
array.empty? ? last : [array.join(', '), last].join("#{oxford_comma} and ")
|
114
|
+
end
|
115
|
+
|
116
|
+
#Takes a block and calls that block with a path to a temporary
|
117
|
+
#directory. Recursively deletes that directory when the block is
|
118
|
+
#finished.
|
119
|
+
def in_temp_dir
|
120
|
+
dir = Dir.mktmpdir
|
121
|
+
begin
|
122
|
+
yield(dir)
|
123
|
+
ensure
|
124
|
+
FileUtils.remove_entry_secure(dir)
|
125
|
+
end # begin
|
126
|
+
end
|
127
|
+
|
128
|
+
#Returns Typingpool's lib/ root, usually for purposes of
|
129
|
+
#locating templates or test fixtures.
|
130
|
+
def lib_dir
|
131
|
+
File.dirname(__FILE__)
|
132
|
+
end
|
133
|
+
|
134
|
+
#Returns true if this Ruby was built on Mac OS X
|
135
|
+
def os_x?
|
136
|
+
RUBY_PLATFORM.match(/\bdarwin/i)
|
137
|
+
end
|
138
|
+
|
139
|
+
#Returns true if anything appears to be waiting on STDIN
|
140
|
+
def stdin_has_content?
|
141
|
+
STDIN.fcntl(Fcntl::F_GETFL, 0) == 0
|
142
|
+
end
|
143
|
+
|
144
|
+
#Makes one or more HEAD requests to determine whether a
|
145
|
+
#particular web resource is available.
|
146
|
+
# ==== Params
|
147
|
+
#[url] URL as a string.
|
148
|
+
#[max_redirects] Default 6. Maximum number of HTTP redirects to
|
149
|
+
# follow.
|
150
|
+
# ==== Returns
|
151
|
+
#True if the HTTP response code indicates success (after
|
152
|
+
#following redirects). False if the HTTP response code indicates
|
153
|
+
#an error (e.g. 4XX and 5XX response codes).
|
154
|
+
def working_url?(url, max_redirects=6)
|
155
|
+
response = request_url_with(url, max_redirects) do |url, http|
|
156
|
+
http.request_head(url.path)
|
157
|
+
end #request_url_with... do |url|
|
158
|
+
response.kind_of?(Net::HTTPSuccess)
|
159
|
+
end
|
160
|
+
|
161
|
+
#Makes one or more web requests to fetch a resource. Follows
|
162
|
+
#redirects by default.
|
163
|
+
# ==== Params
|
164
|
+
#[url] URL as a string.
|
165
|
+
#[max_redirects] Default 6. Maximum number of HTTP redirects to
|
166
|
+
# follow.
|
167
|
+
# ==== Exceptions
|
168
|
+
#Raises Error::HTTP if it receives an HTTP response code
|
169
|
+
#indicating an error (after followinf redirects). Exception
|
170
|
+
#message will include the response code and response message.
|
171
|
+
# ==== Returns
|
172
|
+
#A Net::HTTPResponse instance, if the request was successful.
|
173
|
+
def fetch_url(url, max_redirects=6)
|
174
|
+
response = request_url_with(url, max_redirects) do |url, http|
|
175
|
+
http.request_get(url.path)
|
176
|
+
end
|
177
|
+
if response.kind_of?(Net::HTTPSuccess)
|
178
|
+
return response
|
179
|
+
else
|
180
|
+
raise Error::HTTP, "HTTP error fetching '#{url.to_s}': '#{response.code}: #{response.message}'"
|
181
|
+
end #if response.kind_of?
|
182
|
+
end
|
183
|
+
|
184
|
+
#protected
|
185
|
+
|
186
|
+
def request_url_with(url, max_redirects=6)
|
187
|
+
seen = Set.new
|
188
|
+
loop do
|
189
|
+
url = URI.parse(url)
|
190
|
+
if seen.include? url.to_s
|
191
|
+
raise Error::HTTP, "Redirect infinite loop (at '#{url.to_s}')"
|
192
|
+
end
|
193
|
+
if seen.count > max_redirects
|
194
|
+
raise Error::HTTP, "Too many redirects (>#{max_redirects})"
|
195
|
+
end
|
196
|
+
seen.add(url.to_s)
|
197
|
+
#Die in a fire, net/http.
|
198
|
+
http = Net::HTTP.new(url.host, url.port)
|
199
|
+
http.use_ssl = true if url.scheme == 'https'
|
200
|
+
response = yield(url, http)
|
201
|
+
if response.kind_of?(Net::HTTPRedirection)
|
202
|
+
url = response['location']
|
203
|
+
else
|
204
|
+
return response
|
205
|
+
end #if response.kind_of?...
|
206
|
+
end #loop do
|
207
|
+
end
|
208
|
+
|
209
|
+
end #class << self
|
210
|
+
|
211
|
+
module Castable
|
212
|
+
#Cast this object instance to a relative class. Call this from
|
213
|
+
#super in your own class if you want to pass args to the
|
214
|
+
#relative class constructor. All args after the first will be
|
215
|
+
#passed to new.
|
216
|
+
#
|
217
|
+
#A relative class can be a subclass and in some cases a sibling
|
218
|
+
#class, parent class, parent sibling class, grandparent class,
|
219
|
+
#grandparent sibling class, and so on. A relative class will
|
220
|
+
#never be higher up the inheritance tree than the subclasses of
|
221
|
+
#the class where Castable was included.
|
222
|
+
# ==== Params
|
223
|
+
# [sym] Symbol corresponding to relative class to cast into. For
|
224
|
+
# example, Class#as(:audio) will cast into a Class::Audio
|
225
|
+
# and Class#as(:csv) will cast into Class::CSV. Casting
|
226
|
+
# is class insensitive, which means you can't have class
|
227
|
+
# CSV and class Csv. To cast into a related class whose
|
228
|
+
# name is not not directly under that of its parent, you
|
229
|
+
# must either specify the full name,
|
230
|
+
# e.g. Class#as(:foo_bar_baz) to cast to Foo::Bar::Baz,
|
231
|
+
# or a name relative to the parent,
|
232
|
+
# e.g. Class#as(:remote_html), where Class::Remote does
|
233
|
+
# not inherit from Class but Class::Remote::HTML does.
|
234
|
+
# ==== Returns
|
235
|
+
# New instance of subclass
|
236
|
+
def as(sym, *args)
|
237
|
+
if klass = self.class.relative_klass(sym.to_s.downcase)
|
238
|
+
klass.new(*args)
|
239
|
+
else
|
240
|
+
raise Error, "Can't find class '#{sym.to_s}' to cast to"
|
241
|
+
end #if subklass =...
|
242
|
+
end
|
243
|
+
|
244
|
+
def self.included(receiver)
|
245
|
+
receiver.extend(ClassMethods)
|
246
|
+
end
|
247
|
+
|
248
|
+
module ClassMethods
|
249
|
+
def inherited(subklass)
|
250
|
+
subklasses[subklass.to_s.split("#{self.name}::").last.downcase.gsub(/::/, '_')] = subklass
|
251
|
+
end
|
252
|
+
|
253
|
+
def subklasses
|
254
|
+
@subklasses ||= {}
|
255
|
+
end
|
256
|
+
|
257
|
+
def subklass(subklass_key)
|
258
|
+
subklasses[subklass_key]
|
259
|
+
end
|
260
|
+
|
261
|
+
def relative_klass(key)
|
262
|
+
if subklasses[key]
|
263
|
+
subklasses[key]
|
264
|
+
elsif self.superclass.respond_to? :relative_klass
|
265
|
+
self.superclass.relative_klass(key)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
end #module ClassMethods
|
270
|
+
end #Castable
|
271
|
+
end #Utility
|
272
|
+
end #Typingpool
|