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