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