solvebio 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/.travis.yml +13 -0
- data/Gemfile +4 -0
- data/Gemspec +3 -0
- data/LICENSE +21 -0
- data/Makefile +17 -0
- data/README.md +64 -0
- data/Rakefile +59 -0
- data/bin/solvebio.rb +36 -0
- data/demo/README.md +14 -0
- data/demo/dataset/facets.rb +13 -0
- data/demo/dataset/field.rb +13 -0
- data/demo/depository/README.md +24 -0
- data/demo/depository/all.rb +13 -0
- data/demo/depository/retrieve.rb +13 -0
- data/demo/depository/versions-all.rb +13 -0
- data/demo/query/query-filter.rb +30 -0
- data/demo/query/query.rb +13 -0
- data/demo/query/range-filter.rb +18 -0
- data/demo/test-api.rb +98 -0
- data/lib/apiresource.rb +130 -0
- data/lib/cli/auth.rb +122 -0
- data/lib/cli/help.rb +13 -0
- data/lib/cli/irb.rb +58 -0
- data/lib/cli/irbrc.rb +53 -0
- data/lib/cli/options.rb +75 -0
- data/lib/client.rb +152 -0
- data/lib/credentials.rb +67 -0
- data/lib/errors.rb +81 -0
- data/lib/filter.rb +312 -0
- data/lib/help.rb +46 -0
- data/lib/locale.rb +47 -0
- data/lib/main.rb +37 -0
- data/lib/query.rb +415 -0
- data/lib/resource.rb +414 -0
- data/lib/solvebio.rb +14 -0
- data/lib/solveobject.rb +101 -0
- data/lib/tabulate.rb +706 -0
- data/solvebio.gemspec +75 -0
- data/test/data/netrc-save +6 -0
- data/test/helper.rb +3 -0
- data/test/test-auth.rb +54 -0
- data/test/test-client.rb +27 -0
- data/test/test-error.rb +36 -0
- data/test/test-filter.rb +70 -0
- data/test/test-netrc.rb +42 -0
- data/test/test-query-batch.rb +60 -0
- data/test/test-query-init.rb +29 -0
- data/test/test-query-paging.rb +123 -0
- data/test/test-query.rb +88 -0
- data/test/test-resource.rb +47 -0
- data/test/test-solveobject.rb +27 -0
- data/test/test-tabulate.rb +127 -0
- metadata +158 -0
data/lib/resource.rb
ADDED
@@ -0,0 +1,414 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# from utils.tabulate import tabulate
|
3
|
+
|
4
|
+
require_relative 'solveobject'
|
5
|
+
require_relative 'apiresource'
|
6
|
+
require_relative 'client'
|
7
|
+
require_relative 'query'
|
8
|
+
require_relative 'help'
|
9
|
+
|
10
|
+
class SolveBio::ListObject < SolveBio::SolveObject
|
11
|
+
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
def all(params={})
|
15
|
+
return request('get', self['url'], params)
|
16
|
+
end
|
17
|
+
|
18
|
+
def create(params={})
|
19
|
+
return request('post', self['url'], params)
|
20
|
+
end
|
21
|
+
|
22
|
+
def next_page(params={})
|
23
|
+
if self['links']['next']
|
24
|
+
return request('get', self['links']['next'], params)
|
25
|
+
end
|
26
|
+
return nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def prev_page(params={})
|
30
|
+
if self['links']['prev']
|
31
|
+
request('get', self['links']['prev'], params)
|
32
|
+
end
|
33
|
+
return nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def at(i)
|
37
|
+
self.to_a[i]
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_a
|
41
|
+
return to_solve_object(self['data'])
|
42
|
+
end
|
43
|
+
|
44
|
+
def each(*pass)
|
45
|
+
return self unless block_given?
|
46
|
+
i = 0
|
47
|
+
ary = self.dup
|
48
|
+
done = false
|
49
|
+
until done
|
50
|
+
if i >= ary['data'].size
|
51
|
+
ary = next_page
|
52
|
+
break unless ary
|
53
|
+
i = 0
|
54
|
+
end
|
55
|
+
yield(ary.at(i))
|
56
|
+
i += 1
|
57
|
+
end
|
58
|
+
return self
|
59
|
+
end
|
60
|
+
|
61
|
+
def first
|
62
|
+
self['data'][0]
|
63
|
+
end
|
64
|
+
|
65
|
+
# def max
|
66
|
+
# self['data'][self['total']]
|
67
|
+
# end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
class SingletonAPIResource < SolveBio::APIResource
|
73
|
+
|
74
|
+
def self.retrieve(cls)
|
75
|
+
return super(SingletonAPIResource, cls).retrieve(nil)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.class_url(cls)
|
79
|
+
# cls_name = cls.class_name()
|
80
|
+
cls_name = cls.to_s.sub('SolveBio::', '')
|
81
|
+
cls_name = camelcase_to_underscore(cls_name)
|
82
|
+
return "/v1/%s #{cls_name}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def instance_url
|
86
|
+
class_url()
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
# API resources
|
92
|
+
|
93
|
+
class SolveBio::User < SingletonAPIResource
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
class SolveBio::Depository < SolveBio::APIResource
|
98
|
+
|
99
|
+
include SolveBio::CreateableAPIResource
|
100
|
+
include SolveBio::ListableAPIResource
|
101
|
+
include SolveBio::SearchableAPIResource
|
102
|
+
include SolveBio::UpdateableAPIResource
|
103
|
+
include SolveBio::HelpableAPIResource
|
104
|
+
|
105
|
+
ALLOW_FULL_NAME_ID = true
|
106
|
+
FULL_NAME_REGEX = %r{^[\w\-\.]+$}
|
107
|
+
|
108
|
+
# lookup by ID or full name
|
109
|
+
def self.retrieve(id, params={})
|
110
|
+
if id.kind_of?(String)
|
111
|
+
_id = id.strip
|
112
|
+
id = nil
|
113
|
+
if _id =~ FULL_NAME_REGEX
|
114
|
+
params['full_name'] = _id
|
115
|
+
else
|
116
|
+
raise Exception, 'Unrecognized full name: "%s"' % _id
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
return SolveBio::APIResource.
|
121
|
+
retrieve(SolveBio::Depository, id, params)
|
122
|
+
end
|
123
|
+
|
124
|
+
def versions_url
|
125
|
+
return SolveBio::APIResource.
|
126
|
+
retrieve(SolveBio::Depository, self['id'])['versions_url']
|
127
|
+
end
|
128
|
+
|
129
|
+
def versions(name=nil, params={})
|
130
|
+
# construct the depo version full name
|
131
|
+
return SolveBio::DepositoryVersion.
|
132
|
+
retrieve("#{self['full_name']}/#{name}") if name
|
133
|
+
|
134
|
+
response = SolveBio::Client.
|
135
|
+
client.request('get', versions_url, params)
|
136
|
+
return response.to_solvebio
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
class SolveBio::DepositoryVersion < SolveBio::APIResource
|
142
|
+
|
143
|
+
|
144
|
+
include SolveBio::CreateableAPIResource
|
145
|
+
include SolveBio::ListableAPIResource
|
146
|
+
include SolveBio::UpdateableAPIResource
|
147
|
+
include SolveBio::HelpableAPIResource
|
148
|
+
|
149
|
+
ALLOW_FULL_NAME_ID = true
|
150
|
+
|
151
|
+
# FIXME: base off of Depository::FULL_NAME_REGEX
|
152
|
+
# Sample matches:
|
153
|
+
# 'Clinvar/2.0.0-1'
|
154
|
+
FULL_NAME_REGEX = %r{^[\w\.]+/[\w\-\.]+$}
|
155
|
+
|
156
|
+
# Supports lookup by full name
|
157
|
+
def self.retrieve(id, params={})
|
158
|
+
if id.kind_of?(String)
|
159
|
+
_id = id.strip
|
160
|
+
id = nil
|
161
|
+
if _id =~ FULL_NAME_REGEX
|
162
|
+
params['full_name'] = _id
|
163
|
+
else
|
164
|
+
raise Exception, 'Unrecognized full name.'
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
return SolveBio::APIResource.
|
169
|
+
retrieve(SolveBio::DepositoryVersion, id, params)
|
170
|
+
end
|
171
|
+
|
172
|
+
def datasets_url(name=nil)
|
173
|
+
name ||= self['name']
|
174
|
+
"#{self['full_name']}/#{name}"
|
175
|
+
end
|
176
|
+
|
177
|
+
def datasets(name=nil, params={})
|
178
|
+
if name
|
179
|
+
# construct the dataset full name
|
180
|
+
return SolveBio::Dataset.retrieve(datasets_url(name))
|
181
|
+
end
|
182
|
+
|
183
|
+
response = SolveBio::Client.
|
184
|
+
client.request('get', datasets_url, params)
|
185
|
+
return response.to_solvebio
|
186
|
+
end
|
187
|
+
|
188
|
+
# Set the released flag and optional release date and save
|
189
|
+
def release(released_at=nil)
|
190
|
+
if released_at
|
191
|
+
@released_at = released_at
|
192
|
+
end
|
193
|
+
@released = true
|
194
|
+
save()
|
195
|
+
end
|
196
|
+
|
197
|
+
# Unset the released flag and save
|
198
|
+
def unrelease
|
199
|
+
@released = false
|
200
|
+
save()
|
201
|
+
end
|
202
|
+
|
203
|
+
# FIXME: is there a better field to sort on?
|
204
|
+
def <=>(other)
|
205
|
+
self.id <=> other.id
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
class SolveBio::Dataset < SolveBio::APIResource
|
211
|
+
|
212
|
+
include SolveBio::CreateableAPIResource
|
213
|
+
include SolveBio::ListableAPIResource
|
214
|
+
include SolveBio::UpdateableAPIResource
|
215
|
+
include SolveBio::HelpableAPIResource
|
216
|
+
|
217
|
+
ALLOW_FULL_NAME_ID = true
|
218
|
+
|
219
|
+
# FIXME: base off of DepositoryVersion::FULL_NAME_REGEX
|
220
|
+
# Sample matches:
|
221
|
+
# 'Clinvar/2.0.0-1/Variants'
|
222
|
+
# 'omim/0.0.1-1/omim'
|
223
|
+
FULL_NAME_REGEX = %r{^([\w\-\.]+/){2}[\w\-\.]+$}
|
224
|
+
|
225
|
+
# Dataset lookup by full string name
|
226
|
+
def self.retrieve(id, params={})
|
227
|
+
if id.kind_of?(String)
|
228
|
+
_id = id.strip
|
229
|
+
id = nil
|
230
|
+
if _id =~ FULL_NAME_REGEX
|
231
|
+
params['full_name'] = _id
|
232
|
+
else
|
233
|
+
raise Exception, 'Unrecognized full name.'
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
return SolveBio::APIResource.
|
238
|
+
retrieve(SolveBio::Dataset, id, params)
|
239
|
+
end
|
240
|
+
|
241
|
+
def depository_version
|
242
|
+
return SolveBio::DepositoryVersion.
|
243
|
+
retrieve(self['depository_version'])
|
244
|
+
end
|
245
|
+
|
246
|
+
def depository
|
247
|
+
return SolveBio::Depository.retrieve(self['depository'])
|
248
|
+
end
|
249
|
+
|
250
|
+
def fields(name=nil, params={})
|
251
|
+
unless self['fields_url']
|
252
|
+
raise Exception,
|
253
|
+
'Please use Dataset.retrieve({ID}) before doing looking ' +
|
254
|
+
'up fields'
|
255
|
+
end
|
256
|
+
|
257
|
+
if name
|
258
|
+
# construct the field's full_name if a field name is provided
|
259
|
+
return DatasetField.retrieve("#{self['full_name']}/#{name}")
|
260
|
+
end
|
261
|
+
|
262
|
+
SolveBio::Client.
|
263
|
+
client.request('get', self['fields_url']).to_solvebio
|
264
|
+
end
|
265
|
+
|
266
|
+
def query(params={})
|
267
|
+
paging = false
|
268
|
+
if params.member?(:paging)
|
269
|
+
paging = params[:paging]
|
270
|
+
params.delete(:paging)
|
271
|
+
end
|
272
|
+
q = paging ? SolveBio::PagingQuery.new(self['id'], params) :
|
273
|
+
SolveBio::Query.new(self['id'], params)
|
274
|
+
|
275
|
+
if params[:filters]
|
276
|
+
return q.filter(params[:filters])
|
277
|
+
end
|
278
|
+
return q
|
279
|
+
end
|
280
|
+
|
281
|
+
private
|
282
|
+
def data_url
|
283
|
+
unless self['data_url']
|
284
|
+
unless self['id']
|
285
|
+
raise Exception,
|
286
|
+
'No Dataset ID was provided. ' +
|
287
|
+
'Please instantiate the Dataset ' +
|
288
|
+
'object with an ID or full_name.'
|
289
|
+
end
|
290
|
+
# automatically construct the data_url from the ID
|
291
|
+
return instance_url() + '/data'
|
292
|
+
end
|
293
|
+
return self['data_url']
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
297
|
+
|
298
|
+
class SolveBio::DatasetField < SolveBio::APIResource
|
299
|
+
|
300
|
+
include SolveBio::CreateableAPIResource
|
301
|
+
include SolveBio::ListableAPIResource
|
302
|
+
include SolveBio::UpdateableAPIResource
|
303
|
+
|
304
|
+
ALLOW_FULL_NAME_ID = true
|
305
|
+
FULL_NAME_REGEX = %r{^([\w\-\.]+/){3}[\w\-\.]+$}
|
306
|
+
|
307
|
+
# Supports lookup by ID or full name
|
308
|
+
def self.retrieve(id, params={})
|
309
|
+
if id.kind_of?(String)
|
310
|
+
_id = id.strip
|
311
|
+
id = nil
|
312
|
+
if FULL_NAME_REGEX =~ _id
|
313
|
+
params['full_name'] = _id
|
314
|
+
else
|
315
|
+
raise Exception, 'Unrecognized full name.'
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
return SolveBio::APIResource.
|
320
|
+
retrieve(SolveBio::DatasetField, id, params)
|
321
|
+
end
|
322
|
+
|
323
|
+
def facets_url
|
324
|
+
return "/v1/dataset_fields/#{self.id}/facets"
|
325
|
+
end
|
326
|
+
|
327
|
+
def facets(params={})
|
328
|
+
response = SolveBio::Client.
|
329
|
+
client.request('get', facets_url, params)
|
330
|
+
return response.to_solvebio
|
331
|
+
end
|
332
|
+
|
333
|
+
def help
|
334
|
+
facets
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
SolveBio::SolveObject::CONVERSION = {
|
339
|
+
'Depository' => SolveBio::Depository,
|
340
|
+
'DepositoryVersion' => SolveBio::DepositoryVersion,
|
341
|
+
'Dataset' => SolveBio::Dataset,
|
342
|
+
'DatasetField' => SolveBio::DatasetField,
|
343
|
+
'User' => SolveBio::User,
|
344
|
+
'list' => SolveBio::ListObject
|
345
|
+
}
|
346
|
+
|
347
|
+
class Hash
|
348
|
+
def to_solvebio
|
349
|
+
resp = self.dup()
|
350
|
+
klass_name = resp['class_name']
|
351
|
+
if klass_name.kind_of?(String)
|
352
|
+
klass = SolveBio::SolveObject::CONVERSION[klass_name] ||
|
353
|
+
SolveBio::SolveObject
|
354
|
+
else
|
355
|
+
klass = SolveBio::SolveObject
|
356
|
+
end
|
357
|
+
SolveBio::SolveObject::construct_from(klass, resp)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
class Array
|
362
|
+
def to_solvebio
|
363
|
+
return self.map{|i| to_solve_object(i)}
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
|
368
|
+
def to_solve_object(resp)
|
369
|
+
if resp.kind_of?(Array) or
|
370
|
+
(not resp.kind_of? SolveBio::SolveObject and resp.kind_of?(Hash))
|
371
|
+
resp.to_solvebio
|
372
|
+
else
|
373
|
+
return resp
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
if __FILE__ == $0
|
378
|
+
puts '-' * 50
|
379
|
+
resp = {
|
380
|
+
'class_name' => 'Dataset',
|
381
|
+
'data_url' => 'https://api.solvebio.com/v1/datasets/25/data',
|
382
|
+
'depository' => 'ClinVar',
|
383
|
+
'depository_id' => 223,
|
384
|
+
'depository_version' => 'ClinVar/2.0.0-1',
|
385
|
+
'depository_version_id' => 15,
|
386
|
+
'description' => '',
|
387
|
+
'fields_url' => 'https://api.solvebio.com/v1/datasets/25/fields',
|
388
|
+
'full_name' => 'ClinVar/2.0.0-1/Variants',
|
389
|
+
'id' => 25,
|
390
|
+
'name' => 'Variants',
|
391
|
+
'title' => 'Variants',
|
392
|
+
'url' => 'https://api.solvebio.com/v1/datasets/25'
|
393
|
+
}
|
394
|
+
so = to_solve_object(resp)
|
395
|
+
so = resp.to_solvebio
|
396
|
+
puts so.inspect
|
397
|
+
puts so.to_s
|
398
|
+
|
399
|
+
if ARGV[0]
|
400
|
+
require_relative './cli/auth.rb'
|
401
|
+
include SolveBio::Auth
|
402
|
+
login
|
403
|
+
puts '-' * 30, ' HELP ', '-' * 30
|
404
|
+
puts SolveBio::Depository.retrieve('ClinVar').help
|
405
|
+
puts '-' * 30, ' Retrieve ClinVar ','-' * 30
|
406
|
+
puts SolveBio::Depository.retrieve('ClinVar').to_s
|
407
|
+
puts '-' * 30, ' Versions ClinVar ','-' * 30
|
408
|
+
puts SolveBio::Depository.retrieve('Clinvar').versions.to_s
|
409
|
+
puts '-' * 30, ' Dataset ','-' * 30
|
410
|
+
puts SolveBio::Dataset.retrieve('Clinvar/2.0.0-1/Variants').to_s
|
411
|
+
puts '-' * 30, ' All Depository ','-' * 30
|
412
|
+
puts SolveBio::Depository.all.to_s
|
413
|
+
end
|
414
|
+
end
|
data/lib/solvebio.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# Something to pull in the entire SolveBio API.
|
3
|
+
|
4
|
+
require_relative 'resource'
|
5
|
+
require_relative 'query'
|
6
|
+
|
7
|
+
# cli/auth is a little nicer than credentials
|
8
|
+
# FIXME: consider moving cli/auth moving out of cli?
|
9
|
+
require_relative 'cli/auth'
|
10
|
+
|
11
|
+
# Set authentication if possible
|
12
|
+
include SolveBio::Credentials
|
13
|
+
creds = get_credentials()
|
14
|
+
SolveBio.api_key = SolveBio::Client.client.api_key = creds[1] if creds
|
data/lib/solveobject.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'set'
|
5
|
+
require_relative 'client'
|
6
|
+
|
7
|
+
# Add underscore before internal uppercase letters. Also, lowercase
|
8
|
+
# all letters.
|
9
|
+
def camelcase_to_underscore(name)
|
10
|
+
# Using [[:upper:]] and [[:lower]] should help with Unicode.
|
11
|
+
s1 = name.gsub(/(.)([[:upper:]])([[:lower:]]+)/){"#{$1}_#{$2}#{$3}"}
|
12
|
+
return (s1.gsub(/([a-z0-9])([[:upper:]])/){"#{$1}_#{$2}"}).downcase
|
13
|
+
end
|
14
|
+
|
15
|
+
# Base class for all SolveBio API resource objects
|
16
|
+
class SolveBio::SolveObject < Hash
|
17
|
+
|
18
|
+
ALLOW_FULL_NAME_ID = false # Treat full_name parameter as an ID?
|
19
|
+
|
20
|
+
attr_reader :unsaved_values
|
21
|
+
|
22
|
+
def allow_full_name_id
|
23
|
+
self.class.const_get(:ALLOW_FULL_NAME_ID)
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(id=nil, params={})
|
27
|
+
|
28
|
+
super()
|
29
|
+
# store manually updated values for partial updates
|
30
|
+
@unsaved_values = Set.new
|
31
|
+
|
32
|
+
if id
|
33
|
+
self['id'] = id
|
34
|
+
elsif allow_full_name_id and params['full_name']
|
35
|
+
self['full_name'] = params['full_name']
|
36
|
+
# no ID was provided so temporarily set the id as full_name
|
37
|
+
# this will get updated when the resource is refreshed
|
38
|
+
self['id'] = params['full_name']
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Element Reference — Retrieves the value object corresponding to the key object.
|
43
|
+
# Note: *key* is turned into a string before access, because the underlying key type
|
44
|
+
# is a string.
|
45
|
+
def [](key)
|
46
|
+
return super(key.to_s)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.construct_from(cls, values)
|
50
|
+
instance = cls.new(values['id'])
|
51
|
+
instance.refresh_from(values)
|
52
|
+
return instance
|
53
|
+
end
|
54
|
+
|
55
|
+
def refresh_from(values)
|
56
|
+
self.clear()
|
57
|
+
@unsaved_values = Set.new
|
58
|
+
values.each { |k, v| self[k] = to_solve_object(v) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def request(method, url, params=nil)
|
62
|
+
response = SolveBio::Client.client.request(method, url, params)
|
63
|
+
return to_solve_object(response)
|
64
|
+
end
|
65
|
+
|
66
|
+
def inspect
|
67
|
+
ident_parts = [self.class]
|
68
|
+
|
69
|
+
if self['id'].kind_of?(Integer)
|
70
|
+
ident_parts << "id=#{self['id']}"
|
71
|
+
end
|
72
|
+
|
73
|
+
if allow_full_name_id and self['full_name']
|
74
|
+
ident_parts << "full_name=#{self['full_name']}"
|
75
|
+
end
|
76
|
+
|
77
|
+
return '<%s:%x> JSON: %s' % [ident_parts.join(' '),
|
78
|
+
self.object_id, self.to_json]
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_s
|
83
|
+
# No equivalent of Python's json sort_keys?
|
84
|
+
return JSON.pretty_generate(self, :indent => ' ')
|
85
|
+
# return self.to_json json.dumps(self, sort_keys=true, indent=2)
|
86
|
+
end
|
87
|
+
|
88
|
+
# @property
|
89
|
+
def id
|
90
|
+
return self['id']
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
if __FILE__ == $0
|
95
|
+
%w(abc abcDef abc01Def aBcDef a1B2C3 ?Foo Dataset).each do |word|
|
96
|
+
puts word + " -> " + camelcase_to_underscore(word)
|
97
|
+
end
|
98
|
+
puts SolveBio::SolveObject.new.inspect
|
99
|
+
puts SolveBio::SolveObject.new(64).inspect
|
100
|
+
|
101
|
+
end
|