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.
Files changed (54) hide show
  1. data/.gitignore +7 -0
  2. data/.travis.yml +13 -0
  3. data/Gemfile +4 -0
  4. data/Gemspec +3 -0
  5. data/LICENSE +21 -0
  6. data/Makefile +17 -0
  7. data/README.md +64 -0
  8. data/Rakefile +59 -0
  9. data/bin/solvebio.rb +36 -0
  10. data/demo/README.md +14 -0
  11. data/demo/dataset/facets.rb +13 -0
  12. data/demo/dataset/field.rb +13 -0
  13. data/demo/depository/README.md +24 -0
  14. data/demo/depository/all.rb +13 -0
  15. data/demo/depository/retrieve.rb +13 -0
  16. data/demo/depository/versions-all.rb +13 -0
  17. data/demo/query/query-filter.rb +30 -0
  18. data/demo/query/query.rb +13 -0
  19. data/demo/query/range-filter.rb +18 -0
  20. data/demo/test-api.rb +98 -0
  21. data/lib/apiresource.rb +130 -0
  22. data/lib/cli/auth.rb +122 -0
  23. data/lib/cli/help.rb +13 -0
  24. data/lib/cli/irb.rb +58 -0
  25. data/lib/cli/irbrc.rb +53 -0
  26. data/lib/cli/options.rb +75 -0
  27. data/lib/client.rb +152 -0
  28. data/lib/credentials.rb +67 -0
  29. data/lib/errors.rb +81 -0
  30. data/lib/filter.rb +312 -0
  31. data/lib/help.rb +46 -0
  32. data/lib/locale.rb +47 -0
  33. data/lib/main.rb +37 -0
  34. data/lib/query.rb +415 -0
  35. data/lib/resource.rb +414 -0
  36. data/lib/solvebio.rb +14 -0
  37. data/lib/solveobject.rb +101 -0
  38. data/lib/tabulate.rb +706 -0
  39. data/solvebio.gemspec +75 -0
  40. data/test/data/netrc-save +6 -0
  41. data/test/helper.rb +3 -0
  42. data/test/test-auth.rb +54 -0
  43. data/test/test-client.rb +27 -0
  44. data/test/test-error.rb +36 -0
  45. data/test/test-filter.rb +70 -0
  46. data/test/test-netrc.rb +42 -0
  47. data/test/test-query-batch.rb +60 -0
  48. data/test/test-query-init.rb +29 -0
  49. data/test/test-query-paging.rb +123 -0
  50. data/test/test-query.rb +88 -0
  51. data/test/test-resource.rb +47 -0
  52. data/test/test-solveobject.rb +27 -0
  53. data/test/test-tabulate.rb +127 -0
  54. metadata +158 -0
