ntxt 1.0.5 → 1.0.6
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.
- 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
|