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 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
@@ -0,0 +1,2 @@
1
+ "�
2
+ ���l�s ';֮/��ٝ���.XB��@����S\���˻I����sJg��.M��Z�qUo�J��t�u��$�0�Z�*�7�<D4ct��o�=�u��>m�����8>��
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,8 @@
1
+ require "dispel/version"
2
+
3
+ module Dispel
4
+ autoload :Keyboard, 'dispel/keyboard'
5
+ autoload :Screen, 'dispel/screen'
6
+ autoload :Tools, 'dispel/tools'
7
+ autoload :StyleMap, 'dispel/style_map'
8
+ end
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Dispel
2
+ VERSION = "0.0.0"
3
+ 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