data/lib/tabulate.rb ADDED
@@ -0,0 +1,706 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # This file contains code from python-tabulate, modified for SolveBio
4
+ #
5
+ # Copyright © 2011-2013 Sergey Astanin
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ # from __future__ require 'print_function'
27
+ # from __future__ require 'unicode_literals'
28
+
29
+ # from collections require 'namedtuple'
30
+ # from platform require 'python_version_tuple'
31
+
32
+ # if python_version_tuple()[0] < "3"
33
+ # from itertools require 'izip_longest'
34
+ # _none_type = type(nil)
35
+ # _int_type = int
36
+ # _float_type = float
37
+ # _text_type = unicode
38
+ # _binary_type = str
39
+ # else
40
+ # from itertools require 'zip_longest as izip_longest'
41
+ # from functools require 'reduce'
42
+ # _none_type = type(nil)
43
+ # _int_type = int
44
+ # _float_type = float
45
+ # _text_type = str
46
+ # _binary_type = bytes
47
+ # end
48
+
49
+
50
+ require_relative 'main'
51
+
52
+ module SolveBio::Tabulate
53
+
54
+ VERSION = '0.6'
55
+
56
+ TYPES = {NilClass => 0, Fixnum => 1, Float => 2, String => 4}
57
+
58
+ INVISIBILE_CODES = %r{\\x1b\[\d*m} # ANSI color codes
59
+
60
+ Line = Struct.new(:start, :hline, :sep, :last)
61
+
62
+ DataRow = Struct.new(:start, :sep, :last)
63
+
64
+ TableFormat = Struct.new(:lineabove, :linebelowheader,
65
+ :linebetweenrows, :linebelow,
66
+ :headerrow, :datarow,
67
+ :padding, :usecolons,
68
+ :with_header_hide,
69
+ :without_header_hide)
70
+
71
+ FORMAT_DEFAULTS = {
72
+ :padding => 0,
73
+ :usecolons => false,
74
+ :with_header_hide => [],
75
+ :without_header_hide => []
76
+ }
77
+
78
+
79
+ INVTYPES = {
80
+ 4 => String,
81
+ 2 => Float,
82
+ 1 => Fixnum,
83
+ 0 => NilClass
84
+ }
85
+
86
+ SIMPLE_DATAROW = DataRow.new('', ' ', '')
87
+ PIPE_DATAROW = DataRow.new('|', '|', '|')
88
+
89
+ SIMPLE_LINE = Line.new('', '-', ' ', '')
90
+ GRID_LINE = Line.new('+', '-', '+', '+')
91
+
92
+ TABLE_FORMATS = {
93
+ :simple =>
94
+ TableFormat.new(lineabove = nil,
95
+ linebelowheader = SIMPLE_LINE,
96
+ linebetweenrows = nil,
97
+ linebelow = SIMPLE_LINE,
98
+ headerrow = SIMPLE_DATAROW,
99
+ datarow = SIMPLE_DATAROW,
100
+ padding = 0,
101
+ usecolons = false,
102
+ with_header_hide = ['linebelow'],
103
+ without_header_hide = []),
104
+ :grid =>
105
+ TableFormat.new(lineabove = SIMPLE_LINE,
106
+ linebelowheader = Line.new('+', '=', '+', '+'),
107
+ linebetweenrows = SIMPLE_LINE,
108
+ linebelow = SIMPLE_LINE,
109
+ headerrow = PIPE_DATAROW,
110
+ datarow = PIPE_DATAROW,
111
+ padding = 1,
112
+ usecolons = false,
113
+ with_header_hide = [],
114
+ without_header_hide = ['linebelowheader']),
115
+
116
+ :pipe =>
117
+ TableFormat.new(lineabove = nil,
118
+ linebelowheader = Line.new('|', '-', '|', '|'),
119
+ linebetweenrows = nil,
120
+ linebelow = nil,
121
+ headerrow = PIPE_DATAROW,
122
+ datarow = PIPE_DATAROW,
123
+ padding = 1,
124
+ usecolons = true,
125
+ with_header_hide = [],
126
+ without_header_hide = []),
127
+
128
+ :orgmode =>
129
+ TableFormat.new(lineabove=nil,
130
+ linebelowheader = Line.new('|', '-', '+', '|'),
131
+ linebetweenrows = nil,
132
+ linebelow = nil,
133
+ headerrow = PIPE_DATAROW,
134
+ datarow = PIPE_DATAROW,
135
+ padding = 1,
136
+ usecolons = false,
137
+ with_header_hide = [],
138
+ without_header_hide = ['linebelowheader'])
139
+ }
140
+
141
+ module_function
142
+ def simple_separated_format(separator)
143
+ # FIXME? python code hard-codes separator = "\n" below.
144
+ return TableFormat
145
+ .new(
146
+ :lineabove => nil,
147
+ :linebelowheader => nil,
148
+ :linebetweenrows => nil,
149
+ :linebelow => nil,
150
+ :headerrow => nil,
151
+ :datarow => DataRow.new('', separator, ''),
152
+ :padding => 0,
153
+ :usecolons => false,
154
+ :with_header_hide => [],
155
+ :without_header_hide => [],
156
+ )
157
+ end
158
+
159
+ # The least generic type, one of NilClass, Fixnum, Float, or String.
160
+ # _type(nil) => NilClass
161
+ # _type("foo") => String
162
+ # _type("1") => Fixnum
163
+ # _type("\x1b[31m42\x1b[0m") => Fixnum
164
+ def _type(obj, has_invisible=true)
165
+
166
+ obj = obj.strip_invisible if obj.kind_of?(String) and has_invisible
167
+
168
+ if obj.nil?
169
+ return NilClass
170
+ elsif obj.kind_of?(Fixnum) or obj.int?
171
+ return Fixnum
172
+ elsif obj.kind_of?(Float) or obj.number?
173
+ return Float
174
+ else
175
+ return String
176
+ end
177
+ end
178
+
179
+ # [string] -> [padded_string]
180
+ #
181
+ # align_column(
182
+ # ["12.345", "-1234.5", "1.23", "1234.5",
183
+ # "1e+234", "1.0e234"], "decimal") =>
184
+ # [' 12.345 ', '-1234.5 ', ' 1.23 ',
185
+ # ' 1234.5 ', ' 1e+234 ', ' 1.0e234']
186
+ def align_column(strings, alignment, minwidth=0, has_invisible=true)
187
+ if alignment == "right"
188
+ strings = strings.map{|s| s.to_s.strip}
189
+ padfn = :padleft
190
+ elsif alignment == 'center'
191
+ strings = strings.map{|s| s.to_s.strip}
192
+ padfn = :padboth
193
+ elsif alignment == 'decimal'
194
+ decimals = strings.map{|s| s.to_s.afterpoint}
195
+ maxdecimals = decimals.max
196
+ zipped = strings.zip(decimals)
197
+ strings = zipped.map{|s, decs|
198
+ s.to_s + " " * ((maxdecimals - decs))
199
+ }
200
+ padfn = :padleft
201
+ else
202
+ strings = strings.map{|s| s.to_s.strip}
203
+ padfn = :padright
204
+ end
205
+
206
+ if has_invisible
207
+ width_fn = :visible_width
208
+ else
209
+ width_fn = :size
210
+ end
211
+
212
+ maxwidth = [strings.map{|s| s.send(width_fn)}.max, minwidth].max
213
+ strings.map{|s| s.send(padfn, maxwidth, has_invisible) }
214
+ end
215
+
216
+
217
+ def more_generic(type1, type2)
218
+ moregeneric = [TYPES[type1] || 4, TYPES[type2] || 4].max
219
+ return INVTYPES[moregeneric]
220
+ end
221
+
222
+
223
+ # The least generic type all column values are convertible to.
224
+ #
225
+ # column_type(["1", "2"]) => Fixnum
226
+ # column_type(["1", "2.3"]) => Float
227
+ # column_type(["1", "2.3", "four"]) => String
228
+ # column_type(["four", '\u043f\u044f\u0442\u044c']) => String
229
+ # column_type([nil, "brux"]) => String
230
+ # column_type([1, 2, nil]) => Fixnum
231
+ def column_type(strings, has_invisible=true)
232
+ types = strings.map{|s| _type(s, has_invisible)}
233
+ # require 'trepanning'; debugger
234
+ return types.reduce(Fixnum){
235
+ |t, result|
236
+ more_generic(result, t)
237
+ }
238
+ end
239
+
240
+
241
+ # Format a value accoding to its type.
242
+ #
243
+ # Unicode is supported:
244
+ #
245
+ # >>> hrow = ["\u0431\u0443\u043a\u0432\u0430",
246
+ # "\u0446\u0438\u0444\u0440\u0430"]
247
+ # tbl = [["\u0430\u0437", 2], ["\u0431\u0443\u043a\u0438", 4]]
248
+ # expected = "\\u0431\\u0443\\u043a\\u0432\\u0430 \n
249
+ # \\u0446\\u0438\\u0444\\u0440\\u0430\\n-------\n
250
+ # -------\\n\\u0430\\u0437 \n
251
+ # 2\\n\\u0431\\u0443\\u043a\\u0438 4'
252
+ # tabulate(tbl, hrow) => good_result
253
+ # true
254
+ def format(val, valtype, floatfmt, missingval="")
255
+ if val.nil?
256
+ return missingval
257
+ end
258
+
259
+ if [Fixnum, String, Fixnum].member?(valtype)
260
+ return "%s" % val
261
+ elsif valtype.kind_of?(Float)
262
+ return "%#{floatfmt}" % Float(val)
263
+ else
264
+ return "%s" % val
265
+ end
266
+ end
267
+
268
+
269
+ def align_header(header, alignment, width)
270
+ if alignment == "left"
271
+ return header.padright(width)
272
+ elsif alignment == "center"
273
+ return header.padboth(width)
274
+ else
275
+ return header.padleft(width)
276
+ end
277
+ end
278
+
279
+
280
+ # Transform a supported data type to an Array of Arrays, and an
281
+ # Array of headers.
282
+ #
283
+ # Supported tabular data types:
284
+ #
285
+ # * Array-of-Arrays or another Enumerable of Enumerables
286
+ #
287
+ # * Hash of Enumerables
288
+ #
289
+ # The first row can be used as headers if headers="firstrow",
290
+ # column indices can be used as headers if headers="keys".
291
+ #
292
+ def normalize_tabular_data(tabular_data, headers)
293
+ if tabular_data.respond_to?(:keys) and tabular_data.respond_to?(:values)
294
+ # likely a Hash
295
+ keys = tabular_data.keys
296
+ ## FIXME: what's different in the Python code?
297
+ # columns have to be transposed
298
+ # rows = list(izip_longest(*tabular_data.values()))
299
+ # rows = vals[0].zip(*vals[1..-1])
300
+ rows = tabular_data.values
301
+ if headers == "keys"
302
+ # headers should be strings
303
+ headers = keys.map{|k| k.to_s}
304
+ end
305
+ elsif tabular_data.kind_of?(Enumerable)
306
+ # Likely an Enumerable of Enumerables
307
+ rows = tabular_data.to_a
308
+ if headers == "keys" and not rows.empty? # keys are column indices
309
+ headers = (0..rows[0]).map {|i| i.to_s}
310
+ end
311
+ else
312
+ raise(ValueError, "tabular data doesn't appear to be a Hash" +
313
+ " or Array")
314
+ end
315
+
316
+ # take headers from the first row if necessary
317
+ if headers == "firstrow" and not rows.empty?
318
+ headers = rows[0].map{|row| [_text_type(row)]}
319
+ rows.shift
320
+ end
321
+
322
+ # pad with empty headers for initial columns if necessary
323
+ if not headers.empty? and not rows.empty?
324
+ nhs = headers.size
325
+ ncols = rows[0].size
326
+ if nhs < ncols
327
+ headers = [''] * (ncols - nhs) + headers
328
+ end
329
+ end
330
+
331
+ return rows, headers
332
+ end
333
+
334
+ TTY_COLS = ENV['COLUMNS'].to_i || 80 rescue 80
335
+ # Return a string which represents a row of data cells.
336
+ def build_row(cells, padding, first, sep, last)
337
+
338
+ pad = ' ' * padding
339
+ padded_cells = cells.map{|cell| pad + cell + pad }
340
+
341
+ # SolveBio: we're only displaying Key-Value tuples (dimension of 2).
342
+ # enforce that we don't wrap lines by setting a max
343
+ # limit on row width which is equal to TTY_COLS (see printing)
344
+ rendered_cells = (first + padded_cells.join(sep) + last).rstrip
345
+ if rendered_cells.size > TTY_COLS
346
+ if not cells[-1].end_with?(' ') and not cells[-1].end_with?('-')
347
+ terminating_str = ' ... '
348
+ else
349
+ terminating_str = ''
350
+ end
351
+ prefix = rendered_cells[1..TTY_COLS - terminating_str.size - 1]
352
+ rendered_cells = "%s%s%s" % [prefix, terminating_str, last]
353
+ end
354
+
355
+ return rendered_cells
356
+ end
357
+
358
+
359
+ # Return a string which represents a horizontal line.
360
+ def build_line(colwidths, padding, first, fill, sep, last)
361
+ cells = colwidths.map{|w| fill * (w + 2 * padding)}
362
+ return build_row(cells, 0, first, sep, last)
363
+ end
364
+
365
+
366
+ # Return a segment of a horizontal line with optional colons which
367
+ # indicate column's alignment (as in `pipe` output format).
368
+ def _line_segment_with_colons(linefmt, align, colwidth)
369
+ fill = linefmt.hline
370
+ w = colwidth
371
+ if ['right', 'decimal'].member?(align)
372
+ return (fill[0] * (w - 1)) + ":"
373
+ elsif align == "center"
374
+ return ":" + (fill[0] * (w - 2)) + ":"
375
+ elsif align == "left"
376
+ return ":" + (fill[0] * (w - 1))
377
+ else
378
+ return fill[0] * w
379
+ end
380
+ end
381
+
382
+
383
+ # Produce a plain-text representation of the table.
384
+ def format_table(fmt, headers, rows, colwidths, colaligns)
385
+ lines = []
386
+ hidden = headers ? fmt.with_header_hide : fmt.without_header_hide
387
+ pad = fmt.padding || 0
388
+ datarow = fmt.datarow ? fmt.datarow : SIMPLE_DATAROW
389
+ headerrow = fmt.headerrow ? fmt.headerrow : fmt.datarow
390
+
391
+ if fmt.lineabove and hidden and hidden.member?("lineabove")
392
+ lines << build_line(colwidths, pad, *fmt.lineabove)
393
+ end
394
+
395
+ unless headers.empty?
396
+ lines << build_row(headers, pad, headerrow.start, headerrow.sep,
397
+ headerrow.last)
398
+ end
399
+
400
+ if fmt.linebelowheader and not hidden.member?("linebelowheader")
401
+ first, _, sep, last = fmt.linebelowheader
402
+ if fmt.usecolons
403
+ segs = [
404
+ colwidths.zip(colaligns).map do |w, a|
405
+ _line_segment_with_colons(fmt.linebelowheader, a, w + 2 * pad)
406
+ end ]
407
+ lines << build_row(segs, 0, first, sep, last)
408
+ else
409
+ lines << build_line(colwidths, pad, fmt.linebelowheader.start,
410
+ fmt.linebelowheader.hline,
411
+ fmt.linebelowheader.sep,
412
+ fmt.linebelowheader.last)
413
+ end
414
+ end
415
+
416
+ if rows and fmt.linebetweenrows and hidden.member?('linebetweenrows')
417
+ # initial rows with a line below
418
+ rows[1..-1].each do |row|
419
+ lines << build_row(row, pad, fmt.datarow.start,
420
+ fmt.datarow.sep, fmt.datarow.last)
421
+ lines << build_line(colwidths, pad, fmt.linebetweenrows.start,
422
+ fmt.linebelowheader.hline,
423
+ fmt.linebetweenrows.sep,
424
+ fmt.linebetweenrows.last)
425
+ end
426
+ # the last row without a line below
427
+ lines << build_row(rows[-1], pad, datarow.start,
428
+ datarow.sep, datarow.last)
429
+ else
430
+ rows.each do |row|
431
+ lines << build_row(row, pad, datarow.start, datarow.sep,
432
+ datarow.last)
433
+
434
+ if fmt.linebelow and hidden.member?('linebelow')
435
+ lines << build_line(colwidths, pad, fmt.linebelow.start,
436
+ fmt.linebelowheader.hline,
437
+ fmt.linebelow.sep,
438
+ fmt.linebelow.last)
439
+ end
440
+ end
441
+ end
442
+ return lines.join("\n")
443
+ end
444
+
445
+ # Construct a simple TableFormat with columns separated by a separator.
446
+ #
447
+ # tsv = simple_separated_format("\t")
448
+ # tabulate([["foo", 1], ["spam", 23]], [], tsv) =>
449
+ # "foo 1\nspam 23"
450
+ def tabulate(tabular_data, headers=[], tablefmt=TABLE_FORMATS[:orgmode],
451
+ floatfmt="g", aligns=[], missingval='')
452
+ list_of_lists, headers = normalize_tabular_data(tabular_data, headers)
453
+
454
+ # optimization: look for ANSI control codes once,
455
+ # enable smart width functions only if a control code is found
456
+ plain_rows = [headers.map{|h| h.to_s}.join("\t")]
457
+ row_text = list_of_lists.map{|row|
458
+ row.map{|r| r.to_s}.join("\t")
459
+ }
460
+ plain_rows += row_text
461
+ plain_text = plain_rows.join("\n")
462
+
463
+ has_invisible = INVISIBILE_CODES.match(plain_text)
464
+ if has_invisible
465
+ width_fn = :visible_width
466
+ else
467
+ width_fn = :size
468
+ end
469
+
470
+ # format rows and columns, convert numeric values to strings
471
+ cols = list_of_lists[0].zip(*list_of_lists[1..-1]) if
472
+ list_of_lists.size > 1
473
+
474
+ coltypes = cols.map{|c| column_type(c)}
475
+
476
+ cols = cols.zip(coltypes).map do |c, ct|
477
+ c.map{|v| format(v, ct, floatfmt, missingval)}
478
+ end
479
+
480
+ # align columns
481
+ if aligns.empty?
482
+ # dynamic alignment by col type
483
+ aligns = coltypes.map do |ct|
484
+ [Fixnum, Float].member?(ct) ? 'decimal' : 'left'
485
+ end
486
+ end
487
+
488
+ minwidths =
489
+ if headers.empty? then
490
+ [0] * cols.size
491
+ else
492
+ headers.map{|h| h.send(width_fn) + 2}
493
+ end
494
+
495
+ cols = cols.zip(aligns, minwidths).map do |c, a, minw|
496
+ align_column(c, a, minw, has_invisible)
497
+ end
498
+
499
+ if headers.empty?
500
+ minwidths = cols.map{|c| c[0].send(width_fn)}
501
+ else
502
+ # align headers and add headers
503
+ minwidths =
504
+ minwidths.zip(cols).map{|minw, c| [minw, c[0].send(width_fn)].max}
505
+ headers =
506
+ headers.zip(aligns, minwidths).map{|h, a, minw| align_header(h, a, minw)}
507
+ end
508
+ rows = cols[0].zip(cols[1])
509
+
510
+ tablefmt = TABLE_FORMATS[:orgmode] unless
511
+ tablefmt.kind_of?(TableFormat)
512
+
513
+ # make sure values don't have newlines or tabs in them
514
+ rows = rows.each do |r|
515
+ r[1] = r[1].gsub("\n", '').gsub("\t", '')
516
+ end
517
+ return format_table(tablefmt, headers, rows, minwidths, aligns)
518
+ end
519
+ end
520
+
521
+ class Object
522
+
523
+ # "123.45".number? => true
524
+ # "123".number? => true
525
+ # "spam".number? => false
526
+ def number?
527
+ begin
528
+ Float(self)
529
+ return true
530
+ rescue
531
+ return false
532
+ end
533
+ end
534
+
535
+ # "123".int? => true
536
+ # "123.45".int? => false
537
+ def int?
538
+ begin
539
+ Integer(self)
540
+ return true
541
+ rescue
542
+ return false
543
+ end
544
+ end
545
+ end
546
+
547
+ class String
548
+
549
+ # Symbols after a decimal point, -1 if the string lacks the decimal point.
550
+ #
551
+ # "123.45".afterpoint => 2
552
+ # "1001".afterpoint => -1
553
+ # "eggs".afterpoint => -1
554
+ # "123e45".afterpoint => 2
555
+ def afterpoint
556
+ if self.number?
557
+ if self.int?
558
+ return -1
559
+ else
560
+ pos = self.rindex('.') || -1
561
+ pos = self.downcase().rindex('e') if pos < 0
562
+ if pos >= 0
563
+ return self.size - pos - 1
564
+ else
565
+ return -1 # no point
566
+ end
567
+ end
568
+ else
569
+ return -1 # not a number
570
+ end
571
+ end
572
+
573
+ def adjusted_size(has_invisible)
574
+ return has_invisible ? self.strip_invisible.size : self.size
575
+ end
576
+
577
+ # Visible width of a printed string. ANSI color codes are removed.
578
+ #
579
+ # ['\x1b[31mhello\x1b[0m' "world"].map{|s| s.visible_width} =>
580
+ # [5, 5]
581
+ def visible_width
582
+ # if self.kind_of?(_text_type) or self.kind_of?(_binary_type)
583
+ return self.strip_invisible.size
584
+ # else
585
+ # return _text_type(s).size
586
+ # end
587
+ end
588
+
589
+
590
+ # Flush right.
591
+ #
592
+ # '\u044f\u0439\u0446\u0430'.padleft(6) =>
593
+ # ' \u044f\u0439\u0446\u0430'
594
+ # 'abc'.padleft(2) => 'abc'
595
+ def padleft(width, has_invisible=true)
596
+ s_width = self.adjusted_size(has_invisible)
597
+ s_width < width ? (' ' * (width - s_width)) + self : self
598
+ end
599
+
600
+ # Flush left.
601
+ #
602
+ # padright(6, '\u044f\u0439\u0446\u0430') => '\u044f\u0439\u0446\u0430 '
603
+ # padright(2, 'abc') => 'abc'
604
+ def padright(width, has_invisible=true)
605
+ s_width = self.adjusted_size(has_invisible)
606
+ s_width < width ? self + (' ' * (width - s_width)) : self
607
+ end
608
+
609
+
610
+ # Center string with uneven space on the right
611
+ #
612
+ # '\u044f\u0439\u0446\u0430'.padboth(6) => ' \u044f\u0439\u0446\u0430 '
613
+ # 'abc'.padboth(2) => 'abc'
614
+ # 'abc'.padboth(6) => ' abc '
615
+ def padboth(width, has_invisible=true)
616
+ s_width = self.adjusted_size(has_invisible)
617
+ return self if s_width >= width
618
+ pad_size = width - s_width
619
+ pad_left = ' ' * (pad_size/2)
620
+ pad_right = ' ' * ((pad_size + 1)/ 2)
621
+ pad_left + self + pad_right
622
+ end
623
+
624
+
625
+ # Remove invisible ANSI color codes.
626
+ def strip_invisible
627
+ return self.gsub(SolveBio::Tabulate::INVISIBILE_CODES, '')
628
+ end
629
+
630
+ end
631
+
632
+ if __FILE__ == $0
633
+ include SolveBio::Tabulate
634
+ # puts '" 123.45".num? %s' % "123.45".number?() # true
635
+ # puts "'123'.num?: %s" % '123'.number? # true
636
+ # puts "'spam'.num? spam: %s" % "spam".number? # false
637
+ # puts "'123'.int? %s" % "123".int? # true
638
+ # puts "'123.45'int?: %s" % '124.45'.int? # false
639
+
640
+ # puts "_type(nil) %s = %s" % [_type(nil), NilClass]
641
+ # puts "_type('foo') %s = %s" % [_type('foo'), String]
642
+ # puts "_type('1') %s = %s" % [_type('1'), Fixnum]
643
+ # puts "_type(''\x1b[31m42\x1b[0m') %s = %s" % [_type('\x1b[31m42\x1b[0m'), Fixnum]
644
+
645
+ # puts "'123.45'.afterpoint: 2 == %d" % '123.45'.afterpoint
646
+ # puts "'1001'afterpoint : -1 == %d" % '1001'.afterpoint
647
+ # puts "'eggs'.afterpoint : -1 == %d" % 'eggs'.afterpoint
648
+ # puts "'123e45'.afterpoint: 2 == %d" % "123e45".afterpoint
649
+
650
+ # puts("'\u044f\u0439\u0446\u0430'.padleft(6) = '%s' == '%s'" %
651
+ # ["\u044f\u0439\u0446\u0430".padleft(6),
652
+ # " \u044f\u0439\u0446\u0430"])
653
+ # puts("'abc'.padleft(2) = '%s' == '%s'" %
654
+ # ['abc'.padleft(2), 'abc'])
655
+ # puts("padright(2, 'abc') = '%s' == '%s'" %
656
+ # ['abc'.padright(2), 'abc'])
657
+ # puts("'abc'.padboth(2) = '%s' == '%s'" %
658
+ # ['abc'.padboth(2), 'abc'])
659
+ # puts("'abc'.padboth(6) = '%s' == '%s'" %
660
+ # ['abc'.padboth(6), ' abc '])
661
+
662
+ # puts align_column(
663
+ # ["12.345", "-1234.5", "1.23", "1234.5",
664
+ # "1e+234", "1.0e234"], "decimal")
665
+
666
+ # puts '=' * 30
667
+ # puts [' 12.345 ', '-1234.5 ', ' 1.23 ',
668
+ # ' 1234.5 ', ' 1e+234 ', ' 1.0e234']
669
+
670
+ # puts('column_type(["1", "2"]) is Fixnum == %s ' %
671
+ # column_type(["1", "2"]))
672
+ # puts('column_type(["1", "2.3"]) is Float == %s ' %
673
+ # column_type(["1", "2.3"]))
674
+ # puts('column_type(["1", "2.3", "four"]) is String => %s ' %
675
+ # column_type(["1", "2.3", "four"]))
676
+ # puts('column_type(["four", "\u043f\u044f\u0442\u044c"]) is text => %s ' %
677
+ # column_type(["four", "\u043f\u044f\u0442\u044c"]))
678
+ # puts('column_type([nil, "brux"]) is String => %s ' %
679
+ # column_type([nil, "brux"]))
680
+ # puts('column_type([1, 2, nil]) is Fixnum => %s ' %
681
+ # column_type([1, 2, nil]))
682
+ # tsv = simple_separated_format("\t")
683
+ # puts tabulate([["foo", 1], ["spam", 23]], [], tsv)
684
+ # hrow = ["\u0431\u0443\u043a\u0432\u0430", "\u0446\u0438\u0444\u0440\u0430"]
685
+ # tbl = [["\u0430\u0437", 2], ["\u0431\u0443\u043a\u0438", 4]]
686
+ # puts SolveBio::Tabulate.tabulate(tbl, hrow)
687
+
688
+ hash = {
689
+ "rcvaccession_version"=>2,
690
+ "hg18_chromosome"=>"3",
691
+ "hg19_start"=>148562304,
692
+ "rcvaccession"=>"RCV000060731",
693
+ "hg38_start"=>148844517,
694
+ "reference_allele"=>"C",
695
+ "gene_symbols"=>["CPB1"],
696
+ "rsid"=>"rs150241322",
697
+ "hg19_chromosome"=>"3",
698
+ "hgvs"=>["NC_000003.12:g.148844517C>T"],
699
+ "clinical_significance"=>"other",
700
+ "alternate_alleles"=>["T"],
701
+ "clinical_origin"=>["somatic"],
702
+ "type"=>"SNV"}
703
+ puts SolveBio::Tabulate.tabulate(hash.to_a,
704
+ ['Fields', 'Data'],
705
+ ['right', 'left'])
706
+ end