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
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
|