typingpool 0.7.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.
- 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
|