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