dispel 0.0.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 +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
|