typingpool 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/Rakefile +23 -0
  2. data/bin/tp-assign +240 -0
  3. data/bin/tp-collect +50 -0
  4. data/bin/tp-config +114 -0
  5. data/bin/tp-finish +101 -0
  6. data/bin/tp-make +169 -0
  7. data/bin/tp-review +175 -0
  8. data/lib/typingpool/amazon.rb +732 -0
  9. data/lib/typingpool/app.rb +634 -0
  10. data/lib/typingpool/config.rb +344 -0
  11. data/lib/typingpool/error.rb +22 -0
  12. data/lib/typingpool/filer.rb +396 -0
  13. data/lib/typingpool/project.rb +593 -0
  14. data/lib/typingpool/template.rb +175 -0
  15. data/lib/typingpool/templates/assignment/amazon-init.js +38 -0
  16. data/lib/typingpool/templates/assignment/interview/nameless.html.erb +13 -0
  17. data/lib/typingpool/templates/assignment/interview/noisy.html.erb +12 -0
  18. data/lib/typingpool/templates/assignment/interview/partials/voices.html.erb +10 -0
  19. data/lib/typingpool/templates/assignment/interview/phone.html.erb +12 -0
  20. data/lib/typingpool/templates/assignment/interview.html.erb +11 -0
  21. data/lib/typingpool/templates/assignment/main.css +20 -0
  22. data/lib/typingpool/templates/assignment/partials/entry.html.erb +19 -0
  23. data/lib/typingpool/templates/assignment/partials/footer.html.erb +3 -0
  24. data/lib/typingpool/templates/assignment/partials/header.html.erb +11 -0
  25. data/lib/typingpool/templates/assignment/partials/labeling-example.html.erb +4 -0
  26. data/lib/typingpool/templates/assignment/partials/labeling.html.erb +5 -0
  27. data/lib/typingpool/templates/assignment/partials/length-description.html.erb +6 -0
  28. data/lib/typingpool/templates/assignment/partials/voices.html.erb +10 -0
  29. data/lib/typingpool/templates/assignment/speech.html.erb +11 -0
  30. data/lib/typingpool/templates/config.yml +21 -0
  31. data/lib/typingpool/templates/project/audio/chunks/.empty_directory +0 -0
  32. data/lib/typingpool/templates/project/audio/originals/.empty_directory +0 -0
  33. data/lib/typingpool/templates/project/data/.empty_directory +0 -0
  34. data/lib/typingpool/templates/project/etc/ About these files - read me.txt +8 -0
  35. data/lib/typingpool/templates/project/etc/audio-compat.js +25 -0
  36. data/lib/typingpool/templates/project/etc/player/audio-player.js +4 -0
  37. data/lib/typingpool/templates/project/etc/player/license.txt +19 -0
  38. data/lib/typingpool/templates/project/etc/player/player.swf +0 -0
  39. data/lib/typingpool/templates/project/etc/transcript.css +49 -0
  40. data/lib/typingpool/templates/transcript.html.erb +23 -0
  41. data/lib/typingpool/test/fixtures/amazon-question-html.html +95 -0
  42. data/lib/typingpool/test/fixtures/amazon-question-url.txt +1 -0
  43. data/lib/typingpool/test/fixtures/audio/mp3/interview.1.mp3 +0 -0
  44. data/lib/typingpool/test/fixtures/audio/mp3/interview.2.mp3 +0 -0
  45. data/lib/typingpool/test/fixtures/audio/wma/VN620007.WMA +0 -0
  46. data/lib/typingpool/test/fixtures/audio/wma/VN620052.WMA +0 -0
  47. data/lib/typingpool/test/fixtures/config-1 +20 -0
  48. data/lib/typingpool/test/fixtures/config-2 +25 -0
  49. data/lib/typingpool/test/fixtures/not_yaml.txt +4 -0
  50. data/lib/typingpool/test/fixtures/template-2.html.erb +10 -0
  51. data/lib/typingpool/test/fixtures/template-3.html.erb +22 -0
  52. data/lib/typingpool/test/fixtures/template.html.erb +10 -0
  53. data/lib/typingpool/test/fixtures/tp_collect_id.txt +1 -0
  54. data/lib/typingpool/test/fixtures/tp_collect_sandbox-assignment.csv +8 -0
  55. data/lib/typingpool/test/fixtures/tp_review_id.txt +1 -0
  56. data/lib/typingpool/test/fixtures/tp_review_sandbox-assignment.csv +8 -0
  57. data/lib/typingpool/test/fixtures/transcript-chunks.csv +226 -0
  58. data/lib/typingpool/test/fixtures/utf8_transcript.txt +7 -0
  59. data/lib/typingpool/test/fixtures/vcr/tp-collect-1.yml +2712 -0
  60. data/lib/typingpool/test/fixtures/vcr/tp-collect-2.yml +2718 -0
  61. data/lib/typingpool/test/fixtures/vcr/tp-collect-3.yml +2768 -0
  62. data/lib/typingpool/test/fixtures/vcr/tp-review-1.yml +570 -0
  63. data/lib/typingpool/test/fixtures/vcr/tp-review-2.yml +351 -0
  64. data/lib/typingpool/test.rb +418 -0
  65. data/lib/typingpool/transcript.rb +181 -0
  66. data/lib/typingpool/utility.rb +272 -0
  67. data/lib/typingpool.rb +500 -0
  68. data/test/make_amazon_question_fixture.rb +24 -0
  69. data/test/make_tp_collect_fixture_1.rb +26 -0
  70. data/test/make_tp_collect_fixture_2.rb +16 -0
  71. data/test/make_tp_collect_fixture_3.rb +15 -0
  72. data/test/make_tp_collect_fixture_4.rb +17 -0
  73. data/test/make_tp_review_fixture_1.rb +26 -0
  74. data/test/make_tp_review_fixture_2.rb +30 -0
  75. data/test/make_transcript_chunks_fixture.rb +53 -0
  76. data/test/test_integration_script_1_tp_config.rb +108 -0
  77. data/test/test_integration_script_2_tp_make.rb +119 -0
  78. data/test/test_integration_script_3_tp_assign.rb +152 -0
  79. data/test/test_integration_script_4_tp_review.rb +72 -0
  80. data/test/test_integration_script_5_tp_collect.rb +44 -0
  81. data/test/test_integration_script_6_tp_finish.rb +123 -0
  82. data/test/test_unit_amazon.rb +153 -0
  83. data/test/test_unit_config.rb +94 -0
  84. data/test/test_unit_filer.rb +202 -0
  85. data/test/test_unit_project.rb +168 -0
  86. data/test/test_unit_project_local.rb +68 -0
  87. data/test/test_unit_project_remote.rb +157 -0
  88. data/test/test_unit_template.rb +111 -0
  89. data/test/test_unit_transcript.rb +77 -0
  90. 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