sus 0.1.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
- data/bin/sus +34 -0
- data/bin/sus-parallel +88 -0
- data/lib/sus/assertions.rb +188 -0
- data/lib/sus/base.rb +44 -0
- data/lib/sus/be.rb +62 -0
- data/lib/sus/command/list.rb +36 -0
- data/lib/sus/command/run.rb +104 -0
- data/lib/sus/command/sequential.rb +35 -0
- data/lib/sus/command/top.rb +26 -0
- data/lib/sus/context.rb +63 -0
- data/lib/sus/describe.rb +42 -0
- data/lib/sus/expect.rb +88 -0
- data/lib/sus/filter.rb +73 -0
- data/lib/sus/identity.rb +80 -0
- data/lib/sus/include_context.rb +10 -0
- data/lib/sus/it.rb +45 -0
- data/lib/sus/it_behaves_like.rb +34 -0
- data/lib/sus/let.rb +18 -0
- data/lib/sus/output/bar.rb +109 -0
- data/lib/sus/output/buffered.rb +78 -0
- data/lib/sus/output/lines.rb +89 -0
- data/lib/sus/output/null.rb +52 -0
- data/lib/sus/output/status.rb +68 -0
- data/lib/sus/output/text.rb +115 -0
- data/lib/sus/output/xterm.rb +86 -0
- data/lib/sus/output.rb +41 -0
- data/lib/sus/progress.rb +144 -0
- data/lib/sus/registry.rb +47 -0
- data/lib/sus/shared.rb +22 -0
- data/lib/sus/version.rb +5 -0
- data/lib/sus/with.rb +48 -0
- data/lib/sus.rb +10 -0
- metadata +90 -0
data/lib/sus/describe.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
require_relative 'context'
|
3
|
+
|
4
|
+
module Sus
|
5
|
+
module Describe
|
6
|
+
extend Context
|
7
|
+
|
8
|
+
attr_accessor :subject
|
9
|
+
|
10
|
+
def self.extended(base)
|
11
|
+
base.children = Hash.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.build(parent, subject, identity: nil, &block)
|
15
|
+
base = Class.new(parent)
|
16
|
+
base.extend(Describe)
|
17
|
+
base.subject = subject
|
18
|
+
base.description = subject.inspect
|
19
|
+
base.identity = identity || Identity.nested(parent.identity, base.description)
|
20
|
+
base.define_method(:subject, ->{subject})
|
21
|
+
|
22
|
+
if block_given?
|
23
|
+
base.class_exec(&block)
|
24
|
+
end
|
25
|
+
|
26
|
+
return base
|
27
|
+
end
|
28
|
+
|
29
|
+
def print(output)
|
30
|
+
output.write(
|
31
|
+
"describe ", :describe, self.description, :reset,
|
32
|
+
# " ", self.identity.to_s
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module Context
|
38
|
+
def describe(...)
|
39
|
+
add Describe.build(self, ...)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/sus/expect.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
|
2
|
+
module Sus
|
3
|
+
class Expect
|
4
|
+
def initialize(assertions, subject)
|
5
|
+
@assertions = assertions
|
6
|
+
@subject = subject
|
7
|
+
@inverted = false
|
8
|
+
end
|
9
|
+
|
10
|
+
def not
|
11
|
+
self.dup.tap do |expect|
|
12
|
+
expect.instance_variable_set(:@inverted, !@inverted)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def print(output)
|
17
|
+
output.write("expect ", :variable, @subject.inspect, :reset, " ")
|
18
|
+
|
19
|
+
if @inverted
|
20
|
+
output.write(:failed, "to not", :reset)
|
21
|
+
else
|
22
|
+
output.write(:passed, "to", :reset)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def to(predicate)
|
27
|
+
@assertions.nested(self, inverted: @inverted) do |assertions|
|
28
|
+
predicate.call(assertions, @subject)
|
29
|
+
end
|
30
|
+
|
31
|
+
return self
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_throw(...)
|
35
|
+
predicate = ThrowException.new(...)
|
36
|
+
|
37
|
+
@assertions.nested(self, inverted: @inverted) do |assertions|
|
38
|
+
predicate.call(assertions, @subject)
|
39
|
+
end
|
40
|
+
|
41
|
+
return self
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Base
|
46
|
+
def expect(subject)
|
47
|
+
Expect.new(@assertions, subject)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class ThrowException
|
52
|
+
def initialize(exception_class, message = nil)
|
53
|
+
@exception_class = exception_class
|
54
|
+
@message = message
|
55
|
+
end
|
56
|
+
|
57
|
+
def call(assertions, value)
|
58
|
+
assertions.nested(self) do |assertions|
|
59
|
+
begin
|
60
|
+
value.call
|
61
|
+
|
62
|
+
# Didn't throw any exception, so the expectation failed:
|
63
|
+
assertions.assert(false, self)
|
64
|
+
rescue => exception
|
65
|
+
# Did we throw the right kind of exception?
|
66
|
+
if exception.is_a?(@exception_class)
|
67
|
+
# Did it have the right message?
|
68
|
+
if @message
|
69
|
+
assertions.assert(@message === exception.message)
|
70
|
+
else
|
71
|
+
assertions.assert(true, self)
|
72
|
+
end
|
73
|
+
else
|
74
|
+
raise
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def print(output)
|
81
|
+
output << "throw exception " << output.style(@exception_class, :variable)
|
82
|
+
|
83
|
+
if @message
|
84
|
+
output << "with message " << output.style(@message, :variable)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/sus/filter.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
|
2
|
+
module Sus
|
3
|
+
class Filter
|
4
|
+
class Index
|
5
|
+
def initialize
|
6
|
+
@contexts = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
attr :contexts
|
10
|
+
|
11
|
+
def add(parent)
|
12
|
+
parent.children&.each do |identity, child|
|
13
|
+
insert(identity, child)
|
14
|
+
add(child)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def insert(identity, context)
|
19
|
+
@contexts[identity.key] = context
|
20
|
+
end
|
21
|
+
|
22
|
+
def [] key
|
23
|
+
@contexts[key]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(registry: Registry.new)
|
28
|
+
@registry = registry
|
29
|
+
@index = nil
|
30
|
+
@keys = Array.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def load(target)
|
34
|
+
path, filter = target.split(':', 2)
|
35
|
+
|
36
|
+
@registry.load(path)
|
37
|
+
|
38
|
+
if filter
|
39
|
+
@keys << target
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def each(&block)
|
44
|
+
if @keys.any?
|
45
|
+
@index = Index.new
|
46
|
+
@index.add(@registry)
|
47
|
+
|
48
|
+
@keys.each do |key|
|
49
|
+
if target = @index[key]
|
50
|
+
yield target
|
51
|
+
end
|
52
|
+
end
|
53
|
+
else
|
54
|
+
@registry.each(&block)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def call(assertions = Assertions.default)
|
59
|
+
if @keys.any?
|
60
|
+
@index = Index.new
|
61
|
+
@index.add(@registry)
|
62
|
+
|
63
|
+
@keys.each do |key|
|
64
|
+
@index[key]&.call(assertions)
|
65
|
+
end
|
66
|
+
else
|
67
|
+
@registry.call(assertions)
|
68
|
+
end
|
69
|
+
|
70
|
+
return assertions
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/sus/identity.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
module Sus
|
3
|
+
class Identity
|
4
|
+
def self.nested(parent, name, location = nil, **options)
|
5
|
+
location ||= caller_locations(3...4).first
|
6
|
+
|
7
|
+
self.new(location.path, name, location.lineno, parent, **options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(path, name = nil, line = nil, parent = nil, unique: true)
|
11
|
+
@path = path
|
12
|
+
@name = name
|
13
|
+
@line = line
|
14
|
+
@parent = parent
|
15
|
+
@unique = unique
|
16
|
+
|
17
|
+
@key = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
attr :path
|
21
|
+
attr :name
|
22
|
+
attr :line
|
23
|
+
attr :parent
|
24
|
+
attr :unique
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
self.key
|
28
|
+
end
|
29
|
+
|
30
|
+
def inspect
|
31
|
+
"\#<#{self.class} #{self.to_s}>"
|
32
|
+
end
|
33
|
+
|
34
|
+
def match?(other)
|
35
|
+
if path = other.path
|
36
|
+
return false unless path === @path
|
37
|
+
end
|
38
|
+
|
39
|
+
if name = other.name
|
40
|
+
return false unless name === @name
|
41
|
+
end
|
42
|
+
|
43
|
+
if line = other.line
|
44
|
+
return false unless line === @line
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def each(&block)
|
49
|
+
@parent&.each(&block)
|
50
|
+
|
51
|
+
yield self
|
52
|
+
end
|
53
|
+
|
54
|
+
def key
|
55
|
+
unless @key
|
56
|
+
key = Array.new
|
57
|
+
|
58
|
+
append_unique_key(key, false)
|
59
|
+
|
60
|
+
@key = key.join(':')
|
61
|
+
end
|
62
|
+
|
63
|
+
return @key
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def append_unique_key(key, unique = @unique)
|
69
|
+
if @parent
|
70
|
+
@parent.append_unique_key(key)
|
71
|
+
else
|
72
|
+
key << @path
|
73
|
+
end
|
74
|
+
|
75
|
+
if @line
|
76
|
+
key << @line unless unique
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/sus/it.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
|
2
|
+
require_relative 'context'
|
3
|
+
|
4
|
+
module Sus
|
5
|
+
module It
|
6
|
+
def self.build(parent, description, &block)
|
7
|
+
base = Class.new(parent)
|
8
|
+
base.extend(It)
|
9
|
+
base.description = description
|
10
|
+
base.identity = Identity.nested(parent.identity, base.description)
|
11
|
+
|
12
|
+
if block_given?
|
13
|
+
base.define_method(:call, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
return base
|
17
|
+
end
|
18
|
+
|
19
|
+
def leaf?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def print(output)
|
24
|
+
self.superclass.print(output)
|
25
|
+
output.write(" it ", :it, self.description, :reset, " ", self.identity.to_s)
|
26
|
+
end
|
27
|
+
|
28
|
+
def call(assertions)
|
29
|
+
sleep rand
|
30
|
+
assertions.nested(self, isolated: true) do |assertions|
|
31
|
+
instance = self.new(assertions)
|
32
|
+
|
33
|
+
instance.around do
|
34
|
+
instance.call
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module Context
|
41
|
+
def it(...)
|
42
|
+
add It.build(self, ...)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
require_relative 'context'
|
3
|
+
|
4
|
+
module Sus
|
5
|
+
module ItBehavesLike
|
6
|
+
extend Context
|
7
|
+
|
8
|
+
attr_accessor :shared
|
9
|
+
|
10
|
+
def self.extended(base)
|
11
|
+
base.children = Hash.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.build(parent, shared)
|
15
|
+
base = Class.new(parent)
|
16
|
+
base.extend(ItBehavesLike)
|
17
|
+
base.description = shared.name
|
18
|
+
base.identity = Identity.nested(parent.identity, base.description, unique: false)
|
19
|
+
base.class_exec(&shared.block)
|
20
|
+
return base
|
21
|
+
end
|
22
|
+
|
23
|
+
def print(output)
|
24
|
+
self.superclass.print(output)
|
25
|
+
output.write(" it behaves like ", :describe, self.description, :reset)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module Context
|
30
|
+
def it_behaves_like(shared)
|
31
|
+
add ItBehavesLike.build(self, shared)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/sus/let.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
require_relative 'context'
|
3
|
+
|
4
|
+
module Sus
|
5
|
+
module Context
|
6
|
+
def let(name, &block)
|
7
|
+
ivar = :"@#{name}"
|
8
|
+
|
9
|
+
self.define_method(name) do
|
10
|
+
if value = self.instance_variable_get(ivar)
|
11
|
+
return value
|
12
|
+
else
|
13
|
+
self.instance_variable_set(ivar, self.instance_exec(&block))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
module Sus
|
24
|
+
module Output
|
25
|
+
class Bar
|
26
|
+
BLOCK = [
|
27
|
+
" ",
|
28
|
+
"▏",
|
29
|
+
"▎",
|
30
|
+
"▍",
|
31
|
+
"▌",
|
32
|
+
"▋",
|
33
|
+
"▊",
|
34
|
+
"▉",
|
35
|
+
"█",
|
36
|
+
]
|
37
|
+
|
38
|
+
def initialize(current = 0, total = 0, message = nil)
|
39
|
+
@maximum_message_width = 0
|
40
|
+
|
41
|
+
@current = current
|
42
|
+
@total = total
|
43
|
+
@message = message
|
44
|
+
end
|
45
|
+
|
46
|
+
def update(current, total, message)
|
47
|
+
@current = current
|
48
|
+
@total = total
|
49
|
+
@message = message
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.register(output)
|
53
|
+
output[:progress_bar] ||= output.style(:blue, :white)
|
54
|
+
end
|
55
|
+
|
56
|
+
MINIMUM_WIDTH = 8
|
57
|
+
MESSAGE_SUFFIX = ": "
|
58
|
+
|
59
|
+
def print(output)
|
60
|
+
width = output.width
|
61
|
+
|
62
|
+
unless @total.zero?
|
63
|
+
value = @current.to_f / @total.to_f
|
64
|
+
else
|
65
|
+
value = 0.0
|
66
|
+
end
|
67
|
+
|
68
|
+
if @message
|
69
|
+
message = @message + MESSAGE_SUFFIX
|
70
|
+
if message.size > @maximum_message_width
|
71
|
+
@maximum_message_width = message.size
|
72
|
+
end
|
73
|
+
|
74
|
+
if @maximum_message_width < (width - MINIMUM_WIDTH)
|
75
|
+
width -= @maximum_message_width
|
76
|
+
message = message.rjust(@maximum_message_width)
|
77
|
+
else
|
78
|
+
@maximum_message_width = 0
|
79
|
+
message = nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
if message
|
84
|
+
output.write(message)
|
85
|
+
end
|
86
|
+
|
87
|
+
output.write(
|
88
|
+
:progress_bar, draw(value, width), :reset,
|
89
|
+
)
|
90
|
+
|
91
|
+
output.puts
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def draw(value, width)
|
97
|
+
blocks = width * value
|
98
|
+
full_blocks = blocks.floor
|
99
|
+
partial_block = ((blocks - full_blocks) * BLOCK.size).floor
|
100
|
+
|
101
|
+
if partial_block.zero?
|
102
|
+
BLOCK.last * full_blocks
|
103
|
+
else
|
104
|
+
"#{BLOCK.last * full_blocks}#{BLOCK[partial_block]}"
|
105
|
+
end.ljust(width)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'io/console'
|
24
|
+
require 'stringio'
|
25
|
+
|
26
|
+
module Sus
|
27
|
+
# Styled output output.
|
28
|
+
module Output
|
29
|
+
class Buffered
|
30
|
+
def initialize(output)
|
31
|
+
@output = output
|
32
|
+
@buffer = Array.new
|
33
|
+
end
|
34
|
+
|
35
|
+
attr :output
|
36
|
+
attr :buffer
|
37
|
+
|
38
|
+
def append(output)
|
39
|
+
@buffer.each do |operation|
|
40
|
+
output.public_send(*operation)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def string
|
45
|
+
io = StringIO.new
|
46
|
+
append(Text.new(io))
|
47
|
+
return io.string
|
48
|
+
end
|
49
|
+
|
50
|
+
def indent
|
51
|
+
@buffer << [:indent]
|
52
|
+
@output.indent
|
53
|
+
end
|
54
|
+
|
55
|
+
def outdent
|
56
|
+
@buffer << [:outdent]
|
57
|
+
@output.outdent
|
58
|
+
end
|
59
|
+
|
60
|
+
def indented
|
61
|
+
self.indent
|
62
|
+
yield
|
63
|
+
ensure
|
64
|
+
self.outdent
|
65
|
+
end
|
66
|
+
|
67
|
+
def write(*arguments)
|
68
|
+
@output.write(*arguments)
|
69
|
+
@buffer << [:write, *arguments]
|
70
|
+
end
|
71
|
+
|
72
|
+
def puts(*arguments)
|
73
|
+
@output.puts(*arguments)
|
74
|
+
@buffer << [:puts, *arguments]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
|
2
|
+
require 'io/console'
|
3
|
+
|
4
|
+
module Sus
|
5
|
+
module Output
|
6
|
+
class Lines
|
7
|
+
def initialize(output)
|
8
|
+
@output = output
|
9
|
+
@lines = []
|
10
|
+
|
11
|
+
@current_count = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def height
|
15
|
+
@output.size.first
|
16
|
+
end
|
17
|
+
|
18
|
+
def []= index, line
|
19
|
+
@lines[index] = line
|
20
|
+
|
21
|
+
redraw(index)
|
22
|
+
end
|
23
|
+
|
24
|
+
def clear
|
25
|
+
@lines.clear
|
26
|
+
write
|
27
|
+
end
|
28
|
+
|
29
|
+
def redraw(index)
|
30
|
+
if index < @current_count
|
31
|
+
update(index, @lines[index])
|
32
|
+
else
|
33
|
+
write
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def soft_wrap
|
40
|
+
@output.write("\e[?7l")
|
41
|
+
|
42
|
+
yield
|
43
|
+
ensure
|
44
|
+
@output.write("\e[?7h")
|
45
|
+
end
|
46
|
+
|
47
|
+
def origin
|
48
|
+
if @current_count > 0
|
49
|
+
@output.write("\e[#{@current_count}F\e[J")
|
50
|
+
end
|
51
|
+
|
52
|
+
@current_count = 0
|
53
|
+
end
|
54
|
+
|
55
|
+
def write
|
56
|
+
origin
|
57
|
+
|
58
|
+
height = self.height
|
59
|
+
|
60
|
+
soft_wrap do
|
61
|
+
@lines.each do |line|
|
62
|
+
break if (@current_count+1) >= height
|
63
|
+
|
64
|
+
if line
|
65
|
+
line.print(@output)
|
66
|
+
else
|
67
|
+
@output.puts
|
68
|
+
end
|
69
|
+
|
70
|
+
@current_count += 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def update(index, line)
|
76
|
+
offset = @current_count - index
|
77
|
+
|
78
|
+
@output.write("\e[#{offset}F\e[K")
|
79
|
+
soft_wrap do
|
80
|
+
line.print(@output)
|
81
|
+
end
|
82
|
+
|
83
|
+
if offset > 1
|
84
|
+
@output.write("\e[#{offset-1}E")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|