dispel 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +2 -0
- data/MIT-LICENSE +20 -0
- data/lib/dispel.rb +8 -0
- data/lib/dispel/keyboard.rb +210 -0
- data/lib/dispel/screen.rb +159 -0
- data/lib/dispel/style_map.rb +108 -0
- data/lib/dispel/tools.rb +30 -0
- data/lib/dispel/version.rb +3 -0
- metadata +71 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 43be2a0f31061504e3924a3dfc5f3b0ce4fb7de2
|
4
|
+
data.tar.gz: 3935dc92f5658964747ffe18b87d8bc15aa42cf0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 76f2f8a256c4a07ab5e2c2df074d8ef04a1f43707b6a980bec6d7ef10ad46bad4158935c87acf1cc3fbf03be24edcb6263854b87a625366b5d21a3b6b76dc029
|
7
|
+
data.tar.gz: f6ad5c7ab269a2e402b842224de4c9436f84ebdaf6b37cffcc82d38d569ebfef9a07715eaca5f9716c52694e439234e0a6931ec341f351e6bdf1dc5f6cd08c08
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data.tar.gz.sig
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (C) 2013 Michael Grosser <michael@grosser.it>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/dispel.rb
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
module Dispel
|
4
|
+
class Keyboard
|
5
|
+
MAX_CHAR = 255
|
6
|
+
ENTER = 13
|
7
|
+
ESCAPE = 27
|
8
|
+
IS_18 = RUBY_VERSION =~ /^1\.8/
|
9
|
+
SEQUENCE_TIMEOUT = 0.005
|
10
|
+
NOTHING = (2**32 - 1) # getch returns this as 'nothing' on 1.8 but nil on 1.9.2
|
11
|
+
A_TO_Z = ('a'..'z').to_a
|
12
|
+
|
13
|
+
def self.input(&block)
|
14
|
+
@input = block
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.output
|
18
|
+
input { Curses.getch } unless @input # keep input replaceable for tests, but default to curses
|
19
|
+
|
20
|
+
@sequence = []
|
21
|
+
@started = Time.now.to_f
|
22
|
+
|
23
|
+
loop do
|
24
|
+
key = fetch_user_input
|
25
|
+
if sequence_finished?
|
26
|
+
sequence_to_keys(@sequence).each{|k| yield k }
|
27
|
+
@sequence = []
|
28
|
+
end
|
29
|
+
next unless key
|
30
|
+
append_to_sequence key
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def self.translate_key_to_code(key)
|
37
|
+
case key
|
38
|
+
|
39
|
+
# move
|
40
|
+
when Curses::Key::UP then :up
|
41
|
+
when Curses::Key::DOWN then :down
|
42
|
+
when Curses::Key::RIGHT then :right
|
43
|
+
when Curses::Key::LEFT then :left
|
44
|
+
|
45
|
+
# code, unix, iTerm
|
46
|
+
when 337, '^[1;2A', "^[A" then :"Shift+up"
|
47
|
+
when 336, '^[1;2B', "^[B" then :"Shift+down"
|
48
|
+
when 402, '^[1;2C' then :"Shift+right"
|
49
|
+
when 393, '^[1;2D' then :"Shift+left"
|
50
|
+
|
51
|
+
when 558, '^[1;3A' then :"Alt+up"
|
52
|
+
when 517, '^[1;3B' then :"Alt+down"
|
53
|
+
when 552, '^[1;3C' then :"Alt+right"
|
54
|
+
when 537, '^[1;3D' then :"Alt+left"
|
55
|
+
|
56
|
+
when 560, '^[1;5A' then :"Ctrl+up"
|
57
|
+
when 519, '^[1;5B' then :"Ctrl+down"
|
58
|
+
when 554, '^[1;5C' then :"Ctrl+right"
|
59
|
+
when 539, '^[1;5D' then :"Ctrl+left"
|
60
|
+
|
61
|
+
when 561, '^[1;6A' then :"Ctrl+Shift+up"
|
62
|
+
when 520, '^[1;6B' then :"Ctrl+Shift+down"
|
63
|
+
when 555, '^[1;6C', "^[C" then :"Ctrl+Shift+right"
|
64
|
+
when 540, '^[1;6D', "^[D" then :"Ctrl+Shift+left"
|
65
|
+
|
66
|
+
when 562, '^[1;7A' then :"Alt+Ctrl+up"
|
67
|
+
when 521, '^[1;7B' then :"Alt+Ctrl+down"
|
68
|
+
when 556, '^[1;7C' then :"Alt+Ctrl+right"
|
69
|
+
when 541, '^[1;7D' then :"Alt+Ctrl+left"
|
70
|
+
|
71
|
+
when '^[1;8A' then :"Alt+Ctrl+Shift+up"
|
72
|
+
when '^[1;8B' then :"Alt+Ctrl+Shift+down"
|
73
|
+
when '^[1;8C' then :"Alt+Ctrl+Shift+right"
|
74
|
+
when '^[1;8D' then :"Alt+Ctrl+Shift+left"
|
75
|
+
|
76
|
+
when '^[1;10A' then :"Alt+Shift+up"
|
77
|
+
when '^[1;10B' then :"Alt+Shift+down"
|
78
|
+
when '^[1;10C' then :"Alt+Shift+right"
|
79
|
+
when '^[1;10D' then :"Alt+Shift+left"
|
80
|
+
|
81
|
+
when '^[F' then :"Shift+end"
|
82
|
+
when '^[H' then :"Shift+home"
|
83
|
+
|
84
|
+
when '^[1;9F' then :"Alt+end"
|
85
|
+
when '^[1;9H' then :"Alt+home"
|
86
|
+
|
87
|
+
when '^[1;10F' then :"Alt+Shift+end"
|
88
|
+
when '^[1;10H' then :"Alt+Shift+home"
|
89
|
+
|
90
|
+
when '^[1;13F' then :"Alt+Ctrl+end"
|
91
|
+
when '^[1;13H' then :"Alt+Ctrl+home"
|
92
|
+
|
93
|
+
when '^[1;14F' then :"Alt+Ctrl+Shift+end"
|
94
|
+
when '^[1;14H' then :"Alt+Ctrl+Shift+home"
|
95
|
+
|
96
|
+
when 527 then :"Ctrl+Shift+end"
|
97
|
+
when 532 then :"Ctrl+Shift+home"
|
98
|
+
|
99
|
+
when Curses::KEY_END then :end
|
100
|
+
when Curses::KEY_HOME then :home
|
101
|
+
when Curses::KEY_NPAGE then :page_down
|
102
|
+
when Curses::KEY_PPAGE then :page_up
|
103
|
+
when Curses::KEY_IC then :insert
|
104
|
+
when Curses::KEY_F0..Curses::KEY_F63 then :"F#{key - Curses::KEY_F0}"
|
105
|
+
|
106
|
+
# modify
|
107
|
+
when 9 then :tab
|
108
|
+
when 353 then :"Shift+tab"
|
109
|
+
when ENTER then :enter # shadows Ctrl+m
|
110
|
+
when 263, 127 then :backspace
|
111
|
+
when '^[3~', Curses::KEY_DC then :delete
|
112
|
+
|
113
|
+
# misc
|
114
|
+
when 0 then :"Ctrl+space"
|
115
|
+
when 1..26 then :"Ctrl+#{A_TO_Z[key-1]}"
|
116
|
+
when ESCAPE then :escape
|
117
|
+
when Curses::KEY_RESIZE then :resize
|
118
|
+
else
|
119
|
+
if key.is_a? Fixnum
|
120
|
+
key > MAX_CHAR ? key : key.chr
|
121
|
+
elsif is_alt_key_code?(key)
|
122
|
+
:"Alt+#{key.slice(1,1)}"
|
123
|
+
else
|
124
|
+
key
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.fetch_user_input
|
130
|
+
key = @input.call or return
|
131
|
+
key = key.ord unless IS_18
|
132
|
+
if key >= NOTHING
|
133
|
+
# nothing happening -> sleep a bit to save cpu
|
134
|
+
sleep SEQUENCE_TIMEOUT
|
135
|
+
return
|
136
|
+
end
|
137
|
+
key
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.append_to_sequence(key)
|
141
|
+
@started = Time.now.to_f
|
142
|
+
@sequence << key
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.bytes_to_string(bytes)
|
146
|
+
bytes.pack('c*').gsub("\r","\n").force_encoding('utf-8')
|
147
|
+
end
|
148
|
+
|
149
|
+
# split a text so fast-typers do not get bugs like ^B^C in output
|
150
|
+
def self.bytes_to_key_codes(bytes)
|
151
|
+
result = []
|
152
|
+
multi_byte = []
|
153
|
+
|
154
|
+
append_multibyte = lambda{
|
155
|
+
unless multi_byte.empty?
|
156
|
+
result << bytes_to_string(multi_byte)
|
157
|
+
multi_byte = []
|
158
|
+
end
|
159
|
+
}
|
160
|
+
|
161
|
+
bytes.each do |byte|
|
162
|
+
if multi_byte_part?(byte)
|
163
|
+
multi_byte << byte
|
164
|
+
else
|
165
|
+
append_multibyte.call
|
166
|
+
result << translate_key_to_code(byte)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
append_multibyte.call
|
171
|
+
result
|
172
|
+
end
|
173
|
+
|
174
|
+
# not ascii and not control-char
|
175
|
+
def self.multi_byte_part?(byte)
|
176
|
+
127 < byte and byte < 256
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.sequence_finished?
|
180
|
+
@sequence.size != 0 and (Time.now.to_f - @started) > SEQUENCE_TIMEOUT
|
181
|
+
end
|
182
|
+
|
183
|
+
# paste of multiple \n or \n in text would cause weird indentation
|
184
|
+
def self.needs_paste_fix?(sequence)
|
185
|
+
sequence.size > 1 and sequence.include?(ENTER)
|
186
|
+
end
|
187
|
+
|
188
|
+
def self.sequence_to_keys(sequence)
|
189
|
+
if needs_paste_fix?(sequence)
|
190
|
+
[bytes_to_string(sequence)]
|
191
|
+
else
|
192
|
+
# when connected via ssh escape sequences are used
|
193
|
+
if escape_sequence?(sequence)
|
194
|
+
stringified = bytes_to_string(sequence).sub(/\e+/,'^').sub('[[','[')
|
195
|
+
[translate_key_to_code(stringified)]
|
196
|
+
else
|
197
|
+
bytes_to_key_codes(sequence)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def self.escape_sequence?(sequence)
|
203
|
+
sequence[0] == ESCAPE and sequence.size.between?(2,7)
|
204
|
+
end
|
205
|
+
|
206
|
+
def self.is_alt_key_code?(sequence)
|
207
|
+
sequence.slice(0,1) == "^" and sequence.size == 2
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
module Dispel
|
4
|
+
class Screen
|
5
|
+
attr_accessor :options
|
6
|
+
|
7
|
+
def initialize(options)
|
8
|
+
@options = options
|
9
|
+
@cache = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.open(options={}, &block)
|
13
|
+
new(options).open(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def open(&block)
|
17
|
+
Curses.noecho # do not show typed chars
|
18
|
+
Curses.nonl # turn off newline translation
|
19
|
+
Curses.stdscr.keypad(true) # enable arrow keys
|
20
|
+
Curses.raw # give us all other keys
|
21
|
+
Curses.stdscr.nodelay = 1 # do not block -> we can use timeouts
|
22
|
+
Curses.init_screen
|
23
|
+
Curses.start_color if color?
|
24
|
+
yield self
|
25
|
+
ensure
|
26
|
+
Curses.clear # needed to clear the menu/status bar on windows
|
27
|
+
Curses.close_screen
|
28
|
+
end
|
29
|
+
|
30
|
+
def columns
|
31
|
+
Curses.stdscr.maxx
|
32
|
+
end
|
33
|
+
|
34
|
+
def lines
|
35
|
+
Curses.stdscr.maxy
|
36
|
+
end
|
37
|
+
|
38
|
+
def clear_cache
|
39
|
+
@cache.clear
|
40
|
+
end
|
41
|
+
|
42
|
+
def draw(view, style_map=[], cursor=nil)
|
43
|
+
draw_view(view, style_map)
|
44
|
+
Curses.setpos(*cursor) if cursor
|
45
|
+
end
|
46
|
+
|
47
|
+
def debug_key(key)
|
48
|
+
@key_line ||= -1
|
49
|
+
@key_line = (@key_line + 1) % lines
|
50
|
+
write(@key_line, 0, "#{key.inspect}---")
|
51
|
+
end
|
52
|
+
|
53
|
+
def color?
|
54
|
+
@options[:colors] and Curses.has_colors?
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def write(line,row,text)
|
60
|
+
Curses.setpos(line,row)
|
61
|
+
Curses.addstr(text);
|
62
|
+
end
|
63
|
+
|
64
|
+
def draw_view(view, style_map)
|
65
|
+
lines = Tools.naive_split(view, "\n")
|
66
|
+
style_map = style_map.flatten
|
67
|
+
|
68
|
+
lines.each_with_index do |line, line_number|
|
69
|
+
styles = style_map[line_number]
|
70
|
+
|
71
|
+
# expand line with whitespace to overwrite previous content
|
72
|
+
missing = columns - line.size
|
73
|
+
raise line if missing < 0
|
74
|
+
line += " " * missing
|
75
|
+
|
76
|
+
# display tabs as single-space -> nothing breaks
|
77
|
+
line.gsub!("\t",' ')
|
78
|
+
|
79
|
+
if_line_changes line_number, [line, styles] do
|
80
|
+
# position at start of line and draw
|
81
|
+
Curses.setpos(line_number,0)
|
82
|
+
Dispel::StyleMap.styled(line, styles).each do |style, part|
|
83
|
+
Curses.attrset self.class.curses_style(style, color?, options)
|
84
|
+
Curses.addstr part
|
85
|
+
end
|
86
|
+
|
87
|
+
if @options[:debug_cache]
|
88
|
+
write(line_number, 0, (rand(899)+100).to_s)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def if_line_changes(key, args)
|
95
|
+
return if @cache[key] == args # would not change the line -> nothing to do
|
96
|
+
@cache[key] = args # store current line
|
97
|
+
yield # render the line
|
98
|
+
end
|
99
|
+
|
100
|
+
class << self
|
101
|
+
# TODO maybe instance and simpler caching...
|
102
|
+
def curses_style(style, colors, options={})
|
103
|
+
Tools.memoize(:curses_style, style, colors) do
|
104
|
+
if colors
|
105
|
+
foreground = options[:foreground] || '#ffffff'
|
106
|
+
background = options[:background] || '#000000'
|
107
|
+
|
108
|
+
foreground, background = if style == :normal
|
109
|
+
[foreground, background]
|
110
|
+
elsif style == :reverse
|
111
|
+
['#000000', '#ffffff']
|
112
|
+
else
|
113
|
+
# :red or [:red, :blue]
|
114
|
+
f,b = style
|
115
|
+
[f || foreground, b || background]
|
116
|
+
end
|
117
|
+
|
118
|
+
foreground = html_to_terminal_color(foreground)
|
119
|
+
background = html_to_terminal_color(background)
|
120
|
+
color_id(foreground, background)
|
121
|
+
else # no colors
|
122
|
+
if style == :reverse
|
123
|
+
Curses::A_REVERSE
|
124
|
+
else
|
125
|
+
Curses::A_NORMAL
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# create a new color from foreground+background or reuse old
|
132
|
+
# and return color-id
|
133
|
+
def color_id(foreground, background)
|
134
|
+
Tools.memoize(:color_id, foreground, background) do
|
135
|
+
# make a new pair with a unique id
|
136
|
+
@@max_color_id ||= 0
|
137
|
+
id = (@@max_color_id += 1)
|
138
|
+
unless defined? RSpec # stops normal text-output, do not use in tests
|
139
|
+
Curses::init_pair(id, foreground, background)
|
140
|
+
end
|
141
|
+
Curses.color_pair(id)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
COLOR_SOURCE_VALUES = 256
|
146
|
+
COLOR_TARGET_VALUES = 5
|
147
|
+
COLOR_DIVIDE = COLOR_SOURCE_VALUES / COLOR_TARGET_VALUES
|
148
|
+
TERM_COLOR_BASE = 16
|
149
|
+
|
150
|
+
def html_to_terminal_color(html_color)
|
151
|
+
return unless html_color
|
152
|
+
r = (html_color[1..2].to_i(16) / COLOR_DIVIDE) * 36
|
153
|
+
g = (html_color[3..4].to_i(16) / COLOR_DIVIDE) * 6
|
154
|
+
b = (html_color[5..6].to_i(16) / COLOR_DIVIDE) * 1
|
155
|
+
TERM_COLOR_BASE + r + g + b
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Dispel
|
2
|
+
class StyleMap
|
3
|
+
attr_accessor :lines, :foreground, :background
|
4
|
+
|
5
|
+
def initialize(lines)
|
6
|
+
@lines = Array.new(lines)
|
7
|
+
end
|
8
|
+
|
9
|
+
def add(style, line, columns)
|
10
|
+
@lines[line] ||= []
|
11
|
+
@lines[line] << [style, columns]
|
12
|
+
end
|
13
|
+
|
14
|
+
def prepend(style, line, columns)
|
15
|
+
@lines[line] ||= []
|
16
|
+
@lines[line].unshift [style, columns]
|
17
|
+
end
|
18
|
+
|
19
|
+
def flatten
|
20
|
+
@lines.map do |styles|
|
21
|
+
next unless styles
|
22
|
+
|
23
|
+
# change to style at start and recalculate one after the end
|
24
|
+
points_of_change = styles.map{|s,c| [c.first, Tools.last_element(c)+1] }.flatten.uniq
|
25
|
+
|
26
|
+
flat = []
|
27
|
+
|
28
|
+
points_of_change.each do |point|
|
29
|
+
flat[point] = :normal # set default
|
30
|
+
styles.each do |style, columns|
|
31
|
+
next unless columns.include?(point)
|
32
|
+
flat[point] = style
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
flat
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def left_pad!(offset)
|
41
|
+
@lines.compact.each do |styles|
|
42
|
+
next unless styles
|
43
|
+
styles.map! do |style, columns|
|
44
|
+
[style, (columns.first + offset)..(columns.last + offset)]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def invert!
|
50
|
+
map = {:reverse => :normal, :normal => :reverse}
|
51
|
+
@lines.compact.each do |styles|
|
52
|
+
styles.map! do |style, columns|
|
53
|
+
[map[style] || style, columns]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def +(other)
|
59
|
+
lines = self.lines + other.lines
|
60
|
+
new = StyleMap.new(0)
|
61
|
+
new.lines = lines
|
62
|
+
new
|
63
|
+
end
|
64
|
+
|
65
|
+
def slice!(*args)
|
66
|
+
sliced = lines.slice!(*args)
|
67
|
+
new = StyleMap.new(0)
|
68
|
+
new.lines = sliced
|
69
|
+
new
|
70
|
+
end
|
71
|
+
|
72
|
+
def shift
|
73
|
+
slice!(0, 1)
|
74
|
+
end
|
75
|
+
|
76
|
+
def pop
|
77
|
+
slice!(-1, 1)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.styled(content, styles)
|
81
|
+
styles ||= []
|
82
|
+
content = content.dup
|
83
|
+
|
84
|
+
build = []
|
85
|
+
build << [:normal]
|
86
|
+
|
87
|
+
buffered = ''
|
88
|
+
styles.each do |style|
|
89
|
+
if style
|
90
|
+
build[-1] << buffered
|
91
|
+
buffered = ''
|
92
|
+
|
93
|
+
# set new style
|
94
|
+
build << [style]
|
95
|
+
end
|
96
|
+
buffered << (content.slice!(0,1) || '')
|
97
|
+
end
|
98
|
+
build[-1] << buffered + content
|
99
|
+
build
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.single_line_reversed(columns)
|
103
|
+
map = StyleMap.new(1)
|
104
|
+
map.add(:reverse, 0, 0...columns)
|
105
|
+
map
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/dispel/tools.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Dispel
|
2
|
+
module Tools
|
3
|
+
class << self
|
4
|
+
# http://grosser.it/2011/08/28/ruby-string-naive-split-because-split-is-to-clever/
|
5
|
+
# " ".split(' ') == []
|
6
|
+
# " ".naive_split(' ') == ['','','','']
|
7
|
+
# "".split(' ') == []
|
8
|
+
# "".naive_split(' ') == ['']
|
9
|
+
def naive_split(string, pattern)
|
10
|
+
pattern = /#{Regexp.escape(pattern)}/ unless pattern.is_a?(Regexp)
|
11
|
+
result = string.split(pattern, -1)
|
12
|
+
result.empty? ? [''] : result
|
13
|
+
end
|
14
|
+
|
15
|
+
def memoize(*args)
|
16
|
+
key = args.map(&:to_s).join("-")
|
17
|
+
@memoize ||= {}
|
18
|
+
if @memoize.key?(key)
|
19
|
+
@memoize[key]
|
20
|
+
else
|
21
|
+
@memoize[key] = yield
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def last_element(range)
|
26
|
+
range.exclude_end? ? range.last.pred : range.last
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dispel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Grosser
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIDMjCCAhqgAwIBAgIBADANBgkqhkiG9w0BAQUFADA/MRAwDgYDVQQDDAdtaWNo
|
14
|
+
YWVsMRcwFQYKCZImiZPyLGQBGRYHZ3Jvc3NlcjESMBAGCgmSJomT8ixkARkWAml0
|
15
|
+
MB4XDTEzMDIwMzE4MTMxMVoXDTE0MDIwMzE4MTMxMVowPzEQMA4GA1UEAwwHbWlj
|
16
|
+
aGFlbDEXMBUGCgmSJomT8ixkARkWB2dyb3NzZXIxEjAQBgoJkiaJk/IsZAEZFgJp
|
17
|
+
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMorXo/hgbUq97+kII9H
|
18
|
+
MsQcLdC/7wQ1ZP2OshVHPkeP0qH8MBHGg6eYisOX2ubNagF9YTCZWnhrdKrwpLOO
|
19
|
+
cPLaZbjUjljJ3cQR3B8Yn1veV5IhG86QseTBjymzJWsLpqJ1UZGpfB9tXcsFtuxO
|
20
|
+
6vHvcIHdzvc/OUkICttLbH+1qb6rsHUceqh+JrH4GrsJ5H4hAfIdyS2XMK7YRKbh
|
21
|
+
h+IBu6dFWJJByzFsYmV1PDXln3UBmgAt65cmCu4qPfThioCGDzbSJrGDGLmw/pFX
|
22
|
+
FPpVCm1zgYSb1v6Qnf3cgXa2f2wYGm17+zAVyIDpwryFru9yF/jJxE38z/DRsd9R
|
23
|
+
/88CAwEAAaM5MDcwCQYDVR0TBAIwADAdBgNVHQ4EFgQUsiNnXHtKeMYYcr4yJVmQ
|
24
|
+
WONL+IwwCwYDVR0PBAQDAgSwMA0GCSqGSIb3DQEBBQUAA4IBAQAlyN7kKo/NQCQ0
|
25
|
+
AOzZLZ3WAePvStkCFIJ53tsv5Kyo4pMAllv+BgPzzBt7qi605mFSL6zBd9uLou+W
|
26
|
+
Co3s48p1dy7CjjAfVQdmVNHF3MwXtfC2OEyvSQPi4xKR8iba8wa3xp9LVo1PuLpw
|
27
|
+
/6DsrChWw74HfsJN6qJOK684hJeT8lBYAUfiC3wD0owoPSg+XtyAAddisR+KV5Y1
|
28
|
+
NmVHuLtQcNTZy+gRht3ahJRMuC6QyLmkTsf+6MaenwAMkAgHdswGsJztOnNnBa3F
|
29
|
+
y0kCSWmK6D+x/SbfS6r7Ke07MRqziJdB9GuE1+0cIRuFh8EQ+LN6HXCKM5pon/GU
|
30
|
+
ycwMXfl0
|
31
|
+
-----END CERTIFICATE-----
|
32
|
+
date: 2013-12-14 00:00:00.000000000 Z
|
33
|
+
dependencies: []
|
34
|
+
description:
|
35
|
+
email: michael@grosser.it
|
36
|
+
executables: []
|
37
|
+
extensions: []
|
38
|
+
extra_rdoc_files: []
|
39
|
+
files:
|
40
|
+
- MIT-LICENSE
|
41
|
+
- lib/dispel.rb
|
42
|
+
- lib/dispel/keyboard.rb
|
43
|
+
- lib/dispel/screen.rb
|
44
|
+
- lib/dispel/style_map.rb
|
45
|
+
- lib/dispel/tools.rb
|
46
|
+
- lib/dispel/version.rb
|
47
|
+
homepage: http://github.com/grosser/dispel
|
48
|
+
licenses:
|
49
|
+
- MIT
|
50
|
+
metadata: {}
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 2.0.14
|
68
|
+
signing_key:
|
69
|
+
specification_version: 4
|
70
|
+
summary: Remove evil curses
|
71
|
+
test_files: []
|
metadata.gz.sig
ADDED
Binary file
|