gamefic 1.7.0 → 2.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 +5 -5
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.rubocop.yml +13 -0
- data/.solargraph.yml +5 -0
- data/Gemfile +7 -0
- data/LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +10 -0
- data/gamefic.gemspec +27 -0
- data/lib/gamefic.rb +7 -6
- data/lib/gamefic/action.rb +38 -28
- data/lib/gamefic/active.rb +325 -280
- data/lib/gamefic/actor.rb +8 -5
- data/lib/gamefic/command.rb +9 -7
- data/lib/gamefic/core_ext/array.rb +24 -49
- data/lib/gamefic/core_ext/string.rb +25 -16
- data/lib/gamefic/describable.rb +21 -23
- data/lib/gamefic/element.rb +43 -31
- data/lib/gamefic/entity.rb +6 -12
- data/lib/gamefic/index.rb +121 -0
- data/lib/gamefic/{matchable.rb → keywords.rb} +52 -50
- data/lib/gamefic/messaging.rb +43 -44
- data/lib/gamefic/node.rb +14 -5
- data/lib/gamefic/plot.rb +69 -89
- data/lib/gamefic/plot/darkroom.rb +92 -264
- data/lib/gamefic/plot/host.rb +42 -48
- data/lib/gamefic/plot/snapshot.rb +5 -18
- data/lib/gamefic/query.rb +14 -18
- data/lib/gamefic/query/base.rb +30 -18
- data/lib/gamefic/query/children.rb +0 -0
- data/lib/gamefic/query/external.rb +18 -14
- data/lib/gamefic/query/family.rb +1 -7
- data/lib/gamefic/query/matches.rb +75 -67
- data/lib/gamefic/query/parent.rb +0 -0
- data/lib/gamefic/query/siblings.rb +0 -0
- data/lib/gamefic/query/text.rb +2 -1
- data/lib/gamefic/scene.rb +0 -2
- data/lib/gamefic/scene/activity.rb +24 -26
- data/lib/gamefic/scene/base.rb +64 -8
- data/lib/gamefic/scene/conclusion.rb +0 -2
- data/lib/gamefic/scene/custom.rb +0 -2
- data/lib/gamefic/scene/multiple_choice.rb +18 -3
- data/lib/gamefic/scene/multiple_scene.rb +29 -20
- data/lib/gamefic/scene/pause.rb +7 -2
- data/lib/gamefic/scene/yes_or_no.rb +21 -9
- data/lib/gamefic/scriptable.rb +87 -0
- data/lib/gamefic/serialize.rb +68 -0
- data/lib/gamefic/subplot.rb +29 -35
- data/lib/gamefic/syntax.rb +14 -13
- data/lib/gamefic/version.rb +3 -3
- data/lib/gamefic/world.rb +16 -0
- data/lib/gamefic/world/callbacks.rb +135 -0
- data/lib/gamefic/world/commands.rb +184 -0
- data/lib/gamefic/{plot → world}/entities.rb +30 -29
- data/lib/gamefic/{plot → world}/playbook.rb +255 -240
- data/lib/gamefic/world/players.rb +21 -0
- data/lib/gamefic/world/scenes.rb +226 -0
- metadata +41 -92
- data/bin/gamefic +0 -9
- data/lib/gamefic/engine.rb +0 -7
- data/lib/gamefic/engine/base.rb +0 -59
- data/lib/gamefic/engine/tty.rb +0 -24
- data/lib/gamefic/grammar.rb +0 -13
- data/lib/gamefic/grammar/conjugator.rb +0 -20
- data/lib/gamefic/grammar/gender.rb +0 -11
- data/lib/gamefic/grammar/person.rb +0 -10
- data/lib/gamefic/grammar/plural.rb +0 -13
- data/lib/gamefic/grammar/pronouns.rb +0 -106
- data/lib/gamefic/grammar/tense.rb +0 -6
- data/lib/gamefic/grammar/verb_set.rb +0 -43
- data/lib/gamefic/grammar/verbs.rb +0 -26
- data/lib/gamefic/grammar/word_adapter.rb +0 -49
- data/lib/gamefic/plot/articles.rb +0 -22
- data/lib/gamefic/plot/callbacks.rb +0 -126
- data/lib/gamefic/plot/commands.rb +0 -120
- data/lib/gamefic/plot/players.rb +0 -15
- data/lib/gamefic/plot/scenes.rb +0 -187
- data/lib/gamefic/plot/theater.rb +0 -73
- data/lib/gamefic/plot/you_mount.rb +0 -22
- data/lib/gamefic/script.rb +0 -13
- data/lib/gamefic/script/base.rb +0 -42
- data/lib/gamefic/script/file.rb +0 -14
- data/lib/gamefic/script/text.rb +0 -14
- data/lib/gamefic/shell.rb +0 -76
- data/lib/gamefic/source.rb +0 -14
- data/lib/gamefic/source/base.rb +0 -12
- data/lib/gamefic/source/file.rb +0 -23
- data/lib/gamefic/source/text.rb +0 -16
- data/lib/gamefic/tester.rb +0 -19
- data/lib/gamefic/text.rb +0 -8
- data/lib/gamefic/text/ansi.rb +0 -53
- data/lib/gamefic/text/html.rb +0 -68
- data/lib/gamefic/text/html/conversions.rb +0 -250
- data/lib/gamefic/text/html/entities.rb +0 -9
- data/lib/gamefic/tty.rb +0 -10
- data/lib/gamefic/user.rb +0 -7
- data/lib/gamefic/user/base.rb +0 -29
- data/lib/gamefic/user/tty.rb +0 -38
data/lib/gamefic/script/file.rb
DELETED
data/lib/gamefic/script/text.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
module Gamefic
|
2
|
-
|
3
|
-
class Script::Text < Script::Base
|
4
|
-
attr_reader :path, :absolute_path
|
5
|
-
def initialize path, code, absolute_path = nil
|
6
|
-
@path = path
|
7
|
-
@code = code
|
8
|
-
@absolute_path = absolute_path || path
|
9
|
-
end
|
10
|
-
def read
|
11
|
-
@code
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
data/lib/gamefic/shell.rb
DELETED
@@ -1,76 +0,0 @@
|
|
1
|
-
require 'thor'
|
2
|
-
require 'gamefic/engine/tty'
|
3
|
-
require 'zip'
|
4
|
-
require 'tmpdir'
|
5
|
-
require 'yaml'
|
6
|
-
|
7
|
-
module Gamefic
|
8
|
-
class Shell < Thor
|
9
|
-
map %w[--version -v] => :version
|
10
|
-
|
11
|
-
desc "--version, -v", "Print the version"
|
12
|
-
def version
|
13
|
-
puts "gamefic #{Gamefic::VERSION}"
|
14
|
-
end
|
15
|
-
|
16
|
-
desc 'play FILE_NAME', 'Execute a compiled (.gfic) game'
|
17
|
-
option :verbose, type: :boolean, aliases: :v, desc: "Don't suppress Ruby exceptions"
|
18
|
-
def play(file)
|
19
|
-
Dir.mktmpdir 'gamefic_' do |dir|
|
20
|
-
puts 'Loading...'
|
21
|
-
decompress file, dir
|
22
|
-
run_game(dir)
|
23
|
-
end
|
24
|
-
rescue Zip::Error => e
|
25
|
-
puts "'#{file}' does not appear to be a valid Gamefic file."
|
26
|
-
show_exception(e) if options[:verbose]
|
27
|
-
rescue StandardError => e
|
28
|
-
puts "An error occurred: #{e.message}"
|
29
|
-
show_exception(e) if options[:verbose]
|
30
|
-
end
|
31
|
-
|
32
|
-
desc 'info FILE_NAME', 'Print information about a (.gfic) game'
|
33
|
-
option :verbose, type: :boolean, aliases: :v, desc: "Don't suppress Ruby exceptions"
|
34
|
-
def info(file)
|
35
|
-
Dir.mktmpdir 'gamefic_' do |dir|
|
36
|
-
decompress file, dir
|
37
|
-
metadata = YAML.load_file File.join(dir, 'metadata.yaml')
|
38
|
-
metadata.each { |k, v|
|
39
|
-
puts "#{k}: #{v}"
|
40
|
-
}
|
41
|
-
end
|
42
|
-
rescue StandardError, Zip::Error => e
|
43
|
-
puts "'#{file}' does not appear to be a valid Gamefic file."
|
44
|
-
show_exception(e) if options[:verbose]
|
45
|
-
end
|
46
|
-
|
47
|
-
# Custom error message for invalid command or filename
|
48
|
-
def method_missing(symbol, *args)
|
49
|
-
raise UndefinedCommandError, "Could not find command or file named \"#{symbol}\"."
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
def show_exception(exception)
|
55
|
-
puts exception.inspect
|
56
|
-
puts exception.backtrace.join("\n")
|
57
|
-
end
|
58
|
-
|
59
|
-
def decompress(zipfile, destination)
|
60
|
-
Zip::File.open(zipfile) do |z|
|
61
|
-
z.each do |entry|
|
62
|
-
FileUtils.mkdir_p File.join(destination, File.dirname(entry.name))
|
63
|
-
full_path = File.join(destination, entry.name)
|
64
|
-
entry.extract full_path unless File.exist?(full_path)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def run_game(directory)
|
70
|
-
plot = Plot.new(Source::File.new(File.join(directory, 'scripts')))
|
71
|
-
plot.script 'main'
|
72
|
-
plot.metadata = YAML.load_file File.join(directory, 'metadata.yaml')
|
73
|
-
Engine::Tty.start(plot)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
data/lib/gamefic/source.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
module Gamefic
|
2
|
-
|
3
|
-
# Plots use Source classes to fetch scripts to be executed. The most common
|
4
|
-
# type of Source class is Source::File, which searches for scripts in
|
5
|
-
# a predefined list of directories on the filesystem, similar to the way
|
6
|
-
# that Kernel#require works.
|
7
|
-
#
|
8
|
-
module Source
|
9
|
-
autoload :Base, 'gamefic/source/base'
|
10
|
-
autoload :File, 'gamefic/source/file'
|
11
|
-
autoload :Text, 'gamefic/source/text'
|
12
|
-
end
|
13
|
-
|
14
|
-
end
|
data/lib/gamefic/source/base.rb
DELETED
data/lib/gamefic/source/file.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
module Gamefic
|
2
|
-
|
3
|
-
class Source::File < Source::Base
|
4
|
-
@@extensions = ['.plot.rb', '.plot', '.rb']
|
5
|
-
attr_reader :directories
|
6
|
-
attr_accessor :main_dir
|
7
|
-
def initialize(*directories)
|
8
|
-
@directories = directories || []
|
9
|
-
end
|
10
|
-
def export path
|
11
|
-
@directories.each { |directory|
|
12
|
-
@@extensions.each { |ext|
|
13
|
-
abs_file = File.join(directory, path + ext)
|
14
|
-
if File.file?(abs_file)
|
15
|
-
return Script::File.new(abs_file, path)
|
16
|
-
end
|
17
|
-
}
|
18
|
-
}
|
19
|
-
raise LoadError.new("cannot load script -- #{path}")
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
end
|
data/lib/gamefic/source/text.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
module Gamefic
|
2
|
-
|
3
|
-
class Source::Text < Source::Base
|
4
|
-
def initialize scripts = {}
|
5
|
-
@scripts = scripts
|
6
|
-
end
|
7
|
-
def export path
|
8
|
-
if @scripts.has_key?(path)
|
9
|
-
Script::Text.new(path, @scripts[path])
|
10
|
-
else
|
11
|
-
raise "Script #{path} not found"
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
end
|
data/lib/gamefic/tester.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
module Gamefic
|
2
|
-
|
3
|
-
module Tester
|
4
|
-
def test_procs
|
5
|
-
@test_procs ||= Hash.new
|
6
|
-
end
|
7
|
-
def on_test name = :me, &block
|
8
|
-
test_procs[name] = block
|
9
|
-
end
|
10
|
-
def run_test name, actor
|
11
|
-
queue = []
|
12
|
-
stage actor, queue, &test_procs[name]
|
13
|
-
actor.queue.push *queue
|
14
|
-
actor[:test_queue_length] = queue.length
|
15
|
-
actor[:test_queue_scene] = actor.scene
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|
data/lib/gamefic/text.rb
DELETED
data/lib/gamefic/text/ansi.rb
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
module Gamefic
|
2
|
-
|
3
|
-
# Constants for ANSI codes, plus ExtraCodes for custom formatting.
|
4
|
-
module Text::Ansi
|
5
|
-
module Code
|
6
|
-
module Attribute
|
7
|
-
NORMAL = 0
|
8
|
-
BOLD = 1
|
9
|
-
UNDERSCORE = 4
|
10
|
-
BLINK = 5
|
11
|
-
REVERSE = 7
|
12
|
-
CONCEALED = 8
|
13
|
-
end
|
14
|
-
module Foreground
|
15
|
-
BLACK = 30
|
16
|
-
RED = 31
|
17
|
-
GREEN = 32
|
18
|
-
YELLOW = 33
|
19
|
-
BLUE = 34
|
20
|
-
MAGENTA = 35
|
21
|
-
CYAN = 36
|
22
|
-
WHITE = 37
|
23
|
-
end
|
24
|
-
module Background
|
25
|
-
BLACK = 40
|
26
|
-
RED = 41
|
27
|
-
GREEN = 42
|
28
|
-
YELLOW = 43
|
29
|
-
BLUE = 44
|
30
|
-
MAGENTA = 45
|
31
|
-
CYAN = 46
|
32
|
-
WHITE = 47
|
33
|
-
end
|
34
|
-
module Extra
|
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
|
44
|
-
end
|
45
|
-
end
|
46
|
-
def self.graphics_mode(*settings)
|
47
|
-
ansi = settings.flatten.that_are(Fixnum)
|
48
|
-
return '' if ansi.length == 0
|
49
|
-
"\e[#{ansi.join(';')}m"
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
end
|
data/lib/gamefic/text/html.rb
DELETED
@@ -1,68 +0,0 @@
|
|
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
|
@@ -1,250 +0,0 @@
|
|
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
|