ntxt 1.0.5 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/ntxt +8 -5
- data/lib/ntxt/block.rb +9 -1
- data/lib/ntxt/parser.rb +97 -257
- metadata +2 -2
data/bin/ntxt
CHANGED
@@ -26,13 +26,16 @@ if File.exists?(DEFAULT_CONFIG_FILE)
|
|
26
26
|
$configs.merge!(YAML::load(io))
|
27
27
|
end
|
28
28
|
else
|
29
|
+
puts("Creating configuration file #{DEFAULT_CONFIG_FILE}")
|
29
30
|
File.open(DEFAULT_CONFIG_FILE, 'w') do |io|
|
30
31
|
YAML::dump($configs, io)
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
34
35
|
OptionParser.new do |opt|
|
35
|
-
opt.version="1.0.
|
36
|
+
opt.version="1.0.6"
|
37
|
+
opt.banner = "Ussage: #{$0} [options] [ntxt file]\n\n" +
|
38
|
+
"Options:\n"
|
36
39
|
|
37
40
|
opt.on('--trace', 'Trace through the file printing summaries.' ) do |v|
|
38
41
|
$configs[:cmd] = 'trace'
|
@@ -55,10 +58,10 @@ OptionParser.new do |opt|
|
|
55
58
|
$configs[:tag_string] = v
|
56
59
|
end
|
57
60
|
|
58
|
-
opt.on('--path', 'Print the path
|
61
|
+
opt.on('--path', 'Print the path to tagged block.') do |v|
|
59
62
|
$configs[:tag_print_mode] = 'path'
|
60
63
|
end
|
61
|
-
opt.on('--parent', 'Print
|
64
|
+
opt.on('--parent', 'Print parent block of a tagged block.') do |v|
|
62
65
|
$configs[:tag_print_mode] = 'parent'
|
63
66
|
end
|
64
67
|
opt.on('--leaf', 'Print only the tagged block.') do |v|
|
@@ -154,8 +157,7 @@ module TagPrintModes
|
|
154
157
|
end
|
155
158
|
end
|
156
159
|
|
157
|
-
|
158
|
-
|
160
|
+
# Attempt to open the n.txt file or fail with clean error.
|
159
161
|
begin
|
160
162
|
ntxt = File.open($configs[:filename]) { |io| Ntxt::Ntxt.new(io.read) }
|
161
163
|
rescue Errno::ENOENT => e
|
@@ -163,6 +165,7 @@ rescue Errno::ENOENT => e
|
|
163
165
|
exit 1
|
164
166
|
end
|
165
167
|
|
168
|
+
# Execute the application command.
|
166
169
|
case $configs[:cmd]
|
167
170
|
when 'print_tags'
|
168
171
|
# Notice that we are re-wrapping the tags back into square brackets.
|
data/lib/ntxt/block.rb
CHANGED
@@ -29,6 +29,12 @@ module Ntxt
|
|
29
29
|
# The parent Block or nil if this is a root Block.
|
30
30
|
attr_accessor :parent
|
31
31
|
|
32
|
+
# The current header level. 0 for no header. 1-6 otherwise.
|
33
|
+
attr_accessor :header
|
34
|
+
|
35
|
+
# The current indent level. If this is a header block, ident=0.
|
36
|
+
attr_accessor :indent
|
37
|
+
|
32
38
|
# Create a new Block. Typically you will never need to do this.
|
33
39
|
# Blocks are created by Parser.
|
34
40
|
# [ntxtObj] The Ntxt object that this block belongs to.
|
@@ -43,6 +49,8 @@ module Ntxt
|
|
43
49
|
@offset = stopTxt || ntxtObj.text.length
|
44
50
|
@ntxt = ntxtObj
|
45
51
|
@parent = parentBlock
|
52
|
+
@indent = 0
|
53
|
+
@header = 0
|
46
54
|
|
47
55
|
if @parent
|
48
56
|
re = /\s*\S/m
|
@@ -84,7 +92,7 @@ module Ntxt
|
|
84
92
|
end
|
85
93
|
|
86
94
|
# Return true if the parent object is nil.
|
87
|
-
def
|
95
|
+
def root?
|
88
96
|
@parent.nil?
|
89
97
|
end
|
90
98
|
|
data/lib/ntxt/parser.rb
CHANGED
@@ -7,158 +7,73 @@ module Ntxt
|
|
7
7
|
# with the exception of Parser.parse.
|
8
8
|
class Parser
|
9
9
|
|
10
|
-
# An internal class that contains the current parse position
|
11
|
-
# and current limits on that parse, such as an artificial end-of-file
|
12
|
-
# marker to terminate a sub-parsing of a sub-block.
|
13
|
-
class State
|
14
|
-
|
15
|
-
# Array of lines. The result of Ntxt.text being split on '\n'
|
16
|
-
attr_accessor :lines
|
17
|
-
|
18
|
-
# The current Block being built up.
|
19
|
-
attr_accessor :block
|
20
|
-
|
21
|
-
# The index into #lines to start parsing at
|
22
|
-
# and before which #prevLine should return nil.
|
23
|
-
attr_accessor :lineStart
|
24
|
-
|
25
|
-
# The index into #lines at which #nextLine should return nil.
|
26
|
-
# This defaults to #lines.length
|
27
|
-
attr_accessor :lineEnd
|
28
|
-
|
29
|
-
# The current line this State points at.
|
30
|
-
attr_accessor :line
|
31
|
-
|
32
|
-
# The index into Ntxt.text that corresponds to the first
|
33
|
-
# character of the #currLine.
|
34
|
-
attr_accessor :start
|
35
|
-
|
36
|
-
# The offset from #start. The substring of Ntxt.text
|
37
|
-
# starting at #start and of length #offset will produce the
|
38
|
-
# text being considered by this State.
|
39
|
-
attr_accessor :offset
|
40
|
-
|
41
|
-
def initialize(lines, block, lineStart, start, lineEnd)
|
42
|
-
@lines = lines # The array of lines to parse.
|
43
|
-
@block = block # The block this state is operating on.
|
44
|
-
@lineStart = lineStart # The starting line in this state.
|
45
|
-
@line = lineStart # The current line this state points at.
|
46
|
-
@lineEnd = lineEnd # The last line. @lineEnd <= @lines.length.
|
47
|
-
@start = start # Start index in text.
|
48
|
-
@offset = 0 # Offset from @start.
|
49
|
-
end
|
50
|
-
|
51
|
-
# Return the current line. If #prevLine or #nextLine has
|
52
|
-
# walked outside of the #lineStart or #lineEnd limits this will
|
53
|
-
# return nil.
|
54
|
-
def currLine
|
55
|
-
if @line < @lineEnd && @line >= @lineStart
|
56
|
-
@lines[@line]
|
57
|
-
else
|
58
|
-
nil
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
# Return the next line (#line + 1) unless we step beyond #lineEnd.
|
63
|
-
# If we exceed #lineEnd, nil is returned.
|
64
|
-
# Notice that this also updates #offset.
|
65
|
-
def nextLine
|
66
|
-
|
67
|
-
# If we are already past the end, return nil, do nothing.
|
68
|
-
if @line >= @lineEnd
|
69
|
-
nil
|
70
|
-
|
71
|
-
# Otherwise we are updating some state.
|
72
|
-
else
|
73
|
-
@offset = @offset + @lines[@line].length + 1
|
74
|
-
@line = @line + 1
|
75
|
-
|
76
|
-
# Recheck if we are inside the array and return nil if we are not.
|
77
|
-
(@line < @lineEnd) ? @lines[@line] : nil
|
78
|
-
end
|
79
|
-
end # nextLine
|
80
|
-
|
81
|
-
# Return the previous line (#line - 1) unless we step before #lineStart.
|
82
|
-
# If we exceed #lineStart, nil is returned.
|
83
|
-
# Notice that this also updates #offset.
|
84
|
-
def prevLine
|
85
|
-
if @line < @lineStart
|
86
|
-
nil
|
87
|
-
else
|
88
|
-
nLine = @line - 1
|
89
|
-
@offset = @offset - @lines[nLine].length - 1
|
90
|
-
@line = nLine
|
91
|
-
@lines[nLine]
|
92
|
-
end
|
93
|
-
end # prevLine
|
94
|
-
|
95
|
-
# Shift the state starting points to the current position of
|
96
|
-
# what has been read in this state, effecitvely consuming that input.
|
97
|
-
#
|
98
|
-
# The #start field is moved to #start+offset. The #offset is set to 0.
|
99
|
-
# Finally the #lineStart is set to #line.
|
100
|
-
def consume
|
101
|
-
@start = @start + @offset
|
102
|
-
@offset = 0
|
103
|
-
@lineStart = @line
|
104
|
-
end
|
105
|
-
|
106
|
-
# Print as a string.
|
107
|
-
def to_s
|
108
|
-
"lineStart: %s, lineEnd: %s, line: %s, start: %s, offset: %s"%[
|
109
|
-
@lineStart, @lineEnd, @line, @start, @offset
|
110
|
-
]
|
111
|
-
end
|
112
|
-
|
113
|
-
# Seek this state's position forward to the given state's position.
|
114
|
-
# If a state with a position behind the current state's position is
|
115
|
-
# passed in as an argument the behavior is undefined.
|
116
|
-
def seek(state)
|
117
|
-
@line = state.line
|
118
|
-
@offset = (state.start + state.offset - @start).abs()
|
119
|
-
end # seek
|
120
|
-
|
121
|
-
end # Parser::State
|
122
|
-
|
123
|
-
|
124
|
-
|
125
10
|
# Return an array in which the first element is the indent length and
|
126
11
|
# the second element is the contained text. Nil otherwise.
|
127
|
-
def
|
12
|
+
def hlevel()
|
13
|
+
line = @lines[@currentLine]
|
14
|
+
|
15
|
+
nextLine = if @lines.length > @currentLine + 1
|
16
|
+
@lines[@currentLine+1]
|
17
|
+
else
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
128
21
|
case line
|
129
22
|
when /^\s*=([^=].*)=\s*$/
|
130
|
-
[ 1, $~[1] ]
|
23
|
+
[ 1, $~[1], 1 ]
|
131
24
|
when /^\s*==([^=].*)==\s*$/
|
132
|
-
[ 2, $~[1] ]
|
25
|
+
[ 2, $~[1], 1 ]
|
133
26
|
when /^\s*===([^=].*)===\s*$/
|
134
|
-
[ 3, $~[1] ]
|
27
|
+
[ 3, $~[1], 1 ]
|
135
28
|
when /^\s*====([^=].*)====\s*$/
|
136
|
-
[ 4, $~[1] ]
|
29
|
+
[ 4, $~[1], 1 ]
|
137
30
|
when /^\s*=====([^=].*)=====\s*$/
|
138
|
-
[ 5, $~[1] ]
|
31
|
+
[ 5, $~[1], 1 ]
|
139
32
|
when /^\s*======([^=].*)======\s*$/
|
140
|
-
[ 6, $~[1] ]
|
33
|
+
[ 6, $~[1], 1 ]
|
34
|
+
when /^\s*#\s*(.*)$/
|
35
|
+
[ 1, $~[1], 1 ]
|
36
|
+
when /^\s*##\s*(.*)$/
|
37
|
+
[ 2, $~[1], 1 ]
|
38
|
+
when /^\s*###\s*(.*)$/
|
39
|
+
[ 3, $~[1], 1 ]
|
40
|
+
when /^\s*####\s*(.*)$/
|
41
|
+
[ 4, $~[1], 1 ]
|
42
|
+
when /^\s*#####\s*(.*)$/
|
43
|
+
[ 5, $~[1], 1 ]
|
44
|
+
when /^\s*######\s*(.*)$/
|
45
|
+
[ 6, $~[1], 1 ]
|
141
46
|
else
|
142
|
-
|
47
|
+
if nextLine && nextLine =~ /\s*==+\s*$/
|
48
|
+
[ 1, line, 2 ]
|
49
|
+
elsif nextLine && nextLine =~ /\s*--+\s*$/
|
50
|
+
[ 2, line, 2 ]
|
51
|
+
else
|
52
|
+
nil
|
53
|
+
end
|
143
54
|
end
|
144
|
-
end #
|
145
|
-
|
55
|
+
end # hlevel
|
56
|
+
|
57
|
+
def computeIndent
|
58
|
+
/^(\s*).*$/.match(@lines[@currentLine])[1].length
|
59
|
+
end
|
60
|
+
|
61
|
+
def closeBlock
|
62
|
+
@currentBlock.offset = @currentOffset - @currentBlock.start - 1
|
63
|
+
@currentBlock = @currentBlock.parent
|
64
|
+
end
|
65
|
+
|
146
66
|
# Extract all the tags from the given line.
|
147
|
-
# [block] The block to which all tags will be added with Block#addTag.
|
148
|
-
# All parent blocks recieve copies of the child block's tag.
|
149
|
-
# [line] The line to extract all tags from. Tags are
|
150
|
-
# square-bracket-enclosed strings found in sequence at the
|
151
|
-
# beginning of a line. If the sequence is broken, extraction stops.
|
152
67
|
# Some tag examples:
|
153
68
|
# [a tag] [another tag]
|
154
|
-
# [a tag] [another tag] Not a tag. [
|
69
|
+
# [a tag] [another tag] Not a tag. [a tag]
|
155
70
|
# No tag on this line.
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
line = line
|
71
|
+
def extractTags()
|
72
|
+
line = @lines[@currentLine]
|
73
|
+
re = /\[([^\[]+)\]/m
|
74
|
+
while line =~ re
|
75
|
+
@currentBlock.addTag($~[1])
|
76
|
+
line = line.sub(re, '')
|
162
77
|
end
|
163
78
|
end # self.extractTags
|
164
79
|
|
@@ -171,141 +86,66 @@ module Ntxt
|
|
171
86
|
# If ntxtObj isn't an Ntxt, create it as one.
|
172
87
|
( ntxtObj = Ntxt.new(ntxtObj) ) unless ntxtObj.is_a?( Ntxt )
|
173
88
|
|
174
|
-
lines = ntxtObj.text.split("\n")
|
175
|
-
|
176
89
|
rootBlock = Block.new(ntxtObj)
|
177
|
-
|
178
|
-
@
|
90
|
+
|
91
|
+
@lines = ntxtObj.text.split("\n")
|
92
|
+
@currentLine = 0
|
93
|
+
@currentOffset = 0
|
94
|
+
@currentIndent = 0
|
95
|
+
@currentBlock = rootBlock
|
179
96
|
|
180
97
|
parseLines()
|
181
98
|
|
182
|
-
|
183
|
-
# parse success!
|
184
|
-
rootBlock
|
185
|
-
else
|
186
|
-
# parse failure.
|
187
|
-
nil
|
188
|
-
end
|
99
|
+
rootBlock
|
189
100
|
end # parse(ntxtObj)
|
190
101
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
# == Header 2 ==
|
196
|
-
# [level] an integer from 1 to 6.
|
197
|
-
# [title] a string of the text found between the equal signs.
|
198
|
-
def parseHlevel(level, title)
|
199
|
-
state = @stack[-1]
|
200
|
-
|
201
|
-
# If in parseHlevel, don't get the current line. That is contained
|
202
|
-
# in the title argument. Instead, get the next line and proceed.
|
203
|
-
line = state.nextLine
|
102
|
+
def nextLine
|
103
|
+
@currentOffset += @lines[@currentLine].length + 1
|
104
|
+
@currentLine += 1
|
105
|
+
end
|
204
106
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
break if hl && hl[0] <= level
|
210
|
-
|
211
|
-
line = state.nextLine
|
212
|
-
end
|
213
|
-
|
214
|
-
block = Block.new(
|
215
|
-
state.block.ntxt,
|
216
|
-
state.block,
|
217
|
-
state.start,
|
218
|
-
state.offset)
|
107
|
+
def parseLines
|
108
|
+
while @currentLine < @lines.length do
|
109
|
+
extractTags
|
219
110
|
|
220
|
-
|
221
|
-
state.lines,
|
222
|
-
block,
|
223
|
-
state.lineStart+1,
|
224
|
-
state.start + state.lines[state.lineStart].length + 1,
|
225
|
-
state.line)
|
226
|
-
|
227
|
-
@stack.push subState
|
228
|
-
parseLines
|
229
|
-
@stack.pop
|
230
|
-
state.consume
|
231
|
-
end # parseHlevel(leve, title)
|
232
|
-
|
233
|
-
# Parse blocks of text that are indented at the given level or greater.
|
234
|
-
# [indentLevel] an integer denoteing the number of characters this line is
|
235
|
-
# indented at.
|
236
|
-
# [text] the content of the line that was indented.
|
237
|
-
def parseIndent(indentLevel, text)
|
238
|
-
state = @stack[-1]
|
239
|
-
line = state.currLine
|
111
|
+
hlvl = hlevel
|
240
112
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
state.offset)
|
113
|
+
if hlvl
|
114
|
+
while @currentBlock.header >= hlvl[0] do
|
115
|
+
closeBlock
|
116
|
+
end
|
246
117
|
|
247
|
-
|
248
|
-
|
249
|
-
|
118
|
+
@currentBlock = Block.new(
|
119
|
+
@currentBlock.ntxt,
|
120
|
+
@currentBlock,
|
121
|
+
@currentOffset)
|
122
|
+
@currentBlock.header = hlvl[0]
|
250
123
|
|
251
|
-
|
252
|
-
|
124
|
+
hlvl[2].times { nextLine }
|
125
|
+
@currentIndent = 0
|
253
126
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
break if nextIndentLevel < indentLevel
|
127
|
+
next
|
128
|
+
end
|
258
129
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
130
|
+
indent = computeIndent
|
131
|
+
if indent < @currentIndent
|
132
|
+
closeBlock
|
133
|
+
@currentIndent = indent
|
134
|
+
elsif indent > @currentIndent
|
135
|
+
@currentBlock = Block.new(
|
136
|
+
@currentBlock.ntxt,
|
137
|
+
@currentBlock,
|
138
|
+
@currentOffset)
|
139
|
+
@currentBlock.indent = indent
|
140
|
+
@currentIndent = indent
|
141
|
+
end
|
267
142
|
|
268
|
-
|
269
|
-
|
270
|
-
@stack.pop
|
271
|
-
state.seek subState
|
272
|
-
line = state.currLine
|
273
|
-
else
|
274
|
-
Parser::extractTags(block, line)
|
275
|
-
line = state.nextLine
|
276
|
-
end # if nextIndentLevel > indentLevel
|
277
|
-
|
278
|
-
end # while line
|
279
|
-
block.offset = state.offset
|
280
|
-
state.consume
|
281
|
-
end # parseIndent(indentLevel, text)
|
282
|
-
|
283
|
-
# This is the root of the parser's call tree after #parse sets up
|
284
|
-
# the parse. This plucks the State off the Parser.stack, obtains the
|
285
|
-
# State.currLine.
|
286
|
-
#
|
287
|
-
# When an indented line is found, #parseIndent is called.
|
288
|
-
# When a header line is found, #parseHlevel is caled.
|
289
|
-
# Otherwise, we move to the next line.
|
290
|
-
def parseLines
|
291
|
-
state = @stack[-1]
|
292
|
-
line = state.currLine
|
293
|
-
|
294
|
-
while line
|
295
|
-
tmp = Parser::hlevel(line)
|
143
|
+
nextLine
|
144
|
+
end
|
296
145
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
line = state.currLine
|
301
|
-
elsif line =~ /^(\s*)(\S.*)$/
|
302
|
-
state.consume
|
303
|
-
parseIndent($~[1].length, $~[2])
|
304
|
-
line = state.currLine
|
305
|
-
else
|
306
|
-
line = state.nextLine
|
307
|
-
end # if tmp
|
308
|
-
end # while line
|
146
|
+
while not @currentBlock.root?
|
147
|
+
closeBlock
|
148
|
+
end
|
309
149
|
end # parseLines
|
310
150
|
end
|
311
151
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ntxt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
12
|
+
date: 2013-02-28 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: ! 'A library and command line tool for parsing plain text into blocks
|
15
15
|
by
|