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
data/Rakefile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'rake'
|
|
4
|
+
require 'rake/testtask'
|
|
5
|
+
|
|
6
|
+
task :default => [:test]
|
|
7
|
+
|
|
8
|
+
desc "Run all tests"
|
|
9
|
+
task :test => [:test_unit, :test_integration]
|
|
10
|
+
|
|
11
|
+
desc "Run unit tests"
|
|
12
|
+
Rake::TestTask.new('test_unit') do |t|
|
|
13
|
+
t.test_files = FileList[
|
|
14
|
+
'test/test_unit*'
|
|
15
|
+
]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
desc "Run integration tests"
|
|
19
|
+
Rake::TestTask.new('test_integration') do |t|
|
|
20
|
+
t.test_files = FileList[
|
|
21
|
+
(1..6).map{|n| "test/test_integration_script_#{n}*" }
|
|
22
|
+
]
|
|
23
|
+
end
|
data/bin/tp-assign
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require 'typingpool'
|
|
5
|
+
require 'highline/import'
|
|
6
|
+
include Typingpool::App::FriendlyExceptions
|
|
7
|
+
include Typingpool::App::CLI::Formatter
|
|
8
|
+
|
|
9
|
+
options = {
|
|
10
|
+
:keyword => [],
|
|
11
|
+
:qualify => []
|
|
12
|
+
}
|
|
13
|
+
OptionParser.new do |opts|
|
|
14
|
+
options[:banner] = "USAGE: #{File.basename($PROGRAM_NAME)} PROJECT TEMPLATE [--reward 0.75]\n"
|
|
15
|
+
options[:banner] += " [--deadline 3h] [--lifetime 2d] [--approval 1d] \n"
|
|
16
|
+
options[:banner] += " [--qualify 'approval_rate >= 95' --qualify 'hits_approved > 10'...]\n"
|
|
17
|
+
options[:banner] += " [--keyword transcription --keyword mp3...]\n"
|
|
18
|
+
options[:banner] += " [--confirm] [--sandbox] [--config PATH]\n"
|
|
19
|
+
opts.banner = options[:banner]
|
|
20
|
+
|
|
21
|
+
opts.on('--project=PROJECT',
|
|
22
|
+
"Required. Path or name within",
|
|
23
|
+
"$config_file:transcripts. Also accepted as",
|
|
24
|
+
"first argument to script or via STDIN") do |project|
|
|
25
|
+
options[:project] = project
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
opts.on('--template=TEMPLATE',
|
|
29
|
+
"Required. Path or name within dir",
|
|
30
|
+
"$config_file:app/templates/assign. Also",
|
|
31
|
+
"accepted as first argument to script") do |template|
|
|
32
|
+
options[:template] = template
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
opts.on('--reward=DOLLARS',
|
|
36
|
+
"Default: $config_file:assign:reward.",
|
|
37
|
+
"Always in USD (Amazon only supports USD).",
|
|
38
|
+
"Per chunk. Format N.NN") do |reward|
|
|
39
|
+
options[:reward] = reward
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
opts.on('--keyword=WORD',
|
|
43
|
+
"Default: $config_file:assign:keywords.",
|
|
44
|
+
"Repeatable") do |keyword|
|
|
45
|
+
options[:keyword].push(keyword)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
timespec = 'N[.N]y|M(onths)|d|h|m(inutes)|s'
|
|
49
|
+
|
|
50
|
+
opts.on('--deadline=TIMESPEC',
|
|
51
|
+
'Default: $config:assign:deadline. Worker',
|
|
52
|
+
"time to transcribe.",
|
|
53
|
+
timespec) do |timespec|
|
|
54
|
+
options[:deadline] = timespec
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
opts.on('--lifetime=TIMESPEC',
|
|
58
|
+
'Default: $config:assign:lifetime.',
|
|
59
|
+
'Assignment time to expire.',
|
|
60
|
+
timespec) do |timespec|
|
|
61
|
+
options[:lifetime] = timespec
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
opts.on('--approval=TIMESPEC',
|
|
65
|
+
'Default: $config:assign:approval.',
|
|
66
|
+
'Submission time to auto approve.',
|
|
67
|
+
timespec) do |timespec|
|
|
68
|
+
options[:approval] = timespec
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
opts.on('--qualify=QUALIFICATION',
|
|
72
|
+
"Default: $config:assign:qualify.",
|
|
73
|
+
"Repeatable.",
|
|
74
|
+
"An RTurk::Qualifications::TYPES +",
|
|
75
|
+
">|<|==|!=|true|exists|>=|<= [+ INT]") do |qualification|
|
|
76
|
+
options[:qualify].push(qualification)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
opts.on('--confirm',
|
|
80
|
+
"Confirm the total cost of the assignments",
|
|
81
|
+
"before assigning even if",
|
|
82
|
+
"$config:assign:confirm is set to 'no'") do
|
|
83
|
+
options[:confirm] = true
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
opts.on('--sandbox',
|
|
87
|
+
"Test in Mechanical Turk's sandbox") do
|
|
88
|
+
options[:sandbox] = true
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
opts.on('--config=PATH',
|
|
92
|
+
'Default: ~/.typingpool') do |path|
|
|
93
|
+
options[:config] = path
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
opts.on('--help',
|
|
97
|
+
'Display this screen') do
|
|
98
|
+
STDERR.puts opts
|
|
99
|
+
exit
|
|
100
|
+
end
|
|
101
|
+
end.parse!
|
|
102
|
+
|
|
103
|
+
config = Typingpool::App::CLI.config_from_arg(options[:config]) or abort "No config file at '#{options[:config]}'"
|
|
104
|
+
|
|
105
|
+
if options[:keyword].count > 0
|
|
106
|
+
config.assign.keywords = []
|
|
107
|
+
config.assign.keywords.push(*options[:keyword])
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
[:reward, :deadline, :lifetime, :approval].each do |param|
|
|
111
|
+
with_friendly_exceptions("--#{param} argument", options[param]) do
|
|
112
|
+
config.assign.send("#{param.to_s}=", options[param]) if options[param]
|
|
113
|
+
end
|
|
114
|
+
end #[:deadline, :lifetime, :approval].each...
|
|
115
|
+
|
|
116
|
+
if options[:qualify].count > 0
|
|
117
|
+
config.assign['qualify'] = []
|
|
118
|
+
options[:qualify].each do |qualification|
|
|
119
|
+
with_friendly_exceptions('--qualify argument', qualification) do
|
|
120
|
+
config.assign.add_qualification(qualification)
|
|
121
|
+
end
|
|
122
|
+
end #options[:qualify].each...
|
|
123
|
+
end #if options[:qualify].count > 0
|
|
124
|
+
|
|
125
|
+
options[:banner] += "\n#{Typingpool::App::CLI.help_arg_explanation}\n"
|
|
126
|
+
|
|
127
|
+
positional = %w(project template)
|
|
128
|
+
if Typingpool::Utility.stdin_has_content? && project = STDIN.gets
|
|
129
|
+
project.chomp!
|
|
130
|
+
abort "Duplicate project values (STDIN and --project)" if options[:project]
|
|
131
|
+
options[:project] = project
|
|
132
|
+
positional.shift
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
positional.each do |name|
|
|
136
|
+
arg = ARGV.shift
|
|
137
|
+
if options[name.to_sym]
|
|
138
|
+
abort "Duplicate values for #{name} (argument and --#{name})" if arg
|
|
139
|
+
else
|
|
140
|
+
options[name.to_sym] = arg or abort "Missing required arg #{name}\n\n#{options[:banner]}"
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
abort "Unexpected argument(s): #{ARGV.join(';')}" if ARGV.count > 0
|
|
144
|
+
|
|
145
|
+
project = Typingpool::App::CLI.project_from_arg_and_config(options[:project], config)
|
|
146
|
+
|
|
147
|
+
begin
|
|
148
|
+
template = Typingpool::Template::Assignment.from_config(options[:template], config)
|
|
149
|
+
rescue Typingpool::Error::File::NotExists => e
|
|
150
|
+
abort "Couldn't find the template dir in your config file: #{e}"
|
|
151
|
+
rescue Typingpool::Error => e
|
|
152
|
+
abort "Couldn't find your template: #{e}"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
#always upload assignment html (can't re-use old ones because params
|
|
156
|
+
#may have changed, affecting html output) #
|
|
157
|
+
STDERR.puts "Figuring out what needs to be assigned"
|
|
158
|
+
assignments = Typingpool::App.assignments_file_for_sandbox_status(options[:sandbox], project)
|
|
159
|
+
needed_assignments = []
|
|
160
|
+
unneeded_assignments = {
|
|
161
|
+
:complete => 0,
|
|
162
|
+
:outstanding => 0
|
|
163
|
+
}
|
|
164
|
+
assignments.each do |assignment|
|
|
165
|
+
if assignment['transcript']
|
|
166
|
+
unneeded_assignments[:complete] += 1
|
|
167
|
+
next
|
|
168
|
+
end
|
|
169
|
+
if assignment['hit_expires_at'].to_s.match(/\S/) #has been assigned previously
|
|
170
|
+
if ((Time.parse(assignment['hit_expires_at']) + assignment['hit_assignments_duration'].to_i) > Time.now)
|
|
171
|
+
#unexpired active HIT - do not reassign
|
|
172
|
+
unneeded_assignments[:outstanding] += 1
|
|
173
|
+
next
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
needed_assignments << assignment
|
|
177
|
+
end
|
|
178
|
+
STDERR.puts "#{assignments.count} assignments total"
|
|
179
|
+
STDERR.puts "#{unneeded_assignments[:complete]} assignments completed" if unneeded_assignments[:complete] > 0
|
|
180
|
+
STDERR.puts "#{unneeded_assignments[:outstanding]} assignments outstanding" if unneeded_assignments[:outstanding] > 0
|
|
181
|
+
if needed_assignments.empty?
|
|
182
|
+
STDERR.puts "Nothing to assign"
|
|
183
|
+
exit
|
|
184
|
+
end
|
|
185
|
+
STDERR.puts "#{needed_assignments.count} assignments to assign"
|
|
186
|
+
Typingpool::Amazon.setup(:sandbox => options[:sandbox], :config => config)
|
|
187
|
+
|
|
188
|
+
#Are there enough funds for this assignment?
|
|
189
|
+
cost = 0
|
|
190
|
+
cost_string = '0.00'
|
|
191
|
+
unless options[:sandbox]
|
|
192
|
+
STDERR.puts "Checking your balance"
|
|
193
|
+
balance = RTurk.GetAccountBalance.amount
|
|
194
|
+
#"* 1.1" reflects 10% Amazon service surcharge
|
|
195
|
+
cost = (needed_assignments.count * options[:reward].to_f * 1.1).round(2)
|
|
196
|
+
cost_string = sprintf("%.2f", cost)
|
|
197
|
+
abort "Anticipated assignment cost of $#{cost_string} would exceed available balance of $#{balance}" if cost > balance
|
|
198
|
+
if options[:confirm] || config.assign.confirm
|
|
199
|
+
begin
|
|
200
|
+
input = ask("Assignment cost is $#{cost_string}. Proceed? [#{cli_bold('y')}es]/#{cli_bold('n')}o:")
|
|
201
|
+
end until input.to_s.match(/(^y)|(^n)|(^\s*$)/i)
|
|
202
|
+
exit if input.match(/^n/i)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
#we'll need to re-upload audio if we ran tp-finish on the project
|
|
207
|
+
#or if our last attempt to upload audio failed partway through
|
|
208
|
+
Typingpool::App.upload_audio_for_project(project) do |file, as|
|
|
209
|
+
puts "Uploading #{File.basename(file)} to #{project.remote.host}/#{project.remote.path} as #{as}"
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
#Delete old assignment html for assignments to be re-assigned. We
|
|
213
|
+
#can't re-use old assignment HTML because new params (e.g. reward)
|
|
214
|
+
#might cause the new HTML to be different
|
|
215
|
+
STDERR.puts "Deleting old assignment HTML from #{project.remote.host}"
|
|
216
|
+
Typingpool::App.updelete_assignment_assets(project, assignments, needed_assignments, ['assignment'])
|
|
217
|
+
|
|
218
|
+
STDERR.puts "Uploading assignment HTML to #{project.remote.host}"
|
|
219
|
+
urls = Typingpool::App.upload_html_for_project_assignments(project, assignments, needed_assignments, template)
|
|
220
|
+
|
|
221
|
+
STDERR.puts (options[:sandbox] ? 'Assigning (in sandbox)' : 'Assigning')
|
|
222
|
+
needed_assignments.each_with_index do |assignment, i|
|
|
223
|
+
question = Typingpool::Amazon::Question.new(urls[i], template.render(assignment))
|
|
224
|
+
begin
|
|
225
|
+
hit = Typingpool::Amazon::HIT.create(question, config.assign)
|
|
226
|
+
Typingpool::App.record_assigned_hits_in_assignments_file(assignments, [hit])
|
|
227
|
+
rescue RTurk::RTurkError => e
|
|
228
|
+
goodbye = "Mechanical Turk error: #{e}\n\n"
|
|
229
|
+
if i == 0
|
|
230
|
+
goodbye += "To retry, run tp-assign again with the same arguments."
|
|
231
|
+
else
|
|
232
|
+
goodbye += "To cancel #{i} successful assignments, run `tp-finish '#{options[:project]}'`.\n\n"
|
|
233
|
+
goodbye += "To try and assign remaining #{neeeded_assignments.count - i} jobs, run tp-assign again with the same arguments."
|
|
234
|
+
end #if i == 0
|
|
235
|
+
abort goodbye
|
|
236
|
+
end #begin
|
|
237
|
+
STDERR.puts " Assigned transcription job for '#{project.class.local_basename_from_url(assignment['audio_url'])}'"
|
|
238
|
+
end #needed_assignments.each_with_index do...
|
|
239
|
+
STDERR.puts "Assigned #{needed_assignments.count} transcription jobs for $#{cost_string}"
|
|
240
|
+
STDERR.puts "Remaining balance: $#{RTurk.GetAccountBalance.amount}" unless options[:sandbox]
|
data/bin/tp-collect
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'typingpool'
|
|
4
|
+
require 'optparse'
|
|
5
|
+
|
|
6
|
+
options = {}
|
|
7
|
+
OptionParser.new do |commands|
|
|
8
|
+
commands.banner = "USAGE: #{File.basename($PROGRAM_NAME)} [--config PATH] [--sandbox]\n"
|
|
9
|
+
commands.on('--sandbox',
|
|
10
|
+
"Collect from the Mechanical Turk test sandbox") do
|
|
11
|
+
options[:sandbox] = true
|
|
12
|
+
end
|
|
13
|
+
commands.on('--config=PATH',
|
|
14
|
+
"Default: ~/.typingpool",
|
|
15
|
+
"A config file") do |path|
|
|
16
|
+
options[:config] = path
|
|
17
|
+
end
|
|
18
|
+
commands.on('--fixture=PATH',
|
|
19
|
+
"Optional. For testing purposes only.",
|
|
20
|
+
"A VCR ficture for running with mock data.") do |fixture|
|
|
21
|
+
options[:fixture] = fixture
|
|
22
|
+
end
|
|
23
|
+
commands.on('--help',
|
|
24
|
+
"Display this screen") do
|
|
25
|
+
STDERR.puts commands
|
|
26
|
+
exit
|
|
27
|
+
end
|
|
28
|
+
end.parse!
|
|
29
|
+
|
|
30
|
+
config = Typingpool::App::CLI.config_from_arg(options[:config]) or abort "No config file at '#{options[:config]}'"
|
|
31
|
+
|
|
32
|
+
if options[:fixture]
|
|
33
|
+
Typingpool::App.vcr_record(options[:fixture], config)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
STDERR.puts "Collecting results from Amazon"
|
|
37
|
+
Typingpool::Amazon.setup(:sandbox => options[:sandbox], :config => config)
|
|
38
|
+
hits = Typingpool::Amazon::HIT.all_approved
|
|
39
|
+
|
|
40
|
+
STDERR.puts "Looking for local project folders to receive results" unless hits.empty?
|
|
41
|
+
Typingpool::App.find_projects_waiting_for_hits(hits, config) do |project, hits|
|
|
42
|
+
assignments_file = Typingpool::App.assignments_file_for_sandbox_status(options[:sandbox], project)
|
|
43
|
+
Typingpool::App.record_approved_hits_in_assignments_file(assignments_file, hits)
|
|
44
|
+
out_file = Typingpool::App.create_transcript(project, assignments_file)
|
|
45
|
+
STDERR.puts "Wrote #{out_file} to local folder #{project.name}."
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if options[:fixture]
|
|
49
|
+
Typingpool::App.vcr_stop
|
|
50
|
+
end
|
data/bin/tp-config
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'typingpool'
|
|
4
|
+
require 'highline'
|
|
5
|
+
require 'highline/import'
|
|
6
|
+
require 'securerandom'
|
|
7
|
+
require 'fileutils'
|
|
8
|
+
require 'optparse'
|
|
9
|
+
require 'open3'
|
|
10
|
+
|
|
11
|
+
options = {
|
|
12
|
+
:testing => false
|
|
13
|
+
}
|
|
14
|
+
OptionParser.new do |opts|
|
|
15
|
+
opts.banner = "USAGE: #{File.basename($PROGRAM_NAME)} [CONFIGFILE=#{Typingpool::Config.default_file}]\n\n"
|
|
16
|
+
opts.banner += "Installs or updates a Typingpool config file, prompting the user for\n"
|
|
17
|
+
opts.banner += "the minimal information to get up and running."
|
|
18
|
+
opts.on('--help',
|
|
19
|
+
'Display this screen.') do
|
|
20
|
+
puts opts
|
|
21
|
+
exit
|
|
22
|
+
end
|
|
23
|
+
opts.on('--test',
|
|
24
|
+
"Used by automated tests. Ignore.") do
|
|
25
|
+
options[:testing] = true
|
|
26
|
+
end
|
|
27
|
+
end.parse!
|
|
28
|
+
|
|
29
|
+
config_path = ARGV.first || Typingpool::Config.default_file
|
|
30
|
+
config_path_full = File.expand_path(config_path)
|
|
31
|
+
config = nil
|
|
32
|
+
if File.exists? config_path_full
|
|
33
|
+
abort "Not a file: #{config_path}" unless File.file? config_path_full
|
|
34
|
+
STDERR.puts "Editing existing config file '#{config_path}'"
|
|
35
|
+
begin
|
|
36
|
+
config = Typingpool::Config.file(config_path_full)
|
|
37
|
+
rescue ArgumentError, Psych::SyntaxError
|
|
38
|
+
abort "The specified config file is not valid YAML"
|
|
39
|
+
end #begin
|
|
40
|
+
else
|
|
41
|
+
abort "Invalid path '#{config_path}'" unless File.dirname(config_path_full) && File.directory?(File.dirname(config_path_full))
|
|
42
|
+
STDERR.puts "Making a new config file at '#{config_path}'"
|
|
43
|
+
config = Typingpool::Config.from_bundled_template
|
|
44
|
+
end #if File.exists? config_path
|
|
45
|
+
|
|
46
|
+
config.amazon ||= {}
|
|
47
|
+
config.amazon.key = ask('Your Amazon Web Services "Access Key ID"? '){|q| q.default = config.amazon.key if config.amazon.key }.to_s.chomp
|
|
48
|
+
abort "Cannot proceed without an Amazon Access Key ID" if config.amazon.key.empty?
|
|
49
|
+
config.amazon.secret = ask('Your Amazon Web Services "Secret Access Key"? '){|q| q.default = config.amazon.secret if config.amazon.secret }.to_s.chomp
|
|
50
|
+
abort "Cannot proceed without an Amazon Secret Access Key" if config.amazon.secret.empty?
|
|
51
|
+
|
|
52
|
+
unless options[:testing]
|
|
53
|
+
begin
|
|
54
|
+
RTurk.setup(config.amazon.key, config.amazon.secret, :sandbox => true)
|
|
55
|
+
RTurk.GetAccountBalance
|
|
56
|
+
STDERR.puts "Verified your new Amazon credentials"
|
|
57
|
+
rescue RTurk::InvalidRequest
|
|
58
|
+
abort "Your Amazon credentials do not appear to work. Please check them and run #{File.basename($PROGRAM_NAME)} again."
|
|
59
|
+
end #begin
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if not(config.amazon.bucket || (config.sftp && config.sftp.user))
|
|
63
|
+
config.amazon.bucket = ['typingpool', SecureRandom.hex(8)].join('-')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
unless config.transcripts
|
|
67
|
+
desktop_path = File.expand_path(File.join('~', 'Desktop'))
|
|
68
|
+
if File.exists?(desktop_path) && File.directory?(desktop_path)
|
|
69
|
+
config.transcripts = File.join(desktop_path, 'Transcripts')
|
|
70
|
+
else
|
|
71
|
+
config.transcripts = File.join('~', 'transcripts')
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
transcripts = nil
|
|
76
|
+
loop do
|
|
77
|
+
transcripts = ask('Working directory/folder for transcripts? '){|q| q.default = config['transcripts'] }.to_s.chomp
|
|
78
|
+
abort "Cannot proceed without a transcripts directory" if transcripts.empty?
|
|
79
|
+
if File.exists?(File.expand_path(transcripts))
|
|
80
|
+
if File.directory?(File.expand_path(transcripts))
|
|
81
|
+
break
|
|
82
|
+
else
|
|
83
|
+
STDERR.puts "Location #{transcripts} already taken by a file"
|
|
84
|
+
end #if File.directory?...
|
|
85
|
+
else
|
|
86
|
+
FileUtils.mkdir(File.expand_path(transcripts))
|
|
87
|
+
break
|
|
88
|
+
end #if File.exists?...
|
|
89
|
+
end #loop do
|
|
90
|
+
config.transcripts = transcripts
|
|
91
|
+
|
|
92
|
+
unless config.templates
|
|
93
|
+
transcripts_dir = File.basename(File.expand_path(config.transcripts))
|
|
94
|
+
transcripts_dir_capitalized = (transcripts_dir[0].upcase == transcripts_dir[0])
|
|
95
|
+
templates = transcripts_dir_capitalized ? 'Templates' : 'templates'
|
|
96
|
+
config.templates = File.join(config['transcripts'], templates)
|
|
97
|
+
FileUtils.mkdir(config.templates) unless File.exists? config.templates
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
unless config.cache
|
|
101
|
+
config.cache = File.join('~', '.typingpool.cache')
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
File.open(config_path_full, 'w') do |out|
|
|
105
|
+
out << YAML.dump(config.to_hash)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
STDERR.puts "Successfully wrote config to '#{config_path}'."
|
|
109
|
+
|
|
110
|
+
Typingpool::App.if_missing_dependencies do |missing|
|
|
111
|
+
missing.map!{|cmd| "`#{cmd}`" }
|
|
112
|
+
them = missing.count > 1 ? 'them' : 'it'
|
|
113
|
+
STDERR.puts "By the way, it looks like you're missing #{Typingpool::Utility.join_in_english(missing)}. You'll need to install #{them} before Typingpool can run."
|
|
114
|
+
end
|
data/bin/tp-finish
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require 'typingpool'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
|
|
7
|
+
options = {}
|
|
8
|
+
OptionParser.new do |commands|
|
|
9
|
+
options[:banner] = "USAGE: #{File.basename($PROGRAM_NAME)} PROJECT | --dead\n"
|
|
10
|
+
options[:banner] += " [--sandbox]\n [--config PATH]\n"
|
|
11
|
+
commands.banner = options[:banner]
|
|
12
|
+
commands.on('--dead',
|
|
13
|
+
"Remove ALL expired and rejected results, regardless of project.",
|
|
14
|
+
"Use in leiu of PROJECT.",
|
|
15
|
+
"Removes only Mechanical Turk HITs, not remote audio files") do
|
|
16
|
+
options[:dead] = true
|
|
17
|
+
end
|
|
18
|
+
commands.on('--sandbox',
|
|
19
|
+
"Test in Mechanical Turk's sandbox") do
|
|
20
|
+
options[:sandbox] = true
|
|
21
|
+
end
|
|
22
|
+
commands.on('--config=PATH',
|
|
23
|
+
"Default: #{Typingpool::Config.default_file}.",
|
|
24
|
+
"A config file") do |path|
|
|
25
|
+
options[:config] = path
|
|
26
|
+
end
|
|
27
|
+
commands.on('--help',
|
|
28
|
+
'Display this screen') do
|
|
29
|
+
STDERR.puts commands
|
|
30
|
+
exit
|
|
31
|
+
end
|
|
32
|
+
end.parse!
|
|
33
|
+
options[:banner] += "\n#{Typingpool::App::CLI.help_arg_explanation}\n"
|
|
34
|
+
config = Typingpool::App::CLI.config_from_arg(options[:config]) or abort "No config file at '#{options[:config]}'"
|
|
35
|
+
|
|
36
|
+
options[:project] = ARGV.shift
|
|
37
|
+
abort "Can't specify both PROJECT and --dead" if options[:project] && options[:dead]
|
|
38
|
+
abort "No PROJECT specified (and no --dead option)\n#{options[:banner]}" unless (options[:project] || options[:dead])
|
|
39
|
+
|
|
40
|
+
project=nil
|
|
41
|
+
assignments=nil
|
|
42
|
+
if options[:project]
|
|
43
|
+
project = Typingpool::App::CLI.project_from_arg_and_config(options[:project], config)
|
|
44
|
+
project.local.id or abort "Can't find project id in #{project.local.path}"
|
|
45
|
+
assignments = Typingpool::App.assignments_file_for_sandbox_status(options[:sandbox], project)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Typingpool::Amazon.setup(:sandbox => options[:sandbox], :config => config)
|
|
49
|
+
STDERR.puts "Removing from Amazon"
|
|
50
|
+
STDERR.puts " Collecting all results"
|
|
51
|
+
#Set up result set, depending on options
|
|
52
|
+
results = if project
|
|
53
|
+
Typingpool::Amazon::HIT.all_for_project(project.local.id)
|
|
54
|
+
elsif options[:dead]
|
|
55
|
+
Typingpool::Amazon::HIT.all.select do |result|
|
|
56
|
+
dead = ((result.full.expired_and_overdue? || result.rejected?) && result.ours?)
|
|
57
|
+
result.to_cache
|
|
58
|
+
dead
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#Remove the results from Mechanical Turk
|
|
63
|
+
fails = []
|
|
64
|
+
results.each do |result|
|
|
65
|
+
STDERR.puts " Removing HIT #{result.id} (#{result.full.status})"
|
|
66
|
+
begin
|
|
67
|
+
result.remove_from_amazon
|
|
68
|
+
rescue Typingpool::Error::Amazon::UnreviewedContent => e
|
|
69
|
+
fails.push(e)
|
|
70
|
+
else
|
|
71
|
+
if project
|
|
72
|
+
STDERR.puts " Removing from data/assignment.csv"
|
|
73
|
+
Typingpool::App.unrecord_hits_details_in_assignments_file(assignments, [result])
|
|
74
|
+
end
|
|
75
|
+
STDERR.puts " Removing from local cache"
|
|
76
|
+
Typingpool::Amazon::HIT.delete_cache(result.id)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
if fails.count > 0
|
|
80
|
+
STDERR.puts " Removed " + (results.size - fails.size) + " HITs from Amazon"
|
|
81
|
+
abort "#{fails.size} transcriptions are submitted but unprocessed (#{fails.join('; ')})"
|
|
82
|
+
end
|
|
83
|
+
#Remove the remote audio files associated with the results and update
|
|
84
|
+
#one of the assignment.csvs associated with the project
|
|
85
|
+
if project
|
|
86
|
+
#Don't want to delete audio if there's another assignments file
|
|
87
|
+
#relying on it
|
|
88
|
+
delete_types = if (options[:sandbox] || (File.exists? project.local.file('data', 'sandbox-assignment.csv')))
|
|
89
|
+
['assignment']
|
|
90
|
+
else
|
|
91
|
+
['audio', 'assignment']
|
|
92
|
+
end #if options[:sandbox] ||...
|
|
93
|
+
deleted = Typingpool::App.updelete_assignment_assets(project, assignments, assignments, delete_types) do |file|
|
|
94
|
+
STDERR.puts "Removing #{file} from #{project.remote.host}"
|
|
95
|
+
end
|
|
96
|
+
if options[:sandbox]
|
|
97
|
+
#delete sandbox-assignment.csv so we know it's safe to delete
|
|
98
|
+
#audio files when we finish using regular assignment.csv
|
|
99
|
+
FileUtils.remove_entry_secure project.local.file('data', 'sandbox-assignment.csv')
|
|
100
|
+
end
|
|
101
|
+
end #if project
|