typr 1.1.4 → 1.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -0
- data/README.md +9 -9
- data/example/grid +4 -4
- data/example/text +7 -4
- data/lib/browser.rb +87 -66
- data/lib/curses.rb +11 -7
- data/lib/frontend.rb +7 -3
- data/lib/graphical.rb +10 -13
- data/lib/grid.rb +69 -14
- data/lib/line.rb +57 -27
- data/lib/space.rb +14 -5
- data/lib/stack.rb +9 -9
- data/lib/terminal.rb +185 -20
- data/lib/text.rb +122 -7
- data/lib/typr.rb +5 -6
- data/share/mimetypes +1298 -1
- data/typr.gemspec +2 -2
- metadata +2 -7
- data/Gemfile +0 -10
data/lib/grid.rb
CHANGED
|
@@ -9,16 +9,16 @@ module Typr
|
|
|
9
9
|
# input: [ ["Alice", 30], ["Bob", 25] ],
|
|
10
10
|
# header: [:name, :age],
|
|
11
11
|
# format: [:max, :right]
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
# )
|
|
13
|
+
# grid.show
|
|
14
|
+
#
|
|
15
15
|
# Column format tokens: +Integer+ (fixed width), +:min+ (auto), +:max+ (fill remaining).
|
|
16
16
|
#
|
|
17
17
|
# Built-in procs: +:filetree+, +:datetime+, +:magnitudes+, +:convert+.
|
|
18
18
|
|
|
19
19
|
class Grid < Stack # Table
|
|
20
20
|
attr_accessor :format, :procs, :filters, :data, :map
|
|
21
|
-
attr_accessor :align, :rawsort, :rawfilter
|
|
21
|
+
attr_accessor :align, :rawsort, :rawfilter, :reverse
|
|
22
22
|
attr_writer :sequence
|
|
23
23
|
|
|
24
24
|
##
|
|
@@ -177,10 +177,11 @@ module Typr
|
|
|
177
177
|
end
|
|
178
178
|
|
|
179
179
|
##
|
|
180
|
-
# Add a column filter (substring or regex).
|
|
180
|
+
# Add a column filter (substring or regex). +:all+ matches any column.
|
|
181
181
|
#
|
|
182
182
|
# grid.add_filter(0, "Ali") # name contains "Ali"
|
|
183
183
|
# grid.add_filter(1, /^\d{2}$/) # age matches regex
|
|
184
|
+
# grid.add_filter(:all, "ip") # any column contains "ip"
|
|
184
185
|
#
|
|
185
186
|
# @param column [Integer, Symbol]
|
|
186
187
|
# @param query [String, Regexp]
|
|
@@ -202,8 +203,10 @@ module Typr
|
|
|
202
203
|
|
|
203
204
|
def check row
|
|
204
205
|
@filters.reject{ |column,query|
|
|
205
|
-
|
|
206
|
-
|
|
206
|
+
cols = column == :all ? @data[row].each_index.to_a : [column]
|
|
207
|
+
cols.any?{ |col|
|
|
208
|
+
( (@procs[col] and not @rawfilter[col]) ?
|
|
209
|
+
@layer : @data )[row][col].to_s[query] } }.empty?
|
|
207
210
|
end
|
|
208
211
|
|
|
209
212
|
##
|
|
@@ -237,6 +240,57 @@ module Typr
|
|
|
237
240
|
|
|
238
241
|
def sorted_by name=false; (name and @sort) ? @header[@sort] : @sort end
|
|
239
242
|
|
|
243
|
+
##
|
|
244
|
+
# Pick a row with live-filtering.
|
|
245
|
+
#
|
|
246
|
+
# When column is given, `Grid` overrides `Stack#pick` to add interactive
|
|
247
|
+
# text filtering of rows by the given column. Filter text is shown at the
|
|
248
|
+
# bottom of the grid. Passes through to `super` for all other types or
|
|
249
|
+
# when no column is given.
|
|
250
|
+
#
|
|
251
|
+
# grid.pick :row, 0, column: :name
|
|
252
|
+
# grid.pick :row, 0, column: :all # match any column
|
|
253
|
+
#
|
|
254
|
+
# @param type [Symbol] type is ignored (always :row); passed to super otherwise
|
|
255
|
+
# @param column [Symbol, String] column name to filter on, or :all (default nil)
|
|
256
|
+
|
|
257
|
+
def pick type = :row, row=0, column: nil
|
|
258
|
+
return super(type, row) unless column
|
|
259
|
+
|
|
260
|
+
saved = [@filters.dup, @map, @start]
|
|
261
|
+
base = @map || ids
|
|
262
|
+
@filters = [[column, '']]
|
|
263
|
+
@map = base.select{ |id| check id }
|
|
264
|
+
@start = 0
|
|
265
|
+
show
|
|
266
|
+
draw_hints
|
|
267
|
+
result = Typr.read_line '/', left: left, top: self.bottom + 1,
|
|
268
|
+
&->(key, query, _) {
|
|
269
|
+
if key.is_a?(String) and idx = @hints[0..height-@hints_start-1].index(key)
|
|
270
|
+
return page.to_a[idx + @hints_start]
|
|
271
|
+
end
|
|
272
|
+
if key.is_a?(String) and !key.each_char.all?{ |c| c.ord.between?(32, 126) } and
|
|
273
|
+
@keymap.values.include?(key) and ![KEY_BACKSPACE, "\b"].include?(key)
|
|
274
|
+
send key
|
|
275
|
+
else
|
|
276
|
+
@filters = [[column, query]]
|
|
277
|
+
@map = base.select{ |id| check id }
|
|
278
|
+
@start = 0
|
|
279
|
+
end
|
|
280
|
+
show
|
|
281
|
+
draw_hints
|
|
282
|
+
nil
|
|
283
|
+
}
|
|
284
|
+
case result
|
|
285
|
+
when nil; @filters, @map, @start = saved
|
|
286
|
+
when Integer; return result
|
|
287
|
+
else
|
|
288
|
+
@filters = result.empty? ? saved[0] : saved[0] + [[column, result]]
|
|
289
|
+
filter :all
|
|
290
|
+
end
|
|
291
|
+
return
|
|
292
|
+
end
|
|
293
|
+
|
|
240
294
|
##
|
|
241
295
|
# Sort by column (toggle direction on repeat).
|
|
242
296
|
#
|
|
@@ -260,8 +314,9 @@ module Typr
|
|
|
260
314
|
def sort
|
|
261
315
|
return false unless @sort
|
|
262
316
|
data = (@procs[@sort] and not @rawsort[@sort]) ? @layer : @data
|
|
263
|
-
@map =
|
|
317
|
+
@map = (0...@data.size).to_a.sort{ |a,b| (data[a][@sort] <=> data[b][@sort]) || 0 }
|
|
264
318
|
@map.reverse! if @reverse
|
|
319
|
+
@map = @map.select{ |id| check id } unless @filters.empty?
|
|
265
320
|
return
|
|
266
321
|
end
|
|
267
322
|
|
|
@@ -293,11 +348,11 @@ module Typr
|
|
|
293
348
|
##
|
|
294
349
|
# Compute column widths from visible data and render the grid.
|
|
295
350
|
#
|
|
296
|
-
# grid.
|
|
351
|
+
# grid.show
|
|
297
352
|
#
|
|
298
353
|
# @return [void]
|
|
299
354
|
|
|
300
|
-
def
|
|
355
|
+
def show
|
|
301
356
|
if not @data or @data.empty? or ( @map and @map.empty? )
|
|
302
357
|
@widths = [ width/columns ] * columns if @widths.count < columns
|
|
303
358
|
@rest = 0
|
|
@@ -349,20 +404,20 @@ module Typr
|
|
|
349
404
|
def print row=nil
|
|
350
405
|
return super unless row
|
|
351
406
|
header = ( row == :header )
|
|
352
|
-
return
|
|
407
|
+
return draw @header[0..width-1].ljust(width) if
|
|
353
408
|
header and @header.is_a? String
|
|
354
409
|
fields = (header ? @header : @data[row] ).dup
|
|
355
410
|
@layer[row].each{ |col,value| fields[col] = value } if
|
|
356
411
|
@layer[row] if @layer unless header
|
|
357
412
|
for col,id in sequence.each_with_index
|
|
358
|
-
|
|
413
|
+
draw @separator unless id == 0
|
|
359
414
|
select = ( @selected[:columns].include? col or
|
|
360
415
|
@selected[:fields].include? [row,col] ) unless
|
|
361
416
|
@selected[:rows].include?(row) or header
|
|
362
417
|
color( @colors[:fields][[row,col]] ||
|
|
363
418
|
@colors[:columns][col] || @colors[:default] ) unless header
|
|
364
419
|
background @colors[:selected] if select
|
|
365
|
-
|
|
420
|
+
draw prepare( fields[col].to_s, width_for( id ), @align[col] )
|
|
366
421
|
background @colors[:default][1] if select
|
|
367
422
|
end
|
|
368
423
|
end
|
|
@@ -406,7 +461,7 @@ module Typr
|
|
|
406
461
|
@colors = { columns: [], fields: {} }.merge ( @colors )
|
|
407
462
|
@selected = { fields:[], columns:[] }.merge @selected
|
|
408
463
|
@functions = {
|
|
409
|
-
filetree: Proc.new{ |f| f.
|
|
464
|
+
filetree: Proc.new{ |f| ' ' * [(d = f.count('/') - 1), 0].max + '└' + File.basename(f) },
|
|
410
465
|
datetime: Proc.new{|sec|Time.at(sec).strftime"%y-%m-%d %H:%M" rescue ??},
|
|
411
466
|
magnitudes: Proc.new{ |size| mag = (size.to_s.length-1) / 3
|
|
412
467
|
mag>0 ? (size.to_s.insert -(mag*3+1), ?.)[0..4] + %w[B K M G T][mag] :
|
data/lib/line.rb
CHANGED
|
@@ -7,36 +7,36 @@ module Typr
|
|
|
7
7
|
# == Example
|
|
8
8
|
#
|
|
9
9
|
# line = Line.new( top: 5, left: 10, width: 40 )
|
|
10
|
-
# line.
|
|
10
|
+
# line.show "Hello world"
|
|
11
11
|
# line.append " more"
|
|
12
|
-
# line.ask([ ["Name?", :
|
|
12
|
+
# line.ask([ ["Name?", :line], ["Age?", :line] ])
|
|
13
13
|
|
|
14
14
|
class Line < Space
|
|
15
|
-
attr_accessor :default, :prompt, :bindings, :data
|
|
15
|
+
attr_accessor :default, :prompt, :bindings, :data, :align, :trim
|
|
16
16
|
|
|
17
17
|
# Render +msg+ (String, Proc, or Space) on this line.
|
|
18
18
|
#
|
|
19
|
-
# line.
|
|
20
|
-
# line.
|
|
19
|
+
# line.show "status: ok"
|
|
20
|
+
# line.show Proc.new { show_time }
|
|
21
21
|
|
|
22
|
-
def
|
|
22
|
+
def show msg=@data, align=@align
|
|
23
23
|
@data = msg
|
|
24
24
|
move left, top
|
|
25
25
|
color @colors[:default]
|
|
26
26
|
case msg
|
|
27
27
|
when Proc; msg.call
|
|
28
|
-
when Space; msg.
|
|
29
|
-
when String;
|
|
28
|
+
when Space; msg.show
|
|
29
|
+
when String; draw( prepare msg, width, align, @trim )
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# Append +str+ to the current content and redraw.
|
|
34
34
|
#
|
|
35
|
-
# line.
|
|
35
|
+
# line.show "hello"
|
|
36
36
|
# line.append " world" #=> "hello world"
|
|
37
37
|
# line << "!" #=> "hello world!"
|
|
38
38
|
|
|
39
|
-
def append(str);
|
|
39
|
+
def append(str); show @data+str end
|
|
40
40
|
alias :<< :append
|
|
41
41
|
|
|
42
42
|
# Display keybinding help in a floating grid.
|
|
@@ -56,38 +56,68 @@ class Line < Space
|
|
|
56
56
|
#
|
|
57
57
|
# line.reset
|
|
58
58
|
|
|
59
|
-
def reset;
|
|
59
|
+
def reset; show( @default || ( @bindings || [@prompt] ).join(' ') ) end
|
|
60
60
|
|
|
61
61
|
# Prompt the user with a sequence of questions.
|
|
62
|
-
# +sentence+ is an array of [question, type] pairs.
|
|
63
|
-
# Returns a single answer or an array when multiple questions.
|
|
62
|
+
# +sentence+ is an array of [question, type] pairs (or a hash of them).
|
|
63
|
+
# Returns a single answer, or an array when there are multiple questions.
|
|
64
64
|
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
65
|
+
# The +type+ selects how the answer is collected:
|
|
66
|
+
# Array - interactive pick via <widget>.pick(<type>); an optional third
|
|
67
|
+
# element renders the chosen row's field into the line
|
|
68
|
+
# :key - a single keypress via Typr.read_key
|
|
69
|
+
# :line - interactive line editor via Typr.read_line at the cursor
|
|
70
|
+
#
|
|
71
|
+
# Pressing the exit key (@keymap[:exit], Escape by default) aborts the
|
|
72
|
+
# remaining questions and returns nil.
|
|
73
|
+
#
|
|
74
|
+
# line.ask(name: :line) #=> "Alice"
|
|
75
|
+
# line.ask(keys: :key) #=> "a"
|
|
76
|
+
# line.ask(open: [grid, :row, :name]) #=> 2 (picked row id)
|
|
77
|
+
# line.ask([ ["Name?", :line], ["Ok?", :key] ]) #=> ["Alice", "a"]
|
|
67
78
|
|
|
68
79
|
def ask sentence
|
|
69
80
|
answer = []
|
|
70
81
|
move left, top
|
|
71
82
|
Typr.clear :line
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
saved = @trim
|
|
84
|
+
@trim = :left
|
|
85
|
+
begin
|
|
86
|
+
@data = @prompt.to_s
|
|
87
|
+
colored = color_code( @colors[:default] ).to_s + @data
|
|
88
|
+
move left, top
|
|
89
|
+
draw prepare( colored, width, @align, @trim )
|
|
90
|
+
for question, object in sentence
|
|
91
|
+
text = " " + question.to_s + " "
|
|
92
|
+
@data += text
|
|
93
|
+
colored += color_code( @colors[:question] ).to_s + text
|
|
94
|
+
move left, top
|
|
95
|
+
draw prepare( colored, width, @align, @trim )
|
|
96
|
+
color( @colors[:answer] )
|
|
97
|
+
case object
|
|
98
|
+
when Array then answer << object.first.pick( object[1] )
|
|
99
|
+
when :key then answer << Typr.read_key
|
|
100
|
+
when :line then answer << Typr.read_line( colored + color_code( @colors[:answer] ),
|
|
101
|
+
left: left, top: top )
|
|
102
|
+
end
|
|
103
|
+
return if !answer.last or answer.last == @keymap[:exit]
|
|
104
|
+
text = ( object.is_a?(Array) and object[2] ) ?
|
|
105
|
+
object.first.data[answer.last][object[2]].to_s : answer.last.to_s
|
|
106
|
+
@data += text
|
|
107
|
+
colored += color_code( @colors[:answer] ).to_s + text
|
|
108
|
+
move left, top
|
|
109
|
+
draw prepare( colored, width, @align, @trim )
|
|
81
110
|
end
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
append answer.last.to_s
|
|
111
|
+
ensure
|
|
112
|
+
@trim = saved
|
|
85
113
|
end
|
|
86
114
|
return ( answer.one? ? answer.first : answer )
|
|
87
115
|
end
|
|
88
116
|
|
|
89
117
|
def initialize args
|
|
90
118
|
@right= -1
|
|
119
|
+
@align = :left
|
|
120
|
+
@trim = :right
|
|
91
121
|
@prompt = ?>
|
|
92
122
|
super
|
|
93
123
|
@bottom = @top
|
data/lib/space.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Typr
|
|
|
10
10
|
#
|
|
11
11
|
# @example
|
|
12
12
|
# box = Space.new(left: 0, top: 2, right: 0.5, bottom: -4, border: :round)
|
|
13
|
-
# box.
|
|
13
|
+
# box.show
|
|
14
14
|
|
|
15
15
|
class Space
|
|
16
16
|
include Typr
|
|
@@ -29,21 +29,29 @@ include Typr
|
|
|
29
29
|
STR
|
|
30
30
|
eval( method % ([name]*11) ) }
|
|
31
31
|
|
|
32
|
+
def symbolize obj
|
|
33
|
+
case obj
|
|
34
|
+
when Hash
|
|
35
|
+
obj.each_with_object({}) { |(k, v), h|
|
|
36
|
+
h[k.is_a?(String) ? k.to_sym : k] = symbolize(v) }
|
|
37
|
+
else obj end
|
|
38
|
+
end
|
|
39
|
+
|
|
32
40
|
attr_writer :left, :top, :right, :bottom
|
|
33
41
|
attr_accessor :margin, :colors, :interval, :borders
|
|
34
42
|
|
|
35
43
|
def width; right - left + 1 end
|
|
36
44
|
def height; bottom - top + 1 end
|
|
37
45
|
|
|
38
|
-
def start; @updater = Thread.new{ loop{
|
|
46
|
+
def start; @updater = Thread.new{ loop{show; sleep @interval }} end
|
|
39
47
|
def stop; @updater.terminate end
|
|
40
48
|
|
|
41
49
|
def draw_border x,y,char
|
|
42
|
-
|
|
50
|
+
draw move_code(x, y) + char unless not char or
|
|
43
51
|
x < 0 or x > Typr.width or y < 0 or y > Typr.height
|
|
44
52
|
end
|
|
45
53
|
|
|
46
|
-
def
|
|
54
|
+
def show
|
|
47
55
|
return if @borders.empty?
|
|
48
56
|
color @colors[:border]
|
|
49
57
|
draw_border(left - 1, top - 1, @borders[:top_left])
|
|
@@ -71,10 +79,11 @@ eval( method % ([name]*11) ) }
|
|
|
71
79
|
end
|
|
72
80
|
|
|
73
81
|
def initialize args={}
|
|
74
|
-
@max, @keymap, @colors, @margin = 0, {}, {}, '
|
|
82
|
+
@max, @keymap, @colors, @margin = 0, {}, {}, ' '
|
|
75
83
|
@left, @top, @interval, @borders = 0, 0, 1, {}
|
|
76
84
|
args.each{ |name, value|
|
|
77
85
|
instance_variable_set ?@ + name.to_s, value }
|
|
86
|
+
@colors = symbolize( @colors ) if @colors.is_a? Hash
|
|
78
87
|
@keymap = { exit: KEY_ESCAPE }.merge @keymap
|
|
79
88
|
@colors = { default: [:white, :black], border: [:white, :black] }.merge(@colors)
|
|
80
89
|
borders = @borders.dup
|
data/lib/stack.rb
CHANGED
|
@@ -50,7 +50,7 @@ module Typr
|
|
|
50
50
|
#
|
|
51
51
|
# stack.print 42 # renders a blank row
|
|
52
52
|
|
|
53
|
-
def print id;
|
|
53
|
+
def print id; draw " " * (width-@margin.size) end
|
|
54
54
|
|
|
55
55
|
##
|
|
56
56
|
# Resets widget state by one or more categories.
|
|
@@ -80,16 +80,16 @@ module Typr
|
|
|
80
80
|
if type.to_s[ /column/ ]
|
|
81
81
|
positions( row_id )[ @hints_start..-1 ].each_with_index{ |pos, idx|
|
|
82
82
|
move( left + pos, top + row_id )
|
|
83
|
-
|
|
83
|
+
draw @hints[idx] }
|
|
84
84
|
else
|
|
85
85
|
bottom = top + headspace + [height, rows].min - 1
|
|
86
86
|
(top + headspace..bottom).each{ |pos|
|
|
87
|
-
move( left, pos );
|
|
87
|
+
move( left, pos ); draw ' ' }
|
|
88
88
|
@hints.chars.each_with_index{ |char, idx|
|
|
89
89
|
pos = idx + @hints_start + headspace
|
|
90
90
|
break if top + pos > bottom
|
|
91
91
|
move( left, top + pos )
|
|
92
|
-
|
|
92
|
+
draw char }
|
|
93
93
|
end
|
|
94
94
|
end
|
|
95
95
|
|
|
@@ -100,11 +100,11 @@ module Typr
|
|
|
100
100
|
# This method is called by your application's main loop each frame; it does not handle
|
|
101
101
|
# events itself (see {#send}).
|
|
102
102
|
|
|
103
|
-
def
|
|
103
|
+
def show
|
|
104
104
|
if @header
|
|
105
105
|
color @colors[ :header ]
|
|
106
106
|
move left,top
|
|
107
|
-
|
|
107
|
+
draw @margin
|
|
108
108
|
print :header
|
|
109
109
|
end
|
|
110
110
|
list = page.to_a
|
|
@@ -116,7 +116,7 @@ module Typr
|
|
|
116
116
|
dark=!dark if @alternate
|
|
117
117
|
background ( select ? @colors[:selected] :
|
|
118
118
|
(@alternate and dark) ? @colors[:alternate] : @colors[:default][1] )
|
|
119
|
-
|
|
119
|
+
draw @margin
|
|
120
120
|
print id
|
|
121
121
|
background if select
|
|
122
122
|
end
|
|
@@ -203,10 +203,10 @@ module Typr
|
|
|
203
203
|
column = pick( :column, page.to_a.index( row + headspace)) or return
|
|
204
204
|
value = [ row , column ]
|
|
205
205
|
else
|
|
206
|
-
|
|
206
|
+
show
|
|
207
207
|
draw_hints type, row unless type['none']
|
|
208
208
|
limit = type['row'] ? height : positions(row).count
|
|
209
|
-
key = Typr.
|
|
209
|
+
key = Typr.read_key
|
|
210
210
|
if key.is_a?(String) and value =
|
|
211
211
|
@hints[0..limit-@hints_start-1].index(key)
|
|
212
212
|
value += @hints_start
|