dispel 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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