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