psc 0.0.1
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/.gitignore +13 -0
- data/.rvmrc +2 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +20 -0
- data/LICENSE +20 -0
- data/README.md +163 -0
- data/Rakefile +69 -0
- data/cucumber.yml +13 -0
- data/features/step_definitions/eval_steps.rb +14 -0
- data/features/step_definitions/setup_steps.rb +7 -0
- data/features/studies.feature +21 -0
- data/features/support/env.rb +33 -0
- data/features/support/int_psc.rb +165 -0
- data/int-psc/README-int-psc.md +57 -0
- data/int-psc/hsqldb/baseline.log +197 -0
- data/int-psc/hsqldb/baseline.properties +17 -0
- data/int-psc/hsqldb/baseline.script +295 -0
- data/int-psc/hsqldb/datasource.properties +18 -0
- data/int-psc/hsqldb/datasource.script +2205 -0
- data/int-psc/jetty/jetty-runner-7.4.0.v20110414.jar +0 -0
- data/int-psc/state/ABC 1200.xml +115 -0
- data/int-psc/state/int-psc-state.xml +64 -0
- data/lib/psc.rb +47 -0
- data/lib/psc/client.rb +35 -0
- data/lib/psc/connection.rb +96 -0
- data/lib/psc/faraday.rb +11 -0
- data/lib/psc/faraday/http_basic.rb +35 -0
- data/lib/psc/faraday/psc_token.rb +42 -0
- data/lib/psc/faraday/string_is_xml.rb +25 -0
- data/lib/psc/version.rb +3 -0
- data/meta.rakefile +30 -0
- data/psc.gemspec +33 -0
- data/spec/fixtures/studies-json.http +37 -0
- data/spec/middleware_helper.rb +18 -0
- data/spec/psc/client_spec.rb +39 -0
- data/spec/psc/connection_spec.rb +156 -0
- data/spec/psc/faraday/http_basic_spec.rb +15 -0
- data/spec/psc/faraday/psc_token_spec.rb +38 -0
- data/spec/psc/faraday/string_is_xml_spec.rb +49 -0
- data/spec/psc/version_spec.rb +11 -0
- data/spec/psc_spec.rb +16 -0
- data/spec/spec_helper.rb +15 -0
- data/tasks/int-psc.rake +84 -0
- data/tasks/psc/TODO +3 -0
- data/tasks/psc/state.rb +393 -0
- metadata +311 -0
@@ -0,0 +1,11 @@
|
|
1
|
+
require File.expand_path("../../spec_helper.rb", __FILE__)
|
2
|
+
|
3
|
+
describe Psc, "::VERSION" do
|
4
|
+
it "exists" do
|
5
|
+
lambda { Psc::VERSION }.should_not raise_error
|
6
|
+
end
|
7
|
+
|
8
|
+
it "has 3 or 4 dot separated parts" do
|
9
|
+
Psc::VERSION.split('.').size.should be_between(3, 4)
|
10
|
+
end
|
11
|
+
end
|
data/spec/psc_spec.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path("../spec_helper", __FILE__)
|
2
|
+
|
3
|
+
describe Psc do
|
4
|
+
describe '.xml' do
|
5
|
+
it "provides an xml builder configured for PSC XML" do
|
6
|
+
Psc.xml('period', :id => 'foo') { |xml| xml.tag!('planned-activity') }.should ==
|
7
|
+
"<period id=\"foo\" xmlns=\"http://bioinformatics.northwestern.edu/ns/psc\">\n <planned-activity/>\n</period>\n"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '.xml_namespace' do
|
12
|
+
it 'is a hash suitable for use with nokogiri xpath' do
|
13
|
+
Psc.xml_namespace['psc'].should == 'http://bioinformatics.northwestern.edu/ns/psc'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
require 'rspec'
|
5
|
+
require 'webmock/rspec'
|
6
|
+
require File.expand_path("../middleware_helper.rb", __FILE__)
|
7
|
+
|
8
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
9
|
+
require 'psc'
|
10
|
+
|
11
|
+
RSpec.configure do
|
12
|
+
def http_fixture(name)
|
13
|
+
File.new(File.expand_path("../fixtures/#{name}.http", __FILE__))
|
14
|
+
end
|
15
|
+
end
|
data/tasks/int-psc.rake
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
require File.expand_path('../psc/state.rb', __FILE__)
|
4
|
+
require File.expand_path('../../features/support/int_psc.rb', __FILE__)
|
5
|
+
|
6
|
+
namespace 'int-psc' do
|
7
|
+
desc 'Ensure a psc.war is available for tests. Downloads the latest nightly if not.'
|
8
|
+
task :war => IntPsc.warfile
|
9
|
+
|
10
|
+
namespace :war do
|
11
|
+
directory IntPsc.path('downloads')
|
12
|
+
|
13
|
+
file IntPsc.path('downloads', 'archive.zip') => IntPsc.path('downloads') do |task|
|
14
|
+
sh [
|
15
|
+
'curl',
|
16
|
+
'-o', task.name,
|
17
|
+
"'https://ctms-ci.nubic.northwestern.edu/hudson/job/PSC%20nightly%20distribution/lastSuccessfulBuild/artifact/*zip*/archive.zip'"
|
18
|
+
].join(' ')
|
19
|
+
end
|
20
|
+
|
21
|
+
file IntPsc.warfile do
|
22
|
+
# only download if a psc.war wasn't separately provided
|
23
|
+
task(IntPsc.path('downloads', 'archive.zip')).invoke
|
24
|
+
|
25
|
+
cd IntPsc.path('downloads') do
|
26
|
+
sh "unzip -o archive.zip"
|
27
|
+
distpkg = Dir['archive/psc/target/artifacts/psc*.zip'].first
|
28
|
+
fail "No dist package in downloaded archive" unless distpkg
|
29
|
+
sh "unzip -o '#{distpkg}'"
|
30
|
+
pkgwar = Dir['psc-*/psc.war'].first
|
31
|
+
fail "No war in dist package" unless pkgwar
|
32
|
+
|
33
|
+
mkdir_p File.dirname(IntPsc.warfile)
|
34
|
+
cp pkgwar, IntPsc.warfile
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
task :clean_baseline do
|
40
|
+
rm_rf IntPsc.path('hsqldb')
|
41
|
+
end
|
42
|
+
|
43
|
+
task :recreate_baseline => [IntPsc.warfile, :clean_baseline] do
|
44
|
+
IntPsc.run('baseline') do
|
45
|
+
puts
|
46
|
+
puts "Please run through PSC's setup flow and create an all-powerful user with the"
|
47
|
+
puts "credentials superuser/superuser. PSC is running at"
|
48
|
+
puts
|
49
|
+
puts " #{IntPsc.url}"
|
50
|
+
puts
|
51
|
+
puts "When complete, come back here and press any key."
|
52
|
+
puts
|
53
|
+
STDIN.getc
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
task :clean_datasource do
|
58
|
+
cd IntPsc.path('hsqldb') do
|
59
|
+
Dir['baseline.*'].each { |fn| cp fn, fn.sub(/^baseline/, 'datasource') }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
desc 'Recreate the PSC integrated test instance from the state data in int-psc/state'
|
64
|
+
task :rebuild => [IntPsc.warfile, :clean_datasource] do
|
65
|
+
IntPsc.run do |int_psc|
|
66
|
+
int_psc.apply_state_and_mark_readonly
|
67
|
+
end
|
68
|
+
# the copied log file is not needed in the locked database
|
69
|
+
rm path('hsqldb', 'datasource.log')
|
70
|
+
end
|
71
|
+
|
72
|
+
desc 'Start up the integrated test PSC instance to poke around'
|
73
|
+
task :examine do
|
74
|
+
IntPsc.run do
|
75
|
+
puts "Integrated test PSC running at #{IntPsc.url}.\nPress any key to shut down."
|
76
|
+
STDIN.getc
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
desc 'Purge the logs for the integration test PSC instance'
|
81
|
+
task :clean_logs do
|
82
|
+
rm_rf IntPsc.path('deploy-base', 'logs')
|
83
|
+
end
|
84
|
+
end
|
data/tasks/psc/TODO
ADDED
data/tasks/psc/state.rb
ADDED
@@ -0,0 +1,393 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'builder'
|
4
|
+
require 'highline'
|
5
|
+
|
6
|
+
module Psc
|
7
|
+
class State
|
8
|
+
attr_accessor :sites, :templates, :registrations
|
9
|
+
|
10
|
+
def apply(connection)
|
11
|
+
StateApplier.new(self).apply(connection)
|
12
|
+
end
|
13
|
+
|
14
|
+
def template(ident)
|
15
|
+
templates.detect { |t| t.assigned_identifier == ident } or fail "No template #{ident}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.from_file(path)
|
19
|
+
self.new.tap do |state|
|
20
|
+
doc = Nokogiri::XML(open(path))
|
21
|
+
state.sites = read_sites(doc)
|
22
|
+
state.templates = read_templates(doc, File.dirname(path))
|
23
|
+
state.registrations = read_registrations(doc)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def self.parse_date(s)
|
30
|
+
case s
|
31
|
+
when /(\d{4})-(\d{1,2})-(\d{1,2})/
|
32
|
+
Date.new($1.to_i, $2.to_i, $3.to_i)
|
33
|
+
when /^\d+$/
|
34
|
+
RelativeDate.new s.to_i
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.read_sites(doc)
|
39
|
+
doc.xpath('/psc-state/site').collect do |site_elt|
|
40
|
+
Site.new(site_elt['name'], site_elt['assigned-identifier'])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.read_templates(doc, basedir)
|
45
|
+
doc.xpath('/psc-state/template').collect do |t_elt|
|
46
|
+
ident = t_elt['assigned-identifier']
|
47
|
+
Template.create(ident,
|
48
|
+
:filename =>
|
49
|
+
if filename = t_elt['file']
|
50
|
+
if filename =~ %r{^/}
|
51
|
+
filename
|
52
|
+
else
|
53
|
+
File.join(basedir, filename)
|
54
|
+
end
|
55
|
+
else
|
56
|
+
File.join(basedir, "#{ident}.xml")
|
57
|
+
end,
|
58
|
+
:participating_sites => t_elt.xpath('participating-site').collect { |ps_elt|
|
59
|
+
ParticipatingSite.create(
|
60
|
+
ps_elt['assigned-identifier'],
|
61
|
+
:approval => case ps_elt['approval']
|
62
|
+
when "false"
|
63
|
+
false
|
64
|
+
else
|
65
|
+
parse_date(ps_elt['approval'])
|
66
|
+
end
|
67
|
+
)
|
68
|
+
})
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.read_registrations(doc)
|
73
|
+
doc.xpath('/psc-state/registration').collect do |reg_elt|
|
74
|
+
Registration.create(
|
75
|
+
:subject => read_subject(reg_elt.xpath('subject').first),
|
76
|
+
:study_sites => reg_elt.xpath('study-site').collect { |study_elt|
|
77
|
+
StudySite.create(
|
78
|
+
%w(template site primary_coordinator study_subject_identifier desired_assignment_identifier).
|
79
|
+
inject({}) { |h, k| h[k] = study_elt[k.gsub('_', '-')]; h }.
|
80
|
+
merge(
|
81
|
+
:scheduled_segments => study_elt.xpath('scheduled-segment').collect { |seg_elt|
|
82
|
+
ScheduledSegment.create(seg_elt['segment'],
|
83
|
+
:mode => seg_elt['mode'],
|
84
|
+
:start => parse_date(seg_elt['start'])
|
85
|
+
)
|
86
|
+
})
|
87
|
+
)
|
88
|
+
})
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.read_subject(subject_elt)
|
93
|
+
Subject.create(
|
94
|
+
%w(first_name last_name person_id gender).inject({}) { |h, k|
|
95
|
+
h[k] = subject_elt[k.sub('_', '-')]; h
|
96
|
+
}.merge(
|
97
|
+
:birth_date => parse_date(subject_elt['birth-date']),
|
98
|
+
:properties => subject_elt.xpath('subject-property').
|
99
|
+
collect { |sp_elt| [sp_elt['name'], sp_elt['value']] }
|
100
|
+
)
|
101
|
+
)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
module BulkSettable
|
106
|
+
def self.included(struct_class)
|
107
|
+
struct_class.send(:extend, ClassMethods)
|
108
|
+
end
|
109
|
+
|
110
|
+
def attributes=(map)
|
111
|
+
map.each do |k, v|
|
112
|
+
setter = "#{k}="
|
113
|
+
if self.respond_to?(setter)
|
114
|
+
self.send setter, v
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
module ClassMethods
|
120
|
+
def create(*args)
|
121
|
+
attrs = args.pop
|
122
|
+
i = self.new(*args)
|
123
|
+
i.attributes = attrs
|
124
|
+
i
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class RelativeDate
|
130
|
+
include Comparable
|
131
|
+
|
132
|
+
attr_reader :days
|
133
|
+
|
134
|
+
def initialize(days)
|
135
|
+
@days = days
|
136
|
+
end
|
137
|
+
|
138
|
+
def <=>(other)
|
139
|
+
self.days <=> other.days
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_date
|
143
|
+
Date.today + days
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_s
|
147
|
+
to_date.to_s
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class Site < Struct.new(:name, :assigned_identifier)
|
152
|
+
include BulkSettable
|
153
|
+
|
154
|
+
def apply(connection)
|
155
|
+
connection.put(Psc.build_uri_path('sites', assigned_identifier),
|
156
|
+
Psc.xml('site', 'site-name' => name, 'assigned-identifier' => assigned_identifier).to_s,
|
157
|
+
'Content-Type' => 'text/xml')
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
class Template < Struct.new(:assigned_identifier)
|
162
|
+
include BulkSettable
|
163
|
+
|
164
|
+
attr_accessor :filename, :participating_sites
|
165
|
+
|
166
|
+
def apply(connection)
|
167
|
+
connection.put(Psc.build_uri_path('studies', assigned_identifier, 'template'),
|
168
|
+
File.read(filename), 'Content-Type' => 'text/xml')
|
169
|
+
(participating_sites || []).each do |ps|
|
170
|
+
ps.apply(connection, self)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def document
|
175
|
+
@document ||= Nokogiri::XML(open(filename, 'r'))
|
176
|
+
end
|
177
|
+
|
178
|
+
def resolve_segment(ident)
|
179
|
+
s = (
|
180
|
+
segment_index.detect { |s|
|
181
|
+
ident.downcase.split(/\s*:\s*/) == [s[:epoch_name].downcase, s[:name].downcase]
|
182
|
+
} ||
|
183
|
+
segment_index.detect { |s| ident == s[:id] } ||
|
184
|
+
segment_index.detect { |s| ident == s[:name] }
|
185
|
+
)
|
186
|
+
raise "Unable to resolve segment #{ident.inspect}" unless s
|
187
|
+
s[:id]
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
|
192
|
+
def segment_index
|
193
|
+
# TODO: this will not resolve the enclosing epoch correctly for
|
194
|
+
# segments added in amendments.
|
195
|
+
@ss_index ||= document.css('epoch').inject({}) { |h, ep_elt|
|
196
|
+
(h[ep_elt['name']] ||= []).push(
|
197
|
+
*ep_elt.css('study-segment').collect { |ss_elt|
|
198
|
+
{ :id => ss_elt['id'], :name => ss_elt['name'] }
|
199
|
+
})
|
200
|
+
h
|
201
|
+
}.collect { |epoch, segments|
|
202
|
+
segments.each { |seg| seg[:epoch_name] = epoch }; segments
|
203
|
+
}.flatten
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
class ParticipatingSite < Struct.new(:assigned_identifier)
|
208
|
+
include BulkSettable
|
209
|
+
|
210
|
+
attr_accessor :approval
|
211
|
+
|
212
|
+
def approval
|
213
|
+
@approval = RelativeDate.new(0) if @approval.nil?
|
214
|
+
@approval
|
215
|
+
end
|
216
|
+
|
217
|
+
def apply(connection, template)
|
218
|
+
study_site_path = Psc.build_uri_path(
|
219
|
+
'studies', template.assigned_identifier, 'sites', assigned_identifier)
|
220
|
+
connection.put study_site_path, "<study-site-link/>", 'Content-Type' => 'text/xml'
|
221
|
+
if approval
|
222
|
+
template.document.css('amendment').
|
223
|
+
collect { |am_elt| [am_elt['date'], am_elt['name']].compact }.
|
224
|
+
sort { |a, b| a[0] <=> b[0] }.
|
225
|
+
collect { |pair| pair.join('~') }.
|
226
|
+
each do |am_ident|
|
227
|
+
connection.post "#{study_site_path}/approvals",
|
228
|
+
Psc.xml('amendment-approval', :date => approval.to_s, :amendment => am_ident).to_s,
|
229
|
+
'Content-Type' => 'text/xml'
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
class Registration
|
236
|
+
include BulkSettable
|
237
|
+
attr_accessor :subject, :study_sites
|
238
|
+
|
239
|
+
def apply(connection, state)
|
240
|
+
study_sites.each do |study_site|
|
241
|
+
study_site.apply(connection, subject, state)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
class Subject
|
247
|
+
include BulkSettable
|
248
|
+
attr_accessor :first_name, :last_name, :gender, :birth_date, :person_id, :properties
|
249
|
+
|
250
|
+
def build_on(xml_builder)
|
251
|
+
xml_builder.subject(
|
252
|
+
'first-name' => first_name,
|
253
|
+
'last-name' => last_name,
|
254
|
+
'gender' => gender,
|
255
|
+
'person-id' => person_id,
|
256
|
+
'birth-date' => birth_date.to_s
|
257
|
+
) { |subj_elt|
|
258
|
+
(properties || []).each { |prop|
|
259
|
+
subj_elt.property(:name => prop[0], :value => prop[1])
|
260
|
+
}
|
261
|
+
}
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
class StudySite
|
266
|
+
include BulkSettable
|
267
|
+
attr_accessor :template, :site, :study_subject_identifier, :primary_coordinator,
|
268
|
+
:desired_assignment_identifier, :scheduled_segments
|
269
|
+
|
270
|
+
def apply(connection, subject, state)
|
271
|
+
template_details = state.template(template)
|
272
|
+
|
273
|
+
schedule_resp = connection.post(
|
274
|
+
Psc.build_uri_path('studies', template, 'sites', site, 'subject-assignments'),
|
275
|
+
Psc.xml(
|
276
|
+
'registration',
|
277
|
+
{
|
278
|
+
'subject-coordinator-name' => primary_coordinator,
|
279
|
+
'desired-assignment-id' => desired_assignment_identifier,
|
280
|
+
'study-subject-id' => study_subject_identifier,
|
281
|
+
'first-study-segment-id' => template_details.resolve_segment(
|
282
|
+
scheduled_segments.first.identifier),
|
283
|
+
'date' => (scheduled_segments.first.start || Psc::RelativeDate.new(0)).to_s
|
284
|
+
}.reject { |k, v| !v }
|
285
|
+
) { |reg_elt| subject.build_on(reg_elt) },
|
286
|
+
'Content-Type' => 'text/xml'
|
287
|
+
)
|
288
|
+
|
289
|
+
schedule_url = schedule_resp.headers['Location']
|
290
|
+
|
291
|
+
scheduled_segments[1, scheduled_segments.size].each { |ss|
|
292
|
+
ss.apply(connection, template_details, schedule_url)
|
293
|
+
}
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
class ScheduledSegment < Struct.new(:identifier)
|
298
|
+
include BulkSettable
|
299
|
+
attr_accessor :start, :mode
|
300
|
+
|
301
|
+
def mode
|
302
|
+
@mode ||= "per-protocol"
|
303
|
+
end
|
304
|
+
|
305
|
+
def apply(connection, template, schedule_url)
|
306
|
+
connection.post(schedule_url,
|
307
|
+
Psc.xml('next-scheduled-study-segment',
|
308
|
+
'study-segment-id' => template.resolve_segment(identifier),
|
309
|
+
'start-date' => start.to_s,
|
310
|
+
'mode' => mode
|
311
|
+
), 'Content-Type' => 'text/xml')
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
class StateApplier
|
316
|
+
def initialize(state, out=DefaultOutput.new)
|
317
|
+
@state = state
|
318
|
+
@out = out
|
319
|
+
end
|
320
|
+
|
321
|
+
def apply(connection)
|
322
|
+
c = ConnectionProxy.new(connection, @out)
|
323
|
+
puts (@state.sites || []).collect { |s|
|
324
|
+
@out.monitor("Adding site #{s.assigned_identifier}") { s.apply(c) }
|
325
|
+
}.compact.tap { |result| return false if result.size > 0 }
|
326
|
+
|
327
|
+
(@state.templates || []).collect { |t|
|
328
|
+
@out.monitor("Adding template #{t.assigned_identifier}") { t.apply(c) }
|
329
|
+
}.compact.tap { |result| return false if result.size > 0 }
|
330
|
+
|
331
|
+
(@state.registrations || []).collect { |r|
|
332
|
+
@out.monitor("Registering #{r.subject.first_name} #{r.subject.last_name}") {
|
333
|
+
r.apply(c, @state)
|
334
|
+
}
|
335
|
+
}.compact.tap { |result| return false if result.size > 0 }
|
336
|
+
end
|
337
|
+
|
338
|
+
class ConnectionProxy
|
339
|
+
def initialize(conn, out)
|
340
|
+
@conn = conn
|
341
|
+
@out = out
|
342
|
+
end
|
343
|
+
|
344
|
+
def make_request(method, *args)
|
345
|
+
@out.trace("#{method.to_s.upcase} #{args.first}")
|
346
|
+
resp = @conn.send(method, *args)
|
347
|
+
if resp.respond_to?(:status) && resp.status > 299
|
348
|
+
raise resp.body
|
349
|
+
end
|
350
|
+
resp
|
351
|
+
end
|
352
|
+
|
353
|
+
def method_missing(m, *args)
|
354
|
+
if %w(get put post delete patch).include?(m.to_s)
|
355
|
+
make_request(m, *args)
|
356
|
+
else
|
357
|
+
super
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
class DefaultOutput
|
363
|
+
def initialize
|
364
|
+
@hl = HighLine.new
|
365
|
+
HighLine.color_scheme = HighLine::SampleColorScheme.new
|
366
|
+
end
|
367
|
+
|
368
|
+
def monitor(msg)
|
369
|
+
@hl.say("<%= color('*', :info) %> #{msg}")
|
370
|
+
begin
|
371
|
+
yield
|
372
|
+
nil
|
373
|
+
rescue => e
|
374
|
+
@hl.say("<%= color(' -', :error) %> #{e}")
|
375
|
+
false
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
def trace(msg)
|
380
|
+
@hl.say("<%= color(' +', :info) %> #{msg}")
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
def self.xml(root_name, root_attributes={}, &block)
|
386
|
+
root_attributes['xmlns'] = 'http://bioinformatics.northwestern.edu/ns/psc'
|
387
|
+
Builder::XmlMarkup.new(:indent => 2).tag!(root_name, root_attributes, &block)
|
388
|
+
end
|
389
|
+
|
390
|
+
def self.build_uri_path(*parts)
|
391
|
+
parts.collect { |p| URI.encode p }.join('/')
|
392
|
+
end
|
393
|
+
end
|