gamefic 1.4.1 → 1.5.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 +4 -4
- data/lib/gamefic.rb +1 -2
- data/lib/gamefic/character.rb +31 -45
- data/lib/gamefic/director/delegate.rb +46 -24
- data/lib/gamefic/director/order.rb +7 -0
- data/lib/gamefic/director/parser.rb +11 -11
- data/lib/gamefic/engine/base.rb +2 -3
- data/lib/gamefic/entity.rb +8 -26
- data/lib/gamefic/plot.rb +38 -242
- data/lib/gamefic/plot/callbacks.rb +101 -0
- data/lib/gamefic/plot/command_mount.rb +70 -40
- data/lib/gamefic/plot/entities.rb +82 -0
- data/lib/gamefic/plot/host.rb +46 -0
- data/lib/gamefic/plot/playbook.rb +192 -0
- data/lib/gamefic/plot/players.rb +15 -0
- data/lib/gamefic/plot/scene_mount.rb +69 -31
- data/lib/gamefic/plot/snapshot.rb +20 -5
- data/lib/gamefic/scene/active.rb +8 -1
- data/lib/gamefic/scene/base.rb +4 -26
- data/lib/gamefic/scene/custom.rb +53 -3
- data/lib/gamefic/scene/multiple_choice.rb +1 -0
- data/lib/gamefic/scene/yes_or_no.rb +1 -1
- data/lib/gamefic/scene_data/multiple_scene.rb +0 -4
- data/lib/gamefic/shell.rb +0 -1
- data/lib/gamefic/source/file.rb +1 -1
- data/lib/gamefic/stage.rb +10 -2
- data/lib/gamefic/subplot.rb +70 -53
- data/lib/gamefic/syntax.rb +9 -2
- data/lib/gamefic/tester.rb +1 -1
- data/lib/gamefic/text.rb +8 -0
- data/lib/gamefic/{ansi.rb → text/ansi.rb} +12 -15
- data/lib/gamefic/text/html.rb +68 -0
- data/lib/gamefic/text/html/conversions.rb +250 -0
- data/lib/gamefic/text/html/entities.rb +9 -0
- data/lib/gamefic/tty.rb +2 -0
- data/lib/gamefic/user/tty.rb +2 -4
- data/lib/gamefic/version.rb +1 -1
- metadata +12 -8
- data/lib/gamefic/direction.rb +0 -46
- data/lib/gamefic/html.rb +0 -68
- data/lib/gamefic/html_to_ansi.rb +0 -185
- data/lib/gamefic/plot/entity_mount.rb +0 -45
- data/lib/gamefic/rule.rb +0 -18
@@ -15,7 +15,7 @@ module Gamefic
|
|
15
15
|
if data.yes? or data.no?
|
16
16
|
this_scene = actor.scene
|
17
17
|
do_finish_block actor, data
|
18
|
-
actor.cue :active if (actor.scene == this_scene and actor.next_scene.nil?)
|
18
|
+
#actor.cue :active if (actor.scene == this_scene and actor.next_scene.nil?)
|
19
19
|
else
|
20
20
|
actor.tell data.invalid_message
|
21
21
|
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
module Gamefic
|
2
2
|
|
3
3
|
class SceneData::MultipleScene < SceneData::MultipleChoice
|
4
|
-
attr_accessor :selection
|
5
|
-
attr_accessor :number
|
6
4
|
def options
|
7
5
|
scene_map.keys
|
8
6
|
end
|
@@ -15,8 +13,6 @@ module Gamefic
|
|
15
13
|
scene_map[choice]
|
16
14
|
end
|
17
15
|
|
18
|
-
private
|
19
|
-
|
20
16
|
def scene_map
|
21
17
|
@scene_map ||= {}
|
22
18
|
end
|
data/lib/gamefic/shell.rb
CHANGED
data/lib/gamefic/source/file.rb
CHANGED
data/lib/gamefic/stage.rb
CHANGED
@@ -3,16 +3,17 @@ module Gamefic
|
|
3
3
|
module Stage
|
4
4
|
def stage *args, &block
|
5
5
|
s = generate_stage
|
6
|
-
|
7
6
|
if block.nil?
|
8
7
|
s.module_eval(*args)
|
9
8
|
else
|
10
9
|
s.module_exec(*args, &block)
|
11
10
|
end
|
12
11
|
end
|
12
|
+
|
13
13
|
private
|
14
|
+
|
14
15
|
def generate_stage
|
15
|
-
return @stage
|
16
|
+
return @stage unless @stage.nil?
|
16
17
|
|
17
18
|
exposed = self.class.exposed_methods.keys
|
18
19
|
mounted = self.class.mounted_modules.keys
|
@@ -44,6 +45,7 @@ module Gamefic
|
|
44
45
|
|
45
46
|
return @stage
|
46
47
|
end
|
48
|
+
|
47
49
|
module ClassMethods
|
48
50
|
def mount *args
|
49
51
|
args.each { |a|
|
@@ -51,22 +53,28 @@ module Gamefic
|
|
51
53
|
mounted_modules[a] = nil
|
52
54
|
}
|
53
55
|
end
|
56
|
+
|
54
57
|
def expose *args
|
55
58
|
args.each { |a|
|
56
59
|
exposed_methods[a] = nil
|
57
60
|
}
|
58
61
|
end
|
62
|
+
|
59
63
|
def exposed_methods
|
60
64
|
@@exposed_methods ||= from_superclass(:exposed_methods, {}).dup
|
61
65
|
end
|
66
|
+
|
62
67
|
def mounted_modules
|
63
68
|
@@mounted_modules ||= from_superclass(:mounted_modules, {}).dup
|
64
69
|
end
|
70
|
+
|
65
71
|
private
|
72
|
+
|
66
73
|
def from_superclass(m, default = nil)
|
67
74
|
superclass.respond_to?(m) ? superclass.send(m) : default
|
68
75
|
end
|
69
76
|
end
|
77
|
+
|
70
78
|
def self.included(base)
|
71
79
|
base.extend(ClassMethods)
|
72
80
|
end
|
data/lib/gamefic/subplot.rb
CHANGED
@@ -1,82 +1,99 @@
|
|
1
|
+
require 'gamefic/plot'
|
2
|
+
|
1
3
|
module Gamefic
|
2
4
|
|
3
5
|
class Subplot
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
#
|
19
|
-
# @return [Array<Subplot>]
|
20
|
-
def subplots
|
21
|
-
p_subplots.clone
|
22
|
-
end
|
23
|
-
|
24
|
-
# Start a new subplot based on the provided class.
|
25
|
-
#
|
26
|
-
# @param [Class] The class of the subplot to be created (Subplot by default)
|
27
|
-
# @return [Subplot]
|
28
|
-
def branch subplot_class, feature:nil, &block
|
29
|
-
subplot = subplot_class.new(self, feature: feature, &block)
|
30
|
-
p_subplots.push subplot
|
31
|
-
subplot
|
6
|
+
include Stage
|
7
|
+
|
8
|
+
attr_reader :plot
|
9
|
+
attr_writer :denied_message
|
10
|
+
|
11
|
+
mount Plot::Entities
|
12
|
+
mount Plot::CommandMount
|
13
|
+
mount Plot::Callbacks
|
14
|
+
mount Plot::SceneMount
|
15
|
+
expose :plot, :conclude
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def start_proc
|
19
|
+
@start_proc
|
32
20
|
end
|
33
|
-
|
21
|
+
|
34
22
|
private
|
35
|
-
|
36
|
-
|
23
|
+
|
24
|
+
def on_start &block
|
25
|
+
@start_proc = block
|
37
26
|
end
|
38
27
|
end
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
def initialize plot, feature:nil
|
28
|
+
|
29
|
+
def initialize plot, introduce: nil
|
43
30
|
@plot = plot
|
44
|
-
@entities = []
|
45
|
-
@players = []
|
46
31
|
@concluded = false
|
47
|
-
|
48
|
-
|
49
|
-
introduce
|
32
|
+
stage &self.class.start_proc unless self.class.start_proc.nil?
|
33
|
+
playbook.freeze
|
34
|
+
self.introduce introduce unless introduce.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
def default_scene
|
38
|
+
plot.default_scene
|
39
|
+
end
|
40
|
+
|
41
|
+
def default_conclusion
|
42
|
+
plot.default_conclusion
|
43
|
+
end
|
44
|
+
|
45
|
+
def playbook
|
46
|
+
@playbook ||= plot.playbook.dup
|
50
47
|
end
|
51
|
-
|
48
|
+
|
49
|
+
# HACK: Always assume subplots are running for the sake of entity destruction
|
50
|
+
def running?
|
51
|
+
true
|
52
52
|
end
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
e.instance_variable_set(:@subplot, self)
|
57
|
-
entities.push e
|
58
|
-
e
|
53
|
+
|
54
|
+
def denied_message
|
55
|
+
@denied_message ||= 'You are already involved in another subplot.'
|
59
56
|
end
|
57
|
+
|
60
58
|
def introduce player
|
61
|
-
|
62
|
-
|
59
|
+
if plot.subbed?(player)
|
60
|
+
player.tell denied_message
|
61
|
+
else
|
62
|
+
super
|
63
|
+
end
|
63
64
|
end
|
65
|
+
|
64
66
|
def exeunt player
|
65
|
-
player.
|
66
|
-
|
67
|
+
player.playbook = plot.playbook
|
68
|
+
p_players.delete player
|
67
69
|
end
|
70
|
+
|
68
71
|
def conclude
|
69
|
-
concluded = true
|
72
|
+
@concluded = true
|
70
73
|
entities.each { |e|
|
71
|
-
e
|
74
|
+
destroy e
|
72
75
|
}
|
73
76
|
players.each { |p|
|
74
77
|
exeunt p
|
75
78
|
}
|
76
79
|
end
|
80
|
+
|
77
81
|
def concluded?
|
78
82
|
@concluded
|
79
83
|
end
|
84
|
+
|
85
|
+
def ready
|
86
|
+
conclude if players.empty?
|
87
|
+
return if concluded?
|
88
|
+
playbook.freeze
|
89
|
+
call_ready
|
90
|
+
call_player_ready
|
91
|
+
end
|
92
|
+
|
93
|
+
def update
|
94
|
+
call_player_update
|
95
|
+
call_update
|
96
|
+
end
|
80
97
|
end
|
81
98
|
|
82
99
|
end
|
data/lib/gamefic/syntax.rb
CHANGED
@@ -60,7 +60,10 @@ module Gamefic
|
|
60
60
|
m = text.match(@regexp)
|
61
61
|
return nil if m.nil?
|
62
62
|
arguments = []
|
63
|
-
|
63
|
+
# HACK: Skip the first word if the verb is not nil.
|
64
|
+
# This is ugly.
|
65
|
+
b = @verb.nil? ? 0 : 1
|
66
|
+
@replace.to_s.split_words[b..-1].each { |r|
|
64
67
|
if r.match(/^\{\$[0-9]+\}$/)
|
65
68
|
arguments.push m[r[2..-2].to_i]
|
66
69
|
else
|
@@ -70,6 +73,10 @@ module Gamefic
|
|
70
73
|
Command.new @verb, arguments
|
71
74
|
end
|
72
75
|
|
76
|
+
def accept? text
|
77
|
+
!text.match(@regexp).nil?
|
78
|
+
end
|
79
|
+
|
73
80
|
# Get a signature that identifies the form of the Syntax.
|
74
81
|
# Signatures are used to compare Syntaxes to each other.
|
75
82
|
#
|
@@ -106,7 +113,7 @@ module Gamefic
|
|
106
113
|
}
|
107
114
|
cb <=> ca
|
108
115
|
}
|
109
|
-
matches
|
116
|
+
matches
|
110
117
|
end
|
111
118
|
end
|
112
119
|
|
data/lib/gamefic/tester.rb
CHANGED
@@ -9,7 +9,7 @@ module Gamefic
|
|
9
9
|
end
|
10
10
|
def run_test name, actor
|
11
11
|
queue = []
|
12
|
-
|
12
|
+
stage actor, queue, &test_procs[name]
|
13
13
|
actor.queue.push *queue
|
14
14
|
actor[:test_queue_length] = queue.length
|
15
15
|
actor[:test_queue_scene] = actor.scene
|
data/lib/gamefic/text.rb
ADDED
@@ -1,11 +1,8 @@
|
|
1
1
|
module Gamefic
|
2
2
|
|
3
|
-
# Constants for ANSI codes, plus
|
4
|
-
module Ansi
|
3
|
+
# Constants for ANSI codes, plus ExtraCodes for custom formatting.
|
4
|
+
module Text::Ansi
|
5
5
|
module Code
|
6
|
-
class Nonstandard < String
|
7
|
-
|
8
|
-
end
|
9
6
|
module Attribute
|
10
7
|
NORMAL = 0
|
11
8
|
BOLD = 1
|
@@ -35,19 +32,19 @@ module Gamefic
|
|
35
32
|
WHITE = 47
|
36
33
|
end
|
37
34
|
module Extra
|
38
|
-
BLOCK =
|
39
|
-
PRE =
|
40
|
-
HREF =
|
41
|
-
IMAGE =
|
42
|
-
SRC =
|
43
|
-
UPPERCASE =
|
44
|
-
COMMAND =
|
45
|
-
IGNORED =
|
46
|
-
LINE =
|
35
|
+
BLOCK = :block
|
36
|
+
PRE = :pre
|
37
|
+
HREF = :href
|
38
|
+
IMAGE = :image
|
39
|
+
SRC = :src
|
40
|
+
UPPERCASE = :uppercase
|
41
|
+
COMMAND = :command
|
42
|
+
IGNORED = :ignored
|
43
|
+
LINE = :line
|
47
44
|
end
|
48
45
|
end
|
49
46
|
def self.graphics_mode(*settings)
|
50
|
-
ansi = settings.flatten.
|
47
|
+
ansi = settings.flatten.that_are(Fixnum)
|
51
48
|
return '' if ansi.length == 0
|
52
49
|
"\e[#{ansi.join(';')}m"
|
53
50
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'gamefic/text/html/entities'
|
3
|
+
|
4
|
+
module Gamefic
|
5
|
+
module Text
|
6
|
+
module Html
|
7
|
+
autoload :Conversions, 'gamefic/text/html/conversions'
|
8
|
+
|
9
|
+
# Convert ampersands to &
|
10
|
+
#
|
11
|
+
# @param text [String]
|
12
|
+
# @return [String]
|
13
|
+
def self.fix_ampersands(text)
|
14
|
+
codes = []
|
15
|
+
ENTITIES.keys.each { |e|
|
16
|
+
codes.push e[1..-1]
|
17
|
+
}
|
18
|
+
piped = codes.join('|')
|
19
|
+
re = Regexp.new("&(?!(#{piped}))")
|
20
|
+
text.gsub(re, '&\1')
|
21
|
+
end
|
22
|
+
|
23
|
+
# Encode a String with HTML entities
|
24
|
+
#
|
25
|
+
# @param text [String]
|
26
|
+
# @return [String]
|
27
|
+
def self.encode(text)
|
28
|
+
encoded = text
|
29
|
+
ENTITIES.each { |k, v|
|
30
|
+
encoded = encoded.gsub(v, k)
|
31
|
+
}
|
32
|
+
encoded
|
33
|
+
end
|
34
|
+
|
35
|
+
# Decode a String's HTML entities
|
36
|
+
#
|
37
|
+
# @param text [String]
|
38
|
+
# @return [String]
|
39
|
+
def self.decode(text)
|
40
|
+
ENTITIES.each { |k, v|
|
41
|
+
text = text.gsub(k, v)
|
42
|
+
}
|
43
|
+
text
|
44
|
+
end
|
45
|
+
|
46
|
+
# Parse a String into an XML document
|
47
|
+
#
|
48
|
+
# @param code [String]
|
49
|
+
# @return [REXML::Document]
|
50
|
+
def self.parse(code)
|
51
|
+
code = fix_ampersands(code).strip
|
52
|
+
last = nil
|
53
|
+
begin
|
54
|
+
doc = REXML::Document.new code
|
55
|
+
rescue REXML::ParseException => e
|
56
|
+
# Convert invalid < characters to <
|
57
|
+
if e.source.buffer != last and e.source.buffer[0,1] == '<'
|
58
|
+
code = code[0,(code.length - e.source.buffer.length)] + '<' + e.source.buffer[1..-1]
|
59
|
+
last = e.source.buffer
|
60
|
+
retry
|
61
|
+
end
|
62
|
+
raise e
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
require 'gamefic/text/ansi'
|
2
|
+
require 'gamefic/text/html'
|
3
|
+
require 'io/console'
|
4
|
+
|
5
|
+
module Gamefic
|
6
|
+
|
7
|
+
module Text::Html::Conversions
|
8
|
+
include Gamefic::Text
|
9
|
+
|
10
|
+
def self.html_to_ansi text, wrap: true, width: nil
|
11
|
+
return '' if text.strip == ''
|
12
|
+
output = ''
|
13
|
+
begin
|
14
|
+
doc = Html.parse("<body>#{text.gsub(/\r/, '').strip}</body>")
|
15
|
+
output = AnsiFormatter.new.format(doc) + Ansi.graphics_mode(Ansi::Code::Attribute::NORMAL)
|
16
|
+
output = Html.decode(output)
|
17
|
+
rescue REXML::ParseException => e
|
18
|
+
output = text.strip
|
19
|
+
end
|
20
|
+
calc_width = width || size[0]
|
21
|
+
if calc_width.nil? or !wrap
|
22
|
+
output
|
23
|
+
else
|
24
|
+
terminalize(output, calc_width - 1)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.html_to_text text, wrap: true, width: nil
|
29
|
+
text = html_to_ansi text, wrap: wrap, width: width
|
30
|
+
text.gsub(/\e\[([;\d]+)?m/, '').gsub(/\n +\n/, "\n\n")
|
31
|
+
end
|
32
|
+
|
33
|
+
class AnsiNode
|
34
|
+
include Gamefic::Text::Ansi::Code
|
35
|
+
attr_accessor :parent
|
36
|
+
|
37
|
+
def render
|
38
|
+
end
|
39
|
+
|
40
|
+
def in_block?
|
41
|
+
p = parent
|
42
|
+
while !p.nil?
|
43
|
+
return true if p.kind_of?(BlockNode)
|
44
|
+
p = p.parent
|
45
|
+
end
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
private_constant :AnsiNode
|
50
|
+
|
51
|
+
class TextNode < AnsiNode
|
52
|
+
@@prev_format = nil
|
53
|
+
attr_reader :text, :format
|
54
|
+
def initialize text, format
|
55
|
+
@text = text
|
56
|
+
@format = format
|
57
|
+
end
|
58
|
+
def render
|
59
|
+
return @text if format.include?(Extra::PRE)
|
60
|
+
index = parent.children.index(self)
|
61
|
+
if index > 0
|
62
|
+
prev = parent.children[index - 1]
|
63
|
+
if prev.kind_of?(TextNode) and prev.format == format
|
64
|
+
if prev.text.match(/ $/)
|
65
|
+
return @text.lstrip
|
66
|
+
else
|
67
|
+
return @text
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
if @@prev_format == format
|
72
|
+
@text
|
73
|
+
else
|
74
|
+
@prev_format = format
|
75
|
+
Gamefic::Text::Ansi.graphics_mode(*format) + @text
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
private_constant :TextNode
|
80
|
+
|
81
|
+
class ElementNode < AnsiNode
|
82
|
+
def children
|
83
|
+
@children ||= []
|
84
|
+
end
|
85
|
+
def append child
|
86
|
+
children.push child
|
87
|
+
child.parent = self
|
88
|
+
end
|
89
|
+
end
|
90
|
+
private_constant :ElementNode
|
91
|
+
|
92
|
+
class BlockNode < ElementNode
|
93
|
+
def render
|
94
|
+
output = ''
|
95
|
+
children.each { |c|
|
96
|
+
output += c.render
|
97
|
+
}
|
98
|
+
output = "\n" + output.strip unless in_block?
|
99
|
+
output
|
100
|
+
end
|
101
|
+
end
|
102
|
+
private_constant :BlockNode
|
103
|
+
|
104
|
+
class InlineNode < ElementNode
|
105
|
+
def render
|
106
|
+
output = ''
|
107
|
+
children.each { |c|
|
108
|
+
output += c.render
|
109
|
+
output += "\n" if c.kind_of?(BlockNode)
|
110
|
+
}
|
111
|
+
output
|
112
|
+
end
|
113
|
+
end
|
114
|
+
private_constant :InlineNode
|
115
|
+
|
116
|
+
class BreakNode < ElementNode
|
117
|
+
def render
|
118
|
+
output = ''
|
119
|
+
children.each { |c|
|
120
|
+
output += c.render
|
121
|
+
}
|
122
|
+
output + "\n"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
private_constant :BreakNode
|
126
|
+
|
127
|
+
class AnsiFormatter
|
128
|
+
include Gamefic::Text::Ansi::Code
|
129
|
+
def format document
|
130
|
+
@document = document
|
131
|
+
@ansi_root = InlineNode.new
|
132
|
+
@list_index = []
|
133
|
+
format_recursively @document.root, @ansi_root, [Attribute::NORMAL]
|
134
|
+
output = @ansi_root.render
|
135
|
+
output += (@ansi_root.children.last.kind_of?(BlockNode) ? "\n" : "")
|
136
|
+
end
|
137
|
+
|
138
|
+
def format_recursively element, ansi_node, stack
|
139
|
+
if element.is_a?(REXML::Text)
|
140
|
+
append_text element, ansi_node, stack
|
141
|
+
else
|
142
|
+
current = []
|
143
|
+
case element.name
|
144
|
+
when 'b', 'strong', 'em'
|
145
|
+
current.push Attribute::BOLD
|
146
|
+
format_children element, ansi_node, stack + current
|
147
|
+
when 'i', 'u'
|
148
|
+
current.push Attribute::UNDERSCORE
|
149
|
+
format_children element, ansi_node, stack + current
|
150
|
+
when 'h1', 'h2', 'h3', 'h4', 'h5'
|
151
|
+
current.push Attribute::BOLD, Extra::UPPERCASE
|
152
|
+
format_paragraph element, ansi_node, stack + current
|
153
|
+
when 'p'
|
154
|
+
format_paragraph element, ansi_node, stack
|
155
|
+
when 'ol', 'ul'
|
156
|
+
@list_index.push 0
|
157
|
+
format_paragraph element, ansi_node, stack
|
158
|
+
@list_index.pop
|
159
|
+
when 'li'
|
160
|
+
format_list_item element, ansi_node, stack
|
161
|
+
when 'pre'
|
162
|
+
current.push Extra::PRE
|
163
|
+
format_children element, ansi_node, stack + current
|
164
|
+
when 'br'
|
165
|
+
ansi_node.append TextNode.new("\n", stack + [Extra::PRE])
|
166
|
+
else
|
167
|
+
format_children element, ansi_node, stack
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def append_text element, ansi_node, stack
|
173
|
+
text = element.to_s
|
174
|
+
text.gsub!(/[\s]+/, ' ') unless stack.include?(Extra::PRE)
|
175
|
+
text.upcase! if stack.include?(Extra::UPPERCASE)
|
176
|
+
ansi_node.append TextNode.new(text, stack)
|
177
|
+
end
|
178
|
+
|
179
|
+
def format_children element, node, stack
|
180
|
+
element.each { |e|
|
181
|
+
format_recursively e, node, stack
|
182
|
+
}
|
183
|
+
end
|
184
|
+
|
185
|
+
def format_paragraph element, node, stack
|
186
|
+
paragraph = BlockNode.new
|
187
|
+
node.append paragraph
|
188
|
+
format_children element, paragraph, stack
|
189
|
+
end
|
190
|
+
|
191
|
+
def format_list_item element, node, stack
|
192
|
+
i = 0
|
193
|
+
unless @list_index.empty?
|
194
|
+
@list_index[-1] = @list_index[-1] + 1
|
195
|
+
i = @list_index[-1]
|
196
|
+
end
|
197
|
+
b = BreakNode.new
|
198
|
+
node.append b
|
199
|
+
if element.parent.name == 'ol'
|
200
|
+
b.append TextNode.new("#{i}. ", stack)
|
201
|
+
else
|
202
|
+
b.append TextNode.new("* ", stack)
|
203
|
+
end
|
204
|
+
format_children element, b, stack
|
205
|
+
end
|
206
|
+
end
|
207
|
+
private_constant :AnsiFormatter
|
208
|
+
|
209
|
+
def self.terminalize string, max_length
|
210
|
+
i = 0
|
211
|
+
output = ''
|
212
|
+
line_length = 0
|
213
|
+
while i < string.length
|
214
|
+
line_length += 1
|
215
|
+
char = string[i,1]
|
216
|
+
if char == "\e"
|
217
|
+
# Right now, graphics modes are the only supported ANSI sequences.
|
218
|
+
end_of_seq = string.index("m", i)
|
219
|
+
output += string[i..end_of_seq]
|
220
|
+
i = end_of_seq + 1
|
221
|
+
elsif char == " "
|
222
|
+
next_space = string.index(/[\s]/, i + 1)
|
223
|
+
if !next_space.nil? and line_length + (next_space - i) > max_length
|
224
|
+
output += "\n"
|
225
|
+
line_length = 0
|
226
|
+
else
|
227
|
+
output += char
|
228
|
+
end
|
229
|
+
i += 1
|
230
|
+
else
|
231
|
+
if char == "\n"
|
232
|
+
line_length = 0
|
233
|
+
end
|
234
|
+
output += char
|
235
|
+
i += 1
|
236
|
+
end
|
237
|
+
end
|
238
|
+
output
|
239
|
+
end
|
240
|
+
|
241
|
+
def self.size
|
242
|
+
begin
|
243
|
+
return STDOUT.winsize.reverse
|
244
|
+
rescue
|
245
|
+
return [nil,nil]
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|