1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
##########################################################################
#
# We store the lines we're working on as objects of class Line.
# These contain the text of the line, along with a flag indicating the
# line type, and an indentation level
module SM
class Line
INFINITY = 9999
BLANK = :BLANK
HEADING = :HEADING
LIST = :LIST
RULE = :RULE
PARAGRAPH = :PARAGRAPH
VERBATIM = :VERBATIM
# line type
attr_accessor :type
# The indentation nesting level
attr_accessor :level
# The contents
attr_accessor :text
# A prefix or parameter. For LIST lines, this is
# the text that introduced the list item (the label)
attr_accessor :param
# A flag. For list lines, this is the type of the list
attr_accessor :flag
# the number of leading spaces
attr_accessor :leading_spaces
# true if this line has been deleted from the list of lines
attr_accessor :deleted
def initialize(text)
@text = text.dup
@deleted = false
# expand tabs
1 while @text.gsub!(/\t+/) { ' ' * (8*$&.length - $`.length % 8)} && $~ #`
# Strip trailing whitespace
@text.sub!(/\s+$/, '')
# and look for leading whitespace
if @text.length > 0
@text =~ /^(\s*)/
@leading_spaces = $1.length
else
@leading_spaces = INFINITY
end
end
# Return true if this line is blank
def isBlank?
@text.length.zero?
end
# stamp a line with a type, a level, a prefix, and a flag
def stamp(type, level, param="", flag=nil)
@type, @level, @param, @flag = type, level, param, flag
end
##
# Strip off the leading margin
#
def strip_leading(size)
if @text.size > size
@text[0,size] = ""
else
@text = ""
end
end
def to_s
"#@type#@level: #@text"
end
end
###############################################################################
#
# A container for all the lines
#
class Lines
include Enumerable
attr_reader :lines # for debugging
def initialize(lines)
@lines = lines
rewind
end
def empty?
@lines.size.zero?
end
def each
@lines.each do |line|
yield line unless line.deleted
end
end
# def [](index)
# @lines[index]
# end
def rewind
@nextline = 0
end
def next
begin
res = @lines[@nextline]
@nextline += 1 if @nextline < @lines.size
end while res and res.deleted and @nextline < @lines.size
res
end
def unget
@nextline -= 1
end
def delete(a_line)
a_line.deleted = true
end
def normalize
margin = @lines.collect{|l| l.leading_spaces}.min
margin = 0 if margin == Line::INFINITY
@lines.each {|line| line.strip_leading(margin) } if margin > 0
end
def as_text
@lines.map {|l| l.text}.join("\n")
end
def line_types
@lines.map {|l| l.type }
end
end
end
|