cetusql 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +52 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/cetusql.gemspec +34 -0
- data/exe/cetussql +975 -0
- data/lib/cetusql.rb +5 -0
- data/lib/cetusql/cli_sqlite.rb +285 -0
- data/lib/cetusql/cli_utils.rb +412 -0
- data/lib/cetusql/version.rb +3 -0
- metadata +106 -0
data/lib/cetusql.rb
ADDED
@@ -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["[A"]="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='[H'
|
126
|
+
KEY_END="[F"
|
127
|
+
KEY_F1="OP"
|
128
|
+
KEY_UP="[A"
|
129
|
+
KEY_DOWN="[B"
|
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='[D'
|
139
|
+
KEY_RIGHT='[C'
|
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='[1;2P'
|
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['[1;2Q']="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
|