psc 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|