cetusql 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/lib/cetusql.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "cetusql/version"
2
+
3
+ module Cetusql
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,285 @@
1
+ #!/usr/bin/env ruby -w
2
+ # ----------------------------------------------------------------------------- #
3
+ # File: cli_sqlite.rb
4
+ # Description: common sqlite routines to be sourced in ruby shell programs
5
+ # Author: j kepler http://github.com/mare-imbrium/canis/
6
+ # Date: 2017-03-18 - 17:53
7
+ # License: MIT
8
+ # Last update: 2017-03-29 14:17
9
+ # ----------------------------------------------------------------------------- #
10
+ # YFF Copyright (C) 2012-2016 j kepler
11
+ # ----------------------------------------------------------------------------- #
12
+ require 'sqlite3'
13
+ require 'shellwords'
14
+ require 'pp'
15
+
16
+ def get_table_names db
17
+ #raise "No database file selected." unless $current_db
18
+
19
+ tables = get_data "select name from sqlite_master where type='table'"
20
+ tables.collect!{|x| x[0] } ## 1.9 hack, but will it run on 1.8 ??
21
+ tables
22
+ end
23
+ def get_column_names tbname
24
+ get_metadata tbname
25
+ end
26
+ # connect to given database, and if no name supplied then allow user to choose
27
+ def connect dbname=nil
28
+ dbname ||= getdbname
29
+ return nil unless dbname
30
+ #$log.debug "XXX: CONNECT got #{dbname} "
31
+ $current_db = dbname
32
+ $db = SQLite3::Database.new(dbname) if dbname
33
+ return $db
34
+ end
35
+ def get_data db, sql
36
+ #$log.debug "SQL: #{sql} "
37
+ $columns, *rows = db.execute2(sql)
38
+ #$log.debug "XXX COLUMNS #{sql}, #{rows.count} "
39
+ content = rows
40
+ return nil if content.nil? or content[0].nil?
41
+ $datatypes = content[0].types #if @datatypes.nil?
42
+ return content
43
+ end
44
+ def get_metadata table
45
+ get_data "select * from #{table} limit 1"
46
+ return $columns
47
+ end
48
+ # TODO option of headers
49
+ # run query and put into a temp table and view it using vim
50
+ # If no outputfile name passed, then use temp table
51
+ # What about STDOUT
52
+ # TODO use temp file, format it there and append to given file only after termtable
53
+ def OLDview_data db, sql, options
54
+ outputfile = options[:output_to]
55
+ formatting = options[:formatting]
56
+ headers = options[:headers]
57
+ #str = db.get_data sql
58
+ rs = db.execute_query sql
59
+ str = rs.content
60
+ columns = rs.columns
61
+ #puts "SQL: #{sql}.\nstr: #{str.size}"
62
+ data = []
63
+ if headers
64
+ data << columns.join("\t")
65
+ end
66
+ str.each {|line| data << line.join("\t"); }
67
+ #puts "Rows: #{data.size}"
68
+ require 'tempfile'
69
+ tmpfile = Tempfile.new('SQL.XXXXXX')
70
+ filename = tmpfile.path
71
+ filename = Shellwords.escape(filename)
72
+ #puts "Writing to #{filename}"
73
+ tmpfile.write(data.join("\n"))
74
+ tmpfile.close # need to flush, otherwise write is buffered
75
+ headerstr=nil
76
+ if formatting
77
+ headerstr = "-H" unless headers
78
+ # sometimes this can be slow, and it can fault on UTF-8 chars
79
+ system("cat #{filename} | term-table.rb #{headerstr} | sponge #{filename}")
80
+ end
81
+ if outputfile
82
+ #puts "comes here"
83
+ system("cp #{filename} #{outputfile}")
84
+ filename = outputfile
85
+ end
86
+ system "wc -l #{filename}" if $opt_debug
87
+
88
+ #system "$EDITOR #{filename}"
89
+ system "vim -c ':set nowrap' #{filename}"
90
+ tmpfile.close
91
+ tmpfile.unlink
92
+ end
93
+ def view_data db, sql, options
94
+ outputfile = options[:output_to]
95
+ formatting = options[:formatting]
96
+ headers = options[:headers]
97
+ #str = db.get_data sql
98
+ rs = db.execute_query sql
99
+ str = rs.content
100
+ columns = rs.columns
101
+ #puts "SQL: #{sql}.\nstr: #{str.size}"
102
+ #data = []
103
+ #if headers
104
+ #data << columns.join("\t")
105
+ #end
106
+ #str.each {|line| data << line.join("\t"); }
107
+ #puts "Rows: #{data.size}"
108
+ headerstr=nil
109
+ tmpfile = nil
110
+ if formatting
111
+ if headers
112
+ str.unshift(columns)
113
+ end
114
+ filename = tabulate2 str, options
115
+ else
116
+ data = []
117
+ if headers
118
+ data << columns.join("\t")
119
+ end
120
+ str.each {|line| data << line.join("\t"); }
121
+ tmpfile = mktemp()
122
+ tmpfile.write(data.join("\n"))
123
+ tmpfile.close # need to flush, otherwise write is buffered
124
+ filename = tmpfile.path
125
+ end
126
+ if outputfile
127
+ #puts "comes here"
128
+ system("cp #{filename} #{outputfile}")
129
+ filename = outputfile
130
+ end
131
+
132
+ #system "$EDITOR #{filename}"
133
+ system "vim -c ':set nowrap' #{filename}"
134
+ if tmpfile
135
+ tmpfile.close
136
+ tmpfile.unlink
137
+ end
138
+ end
139
+ def mktemp
140
+ require 'tempfile'
141
+ tmpfile = Tempfile.new('SQL.XXXXXX')
142
+ filename = tmpfile.path
143
+ filename = Shellwords.escape(filename)
144
+ #puts "Writing to #{filename}"
145
+ #tmpfile.write(data.join("\n"))
146
+ #tmpfile.close # need to flush, otherwise write is buffered
147
+ return tmpfile
148
+ end
149
+ # given content returned by get_data, formats and returns in a file
150
+ def tabulate content, options
151
+ data = []
152
+ content.each {|line| data << line.join("\t"); }
153
+ puts "Rows: #{data.size}" if $opt_verbose
154
+ require 'tempfile'
155
+ tmpfile = Tempfile.new('SQL.XXXXXX')
156
+ filename = tmpfile.path
157
+ #filename = Shellwords.escape(filename)
158
+ #puts "Writing to #{filename}"
159
+ tmpfile.write(data.join("\n"))
160
+ tmpfile.close # need to flush, otherwise write is buffered
161
+ if options[:formatting]
162
+ system("term-table.rb < #{filename} | sponge #{filename}")
163
+ end
164
+ return filename
165
+ end
166
+ # rather than use external program with dependencies,
167
+ # we generate tabular format for an array with headings
168
+ # returns filename
169
+ # TODO check for headings true
170
+ def tabulate2 content, options
171
+ widths = calculate_column_widths(content, 99)
172
+ str = "| "
173
+ sep = "+"
174
+ widths.each do |w|
175
+ str << "%-#{w}s | "
176
+ sep << ("-"*(w+2)) + "+"
177
+ end
178
+ data = []
179
+ data << sep
180
+ content.each_with_index {|line, ix|
181
+ data << str % line
182
+ data << sep if ix == 0
183
+ }
184
+ data << sep
185
+ require 'tempfile'
186
+ tmpfile = Tempfile.new('SQL.XXXXXX')
187
+ filename = tmpfile.path
188
+ #filename = Shellwords.escape(filename)
189
+ #puts "Writing to #{filename}"
190
+ tmpfile.write(data.join("\n"))
191
+ tmpfile.close # need to flush, otherwise write is buffered
192
+ return filename
193
+ end
194
+
195
+ def calculate_column_widths content, maxrows=99
196
+ widths = []
197
+ content.first.each_with_index {|r,i| widths << calculate_column_width(content, i, maxrows) }
198
+ return widths
199
+ end
200
+ def calculate_column_width content, col, maxrows=99
201
+ ret = 1
202
+ ctr = 0
203
+ content.each_with_index { |r, i|
204
+ break if ctr > maxrows
205
+ ctr += 1
206
+ c = r[col]
207
+ x = c.to_s.length
208
+ ret = x if x > ret
209
+ }
210
+ ret
211
+ end
212
+ class Database
213
+
214
+ ResultSet = Struct.new(:content, :columns, :datatypes)
215
+
216
+ def initialize(name)
217
+ raise ArgumentError, "Database name cannot be nil" unless name
218
+ @name = name
219
+ connect name
220
+ end
221
+ def connect name
222
+ raise ArgumentError, "Database name cannot be nil" unless name
223
+ @tables = nil
224
+ @db = SQLite3::Database.new(name)
225
+ end
226
+ def tables
227
+ return @tables if @tables
228
+ tables = sql "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
229
+ tables.collect!{|x| x[0] } ## 1.9 hack, but will it run on 1.8 ??
230
+ @tables = tables
231
+ end
232
+ def columns table
233
+ raise ArgumentError, "#{$0}: table name cannot be nil" unless table
234
+ columns, ignore = self.get_metadata table
235
+ return columns
236
+ end
237
+ def indexed_columns table
238
+ indexes = sql %Q{select sql from sqlite_master where type='index' and tbl_name = "#{table}" }
239
+ return nil if indexes == []
240
+ #pp indexes
241
+ arr = []
242
+ indexes.each do |ind|
243
+ if ind.first.nil?
244
+ arr << "auto?"
245
+ next
246
+ end
247
+ m = ind.first.match /\((.*)\)/
248
+ arr << m[1] if m
249
+ end
250
+ return arr.join(",")
251
+ end
252
+ def get_metadata table
253
+ raise ArgumentError, "#{$0}: table name cannot be nil" unless table
254
+ sql = "select * from #{table} limit 1"
255
+ columns, *rows = @db.execute2(sql)
256
+ content = rows
257
+ return nil if content.nil? or content[0].nil?
258
+ datatypes = content[0].types
259
+ return columns, datatypes
260
+ end
261
+ # runs sql query and returns an array of arrays
262
+ def get_data sql
263
+ #$log.debug "SQL: #{sql} "
264
+ columns, *rows = @db.execute2(sql)
265
+ #$log.debug "XXX COLUMNS #{sql}, #{rows.count} "
266
+ content = rows
267
+ return content
268
+ end
269
+ alias :sql :get_data
270
+ def execute_query sql
271
+ columns, *rows = @db.execute2(sql)
272
+ content = rows
273
+ return nil if content.nil? or content[0].nil?
274
+ datatypes = content[0].types
275
+ rs = ResultSet.new(content, columns, datatypes)
276
+ return rs
277
+ end
278
+ def sql_recent_rows tablename
279
+ sql = "SELECT * from #{tablename} ORDER by rowid DESC LIMIT 100"
280
+ end
281
+ def close
282
+ @db = nil
283
+ @tables = nil
284
+ end
285
+ end
@@ -0,0 +1,412 @@
1
+ #!/usr/bin/env ruby
2
+ # ----------------------------------------------------------------------------- #
3
+ # File: cli_utils.rb
4
+ # Description: common stuff for command line utils such as menu, getting a single char
5
+ # inline variable editing
6
+ # Author: j kepler http://github.com/mare-imbrium/canis/
7
+ # Date: 2017-03-18 - 14:33
8
+ # License: MIT
9
+ # Last update: 2017-04-02 19:47
10
+ # ----------------------------------------------------------------------------- #
11
+ # YFF Copyright (C) 2012-2016 j kepler
12
+
13
+
14
+ # edit a variable inline like zsh's vared
15
+ def vared var, prompt=">"
16
+ Readline.pre_input_hook = -> do
17
+ Readline.insert_text var
18
+ Readline.redisplay
19
+ # Remove the hook right away.
20
+ Readline.pre_input_hook = nil
21
+ end
22
+ begin
23
+ input = Readline.readline(prompt, false)
24
+ rescue Exception => e
25
+ return nil
26
+ end
27
+ input
28
+ end
29
+
30
+
31
+ # What if we only want to allow the given keys and ignore others.
32
+ # In menu maybe ENTER and other such keys should be ignored, or atleast
33
+ # option should be there, so i don't accidentally hit enter.
34
+ # print in columns, but take into account size so we don't exceed COLS
35
+ # (Some entries like paths can be long)
36
+ def menu title, h
37
+ return unless h
38
+
39
+ pbold "#{title}"
40
+ ctr = 0
41
+ #h.each_pair { |k, v| puts " #{k}: #{v}" }
42
+ h.each_pair { |k, v|
43
+ print " #{k}: %-20s" % [v]
44
+ if ctr > 1
45
+ print "\n"
46
+ ctr = 0
47
+ else
48
+ ctr += 1
49
+ end
50
+ }
51
+ print "\n >"
52
+ #print "\r >"
53
+ ch = get_char
54
+ puts ch
55
+ #system("clear") # ???
56
+ binding = h[ch]
57
+ binding = h[ch.to_sym] unless binding
58
+ if binding
59
+ if respond_to?(binding, true)
60
+ # 2017-03-19 - we can't send return values from a method ??
61
+ send(binding)
62
+ end
63
+ end
64
+ return ch, binding
65
+ end
66
+ ## get a character from user and return as a string
67
+ # Adapted from:
68
+ #http://stackoverflow.com/questions/174933/how-to-get-a-single-character-without-pressing-enter/8274275#8274275
69
+ # Need to take complex keys and matc against a hash.
70
+ def get_char
71
+ begin
72
+ system("stty raw -echo 2>/dev/null") # turn raw input on
73
+ c = nil
74
+ #if $stdin.ready?
75
+ c = $stdin.getc
76
+ cn=c.ord
77
+ return "ENTER" if cn == 10 || cn == 13
78
+ return "BACKSPACE" if cn == 127
79
+ return "C-SPACE" if cn == 0
80
+ return "SPACE" if cn == 32
81
+ # next does not seem to work, you need to bind C-i
82
+ return "TAB" if cn == 8
83
+ if cn >= 0 && cn < 27
84
+ x= cn + 96
85
+ return "C-#{x.chr}"
86
+ end
87
+ if c == ''
88
+ buff=c.chr
89
+ while true
90
+ k = nil
91
+ if $stdin.ready?
92
+ k = $stdin.getc
93
+ #puts "got #{k}"
94
+ buff += k.chr
95
+ else
96
+ x=$kh[buff]
97
+ return x if x
98
+ #puts "returning with #{buff}"
99
+ if buff.size == 2
100
+ ## possibly a meta/alt char
101
+ k = buff[-1]
102
+ return "M-#{k.chr}"
103
+ end
104
+ return buff
105
+ end
106
+ end
107
+ end
108
+ #end
109
+ return c.chr if c
110
+ ensure
111
+ #system "stty -raw echo" # turn raw input off
112
+ system("stty -raw echo 2>/dev/null") # turn raw input on
113
+ end
114
+ end
115
+ ## clean this up a bit, copied from shell program and macro'd
116
+ $kh=Hash.new
117
+ $kh["OP"]="F1"
118
+ $kh[""]="UP"
119
+ $kh["[5~"]="PGUP"
120
+ $kh['']="ESCAPE"
121
+ KEY_PGDN="[6~"
122
+ KEY_PGUP="[5~"
123
+ ## I needed to replace the O with a [ for this to work
124
+ # in Vim Home comes as ^[OH whereas on the command line it is correct as ^[[H
125
+ KEY_HOME=''
126
+ KEY_END=""
127
+ KEY_F1="OP"
128
+ KEY_UP=""
129
+ KEY_DOWN=""
130
+
131
+ $kh[KEY_PGDN]="PgDn"
132
+ $kh[KEY_PGUP]="PgUp"
133
+ $kh[KEY_HOME]="Home"
134
+ $kh[KEY_END]="End"
135
+ $kh[KEY_F1]="F1"
136
+ $kh[KEY_UP]="UP"
137
+ $kh[KEY_DOWN]="DOWN"
138
+ KEY_LEFT=''
139
+ KEY_RIGHT=''
140
+ $kh["OQ"]="F2"
141
+ $kh["OR"]="F3"
142
+ $kh["OS"]="F4"
143
+ $kh[KEY_LEFT] = "LEFT"
144
+ $kh[KEY_RIGHT]= "RIGHT"
145
+ KEY_F5='[15~'
146
+ KEY_F6='[17~'
147
+ KEY_F7='[18~'
148
+ KEY_F8='[19~'
149
+ KEY_F9='[20~'
150
+ KEY_F10='[21~'
151
+ KEY_S_F1=''
152
+ $kh[KEY_F5]="F5"
153
+ $kh[KEY_F6]="F6"
154
+ $kh[KEY_F7]="F7"
155
+ $kh[KEY_F8]="F8"
156
+ $kh[KEY_F9]="F9"
157
+ $kh[KEY_F10]="F10"
158
+ # testing out shift+Function. these are the codes my kb generates
159
+ $kh[KEY_S_F1]="S-F1"
160
+ $kh['']="S-F2"
161
+
162
+ def pbold text
163
+ puts "#{BOLD}#{text}#{BOLD_OFF}"
164
+ end
165
+ def pgreen text
166
+ puts "#{GREEN}#{text}#{CLEAR}"
167
+ end
168
+ def perror text
169
+ pred text
170
+ get_char
171
+ end
172
+ def pred text
173
+ puts "#{RED}#{text}#{CLEAR}"
174
+ end
175
+ def pause text=" Press a key ..."
176
+ print text
177
+ get_char
178
+ end
179
+ # alternative of menu that takes an array and uses numbers as indices.
180
+ # Hey wait, if there aer more than 10 then we are screwed since we take one character
181
+ # I have handled this somewhere, should check, maybe we should use characters
182
+ # returns text, can be nil if selection not one of choices
183
+ # How do we communicate to caller, that user pressed C-c
184
+ def select_from title, array
185
+ h = {}
186
+ array.each_with_index {|e,ix| ix += 1; h[ix.to_s] = e }
187
+ ch, text = menu title, h
188
+ unless text
189
+ if ch == "ENTER"
190
+ return array.first
191
+ end
192
+ end
193
+ return text
194
+ end
195
+ # multiselect from an array using fzf
196
+ def multi_select title, array
197
+ arr = %x[ echo "#{array.join("\n")}" | fzf --multi --reverse --prompt="#{title} >"]
198
+ return arr.split("\n")
199
+ end
200
+ # single select from an array using fzf
201
+ # CAUTION: this messes with single and double quotes, so don't pass a query in
202
+ def single_select title, array
203
+ str = %x[ echo "#{array.join("\n")}" | fzf --reverse --prompt="#{title} >" -1 -0 ]
204
+ return str
205
+ end
206
+ def editline array
207
+ Readline::HISTORY.push(*array)
208
+ begin
209
+ command = Readline::readline('>', true)
210
+ rescue Exception => e
211
+ return nil
212
+ end
213
+ return command
214
+ end
215
+
216
+ # allows user to select from list, returning string if user pressed ENTER
217
+ # Aborts if user presses Q or C-c or ESCAPE
218
+ def ctrlp arr
219
+ patt = nil
220
+ curr = 0
221
+ while true
222
+ # clear required otherwise will keep drawing itself
223
+ system("clear")
224
+ if patt and patt != ""
225
+ # need fuzzy match here
226
+ view = arr.grep(/^#{patt}/)
227
+ view = view | arr.grep(/#{patt}/)
228
+ fuzzypatt = patt.split("").join(".*")
229
+ view = view | arr.grep(/#{fuzzypatt}/)
230
+ else
231
+ view = arr
232
+ end
233
+ curr = [view.size-1, curr].min
234
+ # if empty then curr becomes -1
235
+ curr = 0 if curr < 0
236
+ view.each_with_index do |a, i|
237
+ mark = " "
238
+ mark = ">" if curr == i
239
+ print "#{mark} #{a} \n"
240
+ end
241
+ #puts " "
242
+ print "\r#{patt} >"
243
+ ch = get_char
244
+ if ch =~ /^[a-z]$/
245
+ patt ||= ""
246
+ patt << ch
247
+ elsif ch == "BACKSPACE"
248
+ if patt && patt.size > 0
249
+ patt = patt[0..-2]
250
+ end
251
+ elsif ch == "Q" or ch == "C-c" or ch == "ESCAPE"
252
+ break
253
+ elsif ch == "UP"
254
+ curr -= 1
255
+ curr = 0 if curr < 0
256
+ elsif ch == "DOWN"
257
+ curr += 1
258
+ curr = [view.size-1, curr].min
259
+ # if empty then curr becomes -1
260
+ curr = 0 if curr < 0
261
+ elsif ch == "ENTER"
262
+ return view[curr]
263
+ else
264
+ # do right and left arrow
265
+
266
+ # get arrow keys here
267
+ end
268
+
269
+ end
270
+ end
271
+ ## CONSTANTS
272
+ GMARK = '*'
273
+ CURMARK = '>'
274
+ SPACE = " "
275
+ CLEAR = "\e[0m"
276
+ BOLD = "\e[1m"
277
+ BOLD_OFF = "\e[22m"
278
+ RED = "\e[31m"
279
+ ON_RED = "\e[41m"
280
+ GREEN = "\e[32m"
281
+ YELLOW = "\e[33m"
282
+ BLUE = "\e[1;34m"
283
+
284
+ ON_BLUE = "\e[44m"
285
+ REVERSE = "\e[7m"
286
+ UNDERLINE = "\e[4m"
287
+ #CURSOR_COLOR = ON_BLUE
288
+ CURSOR_COLOR = CLEAR
289
+
290
+ # --- end constants
291
+ ## check screen size and accordingly adjust some variables
292
+ #
293
+ def screen_settings
294
+ $glines=%x(tput lines).to_i
295
+ $gcols=%x(tput cols).to_i
296
+ $grows = $glines - 3
297
+ $pagesize = 60
298
+ #$gviscols = 3
299
+ $pagesize = $grows * $gviscols
300
+ end
301
+
302
+ # readline version of gets
303
+ def input(prompt="", newline=false)
304
+ prompt += "\n" if newline
305
+ ret = nil
306
+ begin
307
+ ret = Readline.readline(prompt, true).squeeze(" ").strip
308
+ rescue Exception => e
309
+ return nil
310
+ end
311
+ return ret
312
+ end
313
+ def agree(prompt="")
314
+ x = input(prompt)
315
+ return true if x.upcase == "Y"
316
+ false
317
+ end
318
+ alias :confirm :agree
319
+ ##
320
+ #
321
+ # print in columns
322
+ # ary - array of data
323
+ # sz - lines in one column
324
+ #
325
+ def columnate ary, sz
326
+ buff=Array.new
327
+ return buff if ary.nil? || ary.size == 0
328
+
329
+ # determine width based on number of files to show
330
+ # if less than sz then 1 col and full width
331
+ #
332
+ wid = 30
333
+ ars = ary.size
334
+ ars = [$pagesize, ary.size].min
335
+ d = 0
336
+ if ars <= sz
337
+ wid = $gcols - d
338
+ else
339
+ tmp = (ars * 1.000/ sz).ceil
340
+ wid = $gcols / tmp - d
341
+ end
342
+ #elsif ars < sz * 2
343
+ #wid = $gcols/2 - d
344
+ #elsif ars < sz * 3
345
+ #wid = $gcols/3 - d
346
+ #else
347
+ #wid = $gcols/$gviscols - d
348
+ #end
349
+
350
+ # ix refers to the index in the complete file list, wherease we only show 60 at a time
351
+ ix=0
352
+ while true
353
+ ## ctr refers to the index in the column
354
+ ctr=0
355
+ while ctr < sz
356
+
357
+ f = ary[ix]
358
+ fsz = f.size
359
+ if fsz > wid
360
+ f = f[0, wid-2]+"$ "
361
+ ## we do the coloring after trunc so ANSI escpe seq does not get get
362
+ if ix + $sta == $cursor
363
+ f = "#{CURSOR_COLOR}#{f}#{CLEAR}"
364
+ end
365
+ else
366
+ ## we do the coloring before padding so the entire line does not get padded, only file name
367
+ if ix + $sta == $cursor
368
+ f = "#{CURSOR_COLOR}#{f}#{CLEAR}"
369
+ end
370
+ #f = f.ljust(wid)
371
+ f << " " * (wid-fsz)
372
+ end
373
+
374
+ if buff[ctr]
375
+ buff[ctr] += f
376
+ else
377
+ buff[ctr] = f
378
+ end
379
+
380
+ ctr+=1
381
+ ix+=1
382
+ break if ix >= ary.size
383
+ end
384
+ break if ix >= ary.size
385
+ end
386
+ return buff
387
+ end
388
+ # print an array in columns
389
+ # default number of columns is 3 and can be supplied. I actually much prefer that this be derived, so we can get more
390
+ # columns if possible, and te width should be caclulated too
391
+ def print_in_cols a, noc=nil
392
+ unless noc
393
+ noc = 3
394
+ if a.size < 7
395
+ noc = 1
396
+ elsif a.size < 15
397
+ noc = 2
398
+ end
399
+ end
400
+
401
+ x = noc - 1
402
+ cols = a.each_slice((a.size+x)/noc).to_a
403
+ # todo width should be determined based on COLS of screen, and width of data
404
+ cols.first.zip( *cols[1..-1] ).each{|row| puts row.map{|e| e ? '%-30s' % e : ' '}.join(" ") }
405
+ end
406
+ ## expects array of arrays
407
+ #class Array
408
+ #def to_table l = []
409
+ #self.each{|r|r.each_with_index{|f,i|l[i] = [l[i]||0, f.length].max}}
410
+ #self.each{|r|r.each_with_index{|f,i|print "#{f.ljust l[i]}|"};puts ""}
411
+ #end
412
+ #end