ntxt 1.0.0 → 1.0.1
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/README.rdoc +63 -0
- data/bin/ntxt +19 -6
- data/lib/ntxt.rb +5 -0
- data/lib/ntxt/block.rb +85 -18
- data/lib/ntxt/ntxt.rb +12 -11
- data/lib/ntxt/parser.rb +157 -80
- metadata +3 -3
data/README.rdoc
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
|
|
2
|
+
= About
|
|
3
|
+
|
|
4
|
+
Ntxt is a simple text format that defines hierarchical blocks of text
|
|
5
|
+
and tags on those blocks. The goal is to give the author an easy way
|
|
6
|
+
to search their text in a slightly more structured way than +grep+ 'ing.
|
|
7
|
+
|
|
8
|
+
Any tags found in a block are extracted and the block is _tagged_ with them.
|
|
9
|
+
All parent blocks also receive the tags of their child blocks. Thus, the
|
|
10
|
+
root block is tagged with all tags occuring in the document.
|
|
11
|
+
|
|
12
|
+
== Format Rules
|
|
13
|
+
=== Headers
|
|
14
|
+
|
|
15
|
+
Header lines look like something you would see out of a wiki.
|
|
16
|
+
|
|
17
|
+
= Header 1 =
|
|
18
|
+
== Header 2 ==
|
|
19
|
+
= Another Header 1 =
|
|
20
|
+
|
|
21
|
+
Header 1 is the largest and highest header. All text after it, aside from
|
|
22
|
+
another 1-header, will be considered a sub-block. In the example
|
|
23
|
+
"Header 1" and "Another Header 1" are the two top-level blocks. "Header 2" is
|
|
24
|
+
a child of "Header 1".
|
|
25
|
+
|
|
26
|
+
=== Indentation
|
|
27
|
+
|
|
28
|
+
Indentation also forms blocks.
|
|
29
|
+
|
|
30
|
+
= Header 1 =
|
|
31
|
+
== Header 2 ==
|
|
32
|
+
Sub block of header 2.
|
|
33
|
+
Sub block of the preceding line.
|
|
34
|
+
Another block below header 2.
|
|
35
|
+
= Another Header 1 =
|
|
36
|
+
|
|
37
|
+
In the above example Header 2 has 1 sub-block because there is an indentation
|
|
38
|
+
of 2-spaces with an intermediate indentation of 4-spaces. That 4-space line
|
|
39
|
+
is bundled into a subblock to the 2-space indented block of text.
|
|
40
|
+
|
|
41
|
+
The only ways to break out of this 2-indent text is to:
|
|
42
|
+
|
|
43
|
+
1. Put in an empty line.
|
|
44
|
+
2. Put in a header.
|
|
45
|
+
3. Indent more shallowly, such as a 1-space line.
|
|
46
|
+
|
|
47
|
+
=== Tags
|
|
48
|
+
|
|
49
|
+
Lines beginning with [tag1] [tag2] are considered to have tags
|
|
50
|
+
+tag1+ and +tag2+. For example:
|
|
51
|
+
|
|
52
|
+
Block1
|
|
53
|
+
[block 1 tag] [example]
|
|
54
|
+
|
|
55
|
+
[this is tag] Block2 [not a tag]
|
|
56
|
+
[block 2 tag] [example]
|
|
57
|
+
|
|
58
|
+
Notice, tag names may have spaces. Both blocks are tagged with +example+.
|
|
59
|
+
And finally, <code>not a tag</code> is, well, not a tag. It does not start a line.
|
|
60
|
+
|
|
61
|
+
Also note that you can't tag header blocks directly because the header line
|
|
62
|
+
must start with = and tag lines must begin with [. Blocks inherit all their
|
|
63
|
+
child blocks' tags, though, so finding header blocks by tags is still possible.
|
data/bin/ntxt
CHANGED
|
@@ -27,7 +27,13 @@ OptionParser.new do |opt|
|
|
|
27
27
|
$configs[:cmd] = 'tag'
|
|
28
28
|
$configs[:tag_string] = v
|
|
29
29
|
end
|
|
30
|
-
|
|
30
|
+
|
|
31
|
+
opt.on('-l', '--last=[Integer]',
|
|
32
|
+
'Show the last n top-level blocks. Default is 1') do |v|
|
|
33
|
+
$configs[:cmd] = 'last'
|
|
34
|
+
$configs[:last] = (v.nil?)? 1 : v.to_i
|
|
35
|
+
end
|
|
36
|
+
|
|
31
37
|
opt.on('-s','--search=String', 'Search the text for.' ) do |v|
|
|
32
38
|
$configs[:cmd] = 'search'
|
|
33
39
|
$configs[:search_string] = v
|
|
@@ -41,9 +47,10 @@ else
|
|
|
41
47
|
exit 1
|
|
42
48
|
end
|
|
43
49
|
|
|
50
|
+
# Remove empty lines from blocks and print. Guards against nils and empty lines.
|
|
44
51
|
def printNonEmpty(txt)
|
|
45
52
|
if txt
|
|
46
|
-
txt = txt.
|
|
53
|
+
txt = txt.gsub(/^\s*$/m, '')
|
|
47
54
|
print txt, "\n" if txt.length > 0
|
|
48
55
|
end
|
|
49
56
|
end
|
|
@@ -55,11 +62,11 @@ ntxt = File.open($configs[:filename]) { |io| Ntxt::Ntxt.new(io.read) }
|
|
|
55
62
|
case $configs[:cmd]
|
|
56
63
|
when 'print_tags'
|
|
57
64
|
# Notice that we are re-wrapping the tags back into square brackets.
|
|
58
|
-
puts "[#{ntxt.rootBlock.tags.sort.join('] [')}]"
|
|
65
|
+
puts "[#{ntxt.rootBlock.tags.keys.sort.join('] [')}]"
|
|
59
66
|
when 'tag'
|
|
60
67
|
ntxt.walkText(
|
|
61
68
|
lambda { |txt, depth, block|
|
|
62
|
-
printNonEmpty txt if block.tags.join(', ').index( $configs[:tag_string])},
|
|
69
|
+
printNonEmpty txt if block.tags.keys.sort.join(', ').index( $configs[:tag_string])},
|
|
63
70
|
lambda { |depth, block| },
|
|
64
71
|
lambda { |depth, block| } )
|
|
65
72
|
when 'search'
|
|
@@ -73,11 +80,17 @@ when 'print'
|
|
|
73
80
|
lambda { |txt, depth, block| print txt },
|
|
74
81
|
lambda { |depth, block| },
|
|
75
82
|
lambda { |depth, block| } )
|
|
83
|
+
when 'last'
|
|
84
|
+
ntxt.rootBlock.children[-$configs[:last].. -1].each do |blk|
|
|
85
|
+
puts blk.text
|
|
86
|
+
end
|
|
76
87
|
when 'trace'
|
|
77
88
|
ntxt.walkText(
|
|
78
89
|
lambda { |txt, depth, block| print txt },
|
|
79
|
-
lambda { |depth, block|
|
|
80
|
-
|
|
90
|
+
lambda { |depth, block|
|
|
91
|
+
puts "-----> #{depth} #{block.tags.keys.sort.join(',')}" },
|
|
92
|
+
lambda { |depth, block|
|
|
93
|
+
puts "<----- #{depth} " } )
|
|
81
94
|
when ''
|
|
82
95
|
# nop
|
|
83
96
|
when nil
|
data/lib/ntxt.rb
CHANGED
data/lib/ntxt/block.rb
CHANGED
|
@@ -10,21 +10,35 @@ module Ntxt
|
|
|
10
10
|
# ONLY contain subblocks.
|
|
11
11
|
class Block
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
# A list of child Blcoks.
|
|
14
|
+
attr_accessor :children
|
|
15
|
+
|
|
16
|
+
# A hash of all tags of this block and its children.
|
|
17
|
+
attr_accessor :tags
|
|
18
|
+
|
|
19
|
+
# The +start+ index in the text string held in the Ntxt parent object.
|
|
20
|
+
# See the +ntxt+ field.
|
|
21
|
+
attr_accessor :start
|
|
22
|
+
|
|
23
|
+
# The offset from the +start+ field.
|
|
24
|
+
attr_accessor :offset
|
|
25
|
+
|
|
26
|
+
# The Ntxt object.
|
|
27
|
+
attr_accessor :ntxt
|
|
28
|
+
|
|
29
|
+
# The parent Block or nil if this is a root Block.
|
|
30
|
+
attr_accessor :parent
|
|
14
31
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
32
|
+
# Create a new Block. Typically you will never need to do this.
|
|
33
|
+
# Blocks are created by Parser.
|
|
34
|
+
# [ntxtObj] The Ntxt object that this block belongs to.
|
|
35
|
+
# The Ntxt object holds the text this block will reference.
|
|
36
|
+
# [parentBlock] The parent block. Nil by default.
|
|
37
|
+
# [startTxt] The staring character in Ntxt.text.
|
|
38
|
+
# [stopTxt] The initial offset. If nil this is set to ntxtObj.text.length.
|
|
25
39
|
def initialize(ntxtObj, parentBlock=nil, startTxt=0, stopTxt=0)
|
|
26
40
|
@children = []
|
|
27
|
-
@tags =
|
|
41
|
+
@tags = Hash.new(0)
|
|
28
42
|
@start = startTxt
|
|
29
43
|
@offset = stopTxt || ntxtObj.text.length
|
|
30
44
|
@ntxt = ntxtObj
|
|
@@ -45,31 +59,64 @@ module Ntxt
|
|
|
45
59
|
end
|
|
46
60
|
end
|
|
47
61
|
|
|
62
|
+
# Add a tag to this block and all ancestor blocks.
|
|
48
63
|
def addTag(tag)
|
|
49
|
-
@tags
|
|
64
|
+
@tags[tag] += 1
|
|
50
65
|
@parent.addTag(tag) if @parent
|
|
51
66
|
end
|
|
52
|
-
|
|
67
|
+
|
|
68
|
+
# Return the text slice that this block refers to.
|
|
69
|
+
# Note that parent blocks include their child blocks' text.
|
|
53
70
|
def text
|
|
54
71
|
@ntxt.text[@start, @offset]
|
|
55
72
|
end
|
|
56
73
|
|
|
74
|
+
# Return true if the parent object is nil.
|
|
57
75
|
def is_root?
|
|
58
|
-
@parent
|
|
76
|
+
@parent.nil?
|
|
59
77
|
end
|
|
60
78
|
|
|
79
|
+
# Given a block this will first call that block with this Block as
|
|
80
|
+
# the only argument. Then walk is recusively called on all child Blocks.
|
|
61
81
|
def walk(&y)
|
|
62
82
|
yield self
|
|
63
83
|
@children.each { |c| c.walk(&y) }
|
|
64
84
|
end
|
|
65
85
|
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
86
|
+
# This method handles the complexity of handing the user
|
|
87
|
+
# the text immediately handled by each block.
|
|
88
|
+
#
|
|
89
|
+
# If you call Block.text you will get a contiguous block of text
|
|
90
|
+
# that covers this Block and all its children. Essentially the +start+
|
|
91
|
+
# to the +offset+ substring of Ntxt.text.
|
|
92
|
+
#
|
|
93
|
+
# What this method does is pass each text that belongs only to
|
|
94
|
+
# the particular Block in question and the children. The text
|
|
95
|
+
# is passed to the user in order, so concatinating it would
|
|
96
|
+
# result in equivalent output to Block.text.
|
|
97
|
+
#
|
|
98
|
+
# This method is useful for visualizing the text structure
|
|
99
|
+
# or filtering out blocks that aren't interesting to the user.
|
|
100
|
+
#
|
|
101
|
+
# [printFunc] A lambda that takes the text, depth, and the Block.
|
|
102
|
+
# [enterChild] A lambda that takes depth and the Block.
|
|
103
|
+
# [exitChild] A lambda that takes depth and the Block.
|
|
104
|
+
# For example:
|
|
105
|
+
#
|
|
106
|
+
# printBlock = lambda { |text, depth, block| ... }
|
|
107
|
+
# enterBlock = lambda { |depth, block| ... }
|
|
108
|
+
# exitBlock = lambda { |depth, block| ... }
|
|
109
|
+
#
|
|
110
|
+
# block.walkText( printBlock, enterBlock, exitBlock )
|
|
111
|
+
#
|
|
69
112
|
def walkText(printFunc, enterChild, exitChild)
|
|
70
113
|
walkTextHelper(printFunc, enterChild, exitChild, 0)
|
|
71
114
|
end
|
|
72
115
|
|
|
116
|
+
protected
|
|
117
|
+
|
|
118
|
+
# Helper function for walkText. Takes the same arguments
|
|
119
|
+
# with the depth set to 0.
|
|
73
120
|
def walkTextHelper(printFunc, enterChild, exitChild, depth)
|
|
74
121
|
enterChild.call(depth, self)
|
|
75
122
|
|
|
@@ -95,5 +142,25 @@ module Ntxt
|
|
|
95
142
|
|
|
96
143
|
exitChild.call(depth, self)
|
|
97
144
|
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
if RUBY_VERSION =~ /^1.8/
|
|
149
|
+
|
|
150
|
+
# Regular expression matcher with an offset.
|
|
151
|
+
# The implementation of this is descided at runtime depending on
|
|
152
|
+
# if Ruby 1.9's new RE match method is found.
|
|
153
|
+
def self.blockReMatch(re, txt, offset)
|
|
154
|
+
re.match(txt[offset..-1])
|
|
155
|
+
end
|
|
156
|
+
else
|
|
157
|
+
|
|
158
|
+
# Regular expression matcher with an offset.
|
|
159
|
+
# The implementation of this is descided at runtime depending on
|
|
160
|
+
# if Ruby 1.9's new RE match method is found.
|
|
161
|
+
def self.blockReMatch(re, txt, offset)
|
|
162
|
+
re.match(txt, offset)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
98
165
|
end
|
|
99
166
|
end
|
data/lib/ntxt/ntxt.rb
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
|
|
2
2
|
module Ntxt
|
|
3
3
|
|
|
4
|
-
# Root class that contains the text array that
|
|
5
|
-
# and the root block.
|
|
4
|
+
# Root class that contains the text array that Block objects reference.
|
|
6
5
|
class Ntxt
|
|
7
|
-
attr_accessor :text, :rootBlock
|
|
8
6
|
|
|
7
|
+
# The raw text file. This is a String.
|
|
8
|
+
attr_accessor :text
|
|
9
|
+
|
|
10
|
+
# The root Block. It will contain all tags in the document
|
|
11
|
+
# and has a Block.start of 0 and a Block.offset of #text.length.
|
|
12
|
+
attr_accessor :rootBlock
|
|
13
|
+
|
|
14
|
+
# Create a new Ntxt object. This requires a String that is the text
|
|
15
|
+
# of the object.
|
|
16
|
+
# ntxt = Ntxt::Ntxt.new( File.open('n.txt'){ |io| io.read } )
|
|
9
17
|
def initialize(text)
|
|
10
18
|
@text = text
|
|
11
19
|
@rootBlock = (Parser.new).parse(self)
|
|
12
20
|
end
|
|
13
21
|
|
|
14
|
-
# walkText
|
|
15
|
-
# Walk the ntxt tree with 3 callbacks, print, enter, and exit.
|
|
16
|
-
# Print is a lambda that takes the text, the depth, and a copy of
|
|
17
|
-
# the Ntxt::Block it is in.
|
|
18
|
-
# Enter is the same, but is called with no text argument and is called
|
|
19
|
-
# when a block is entered (that is, the depth has increased by 1).
|
|
20
|
-
# Exit is the same, but is called with no text argument and is called
|
|
21
|
-
# when a block is exited (that is, the depth has decreated by 1).
|
|
22
|
+
# Calls Block#walkText.
|
|
22
23
|
def walkText(print, enter, exit)
|
|
23
24
|
@rootBlock.walkText(print, enter, exit)
|
|
24
25
|
end
|
data/lib/ntxt/parser.rb
CHANGED
|
@@ -1,98 +1,126 @@
|
|
|
1
1
|
require 'ntxt/block'
|
|
2
|
+
require 'ntxt/ntxt'
|
|
2
3
|
|
|
3
4
|
module Ntxt
|
|
5
|
+
|
|
6
|
+
# The parser for Ntxt. Most of this a typical user will not find useful
|
|
7
|
+
# with the exception of Parser.parse.
|
|
4
8
|
class Parser
|
|
5
9
|
|
|
6
|
-
|
|
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.
|
|
7
13
|
class State
|
|
8
14
|
|
|
9
|
-
|
|
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
|
|
10
40
|
|
|
11
41
|
def initialize(lines, block, lineStart, start, lineEnd)
|
|
12
|
-
@lines
|
|
13
|
-
@block
|
|
14
|
-
@lineStart = lineStart
|
|
15
|
-
@
|
|
16
|
-
@line
|
|
17
|
-
@start
|
|
18
|
-
@offset
|
|
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.
|
|
19
49
|
end
|
|
20
50
|
|
|
21
|
-
# Return the current line.
|
|
51
|
+
# Return the current line. If #prevLine or #nextLine has
|
|
52
|
+
# walked outside of the #lineStart or #lineEnd limits this will
|
|
53
|
+
# return nil.
|
|
22
54
|
def currLine
|
|
23
|
-
@
|
|
55
|
+
if @line < @lineEnd && @line >= @lineStart
|
|
56
|
+
@lines[@line]
|
|
57
|
+
else
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
24
60
|
end
|
|
25
61
|
|
|
26
|
-
#
|
|
27
|
-
# If
|
|
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.
|
|
28
65
|
def nextLine
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@offset = nextOffset
|
|
33
|
-
@line = nextLine
|
|
34
|
-
@lines[nextLine]
|
|
35
|
-
else
|
|
66
|
+
|
|
67
|
+
# If we are already past the end, return nil, do nothing.
|
|
68
|
+
if @line >= @lineEnd
|
|
36
69
|
nil
|
|
37
|
-
|
|
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
|
|
38
79
|
end # nextLine
|
|
39
80
|
|
|
40
|
-
#
|
|
41
|
-
# If
|
|
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.
|
|
42
84
|
def prevLine
|
|
43
|
-
|
|
44
|
-
if nextLine >= @lineStart
|
|
45
|
-
nextOffset = @offset - @lines[nextLine].length - 1
|
|
46
|
-
@offset = nextOffset
|
|
47
|
-
@line = nextLine
|
|
48
|
-
@lines[nextLine]
|
|
49
|
-
else
|
|
85
|
+
if @line < @lineStart
|
|
50
86
|
nil
|
|
87
|
+
else
|
|
88
|
+
nLine = @line - 1
|
|
89
|
+
@offset = @offset - @lines[nLine].length - 1
|
|
90
|
+
@line = nLine
|
|
91
|
+
@lines[nLine]
|
|
51
92
|
end
|
|
52
93
|
end # prevLine
|
|
53
94
|
|
|
54
95
|
# Shift the state starting points to the current position of
|
|
55
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.
|
|
56
100
|
def consume
|
|
57
101
|
@start = @start + @offset
|
|
58
102
|
@offset = 0
|
|
59
103
|
@lineStart = @line
|
|
60
104
|
end
|
|
61
105
|
|
|
62
|
-
#
|
|
63
|
-
# and ends at the current line of the given state.
|
|
64
|
-
def lowerSubState
|
|
65
|
-
endOfFrame = @line+1
|
|
66
|
-
|
|
67
|
-
endOfFrame = @lineEnd if endOfFrame > @lineEnd
|
|
68
|
-
|
|
69
|
-
State.new(@lines,
|
|
70
|
-
@block,
|
|
71
|
-
@lineStart+1,
|
|
72
|
-
@start + @lines[@lineStart].length + 1,
|
|
73
|
-
endOfFrame)
|
|
74
|
-
end # lowerSubState
|
|
75
|
-
|
|
76
|
-
# Create a new state that is framed with the remaining contents of
|
|
77
|
-
# this state
|
|
78
|
-
def upperSubState
|
|
79
|
-
State.new(@lines, @block, @line, @start + @offset, @lineEnd)
|
|
80
|
-
end
|
|
81
|
-
|
|
106
|
+
# Print as a string.
|
|
82
107
|
def to_s
|
|
83
108
|
"lineStart: %s, lineEnd: %s, line: %s, start: %s, offset: %s"%[
|
|
84
109
|
@lineStart, @lineEnd, @line, @start, @offset
|
|
85
110
|
]
|
|
86
111
|
end
|
|
87
112
|
|
|
88
|
-
# Seek this state's position to the
|
|
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.
|
|
89
116
|
def seek(state)
|
|
90
117
|
@line = state.line
|
|
91
118
|
@offset = (state.start + state.offset - @start).abs()
|
|
92
119
|
end # seek
|
|
93
120
|
|
|
94
121
|
end # Parser::State
|
|
95
|
-
|
|
122
|
+
|
|
123
|
+
|
|
96
124
|
|
|
97
125
|
# Return an array in which the first element is the indent length and
|
|
98
126
|
# the second element is the contained text. Nil otherwise.
|
|
@@ -115,6 +143,17 @@ module Ntxt
|
|
|
115
143
|
end
|
|
116
144
|
end # self.hlevel
|
|
117
145
|
|
|
146
|
+
# 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
|
+
# Some tag examples:
|
|
153
|
+
# [a tag] [another tag]
|
|
154
|
+
# [a tag] [another tag] Not a tag. [not a tag]
|
|
155
|
+
# No tag on this line.
|
|
156
|
+
# No tag on this line either. [not a tag]
|
|
118
157
|
def self.extractTags(block, line)
|
|
119
158
|
while line =~ /^\s*\[([^\[]+)\]/m
|
|
120
159
|
block.addTag($~[1])
|
|
@@ -123,6 +162,10 @@ module Ntxt
|
|
|
123
162
|
end
|
|
124
163
|
end # self.extractTags
|
|
125
164
|
|
|
165
|
+
# Parse the given Ntxt 's Ntxt#text.
|
|
166
|
+
# [ntxtObj] If this is an Ntxt object, Ntxt#text is parsed.
|
|
167
|
+
# If +ntxtObj+ is not an Ntxt object, it is assumed to be
|
|
168
|
+
# a valid argument for Ntxt.new and a new Ntxt is constructed.
|
|
126
169
|
def parse(ntxtObj)
|
|
127
170
|
|
|
128
171
|
# If ntxtObj isn't an Ntxt, create it as one.
|
|
@@ -144,60 +187,87 @@ module Ntxt
|
|
|
144
187
|
nil
|
|
145
188
|
end
|
|
146
189
|
end # parse(ntxtObj)
|
|
147
|
-
|
|
190
|
+
|
|
191
|
+
# Take the state off the top of the #stack and attempt to parse
|
|
192
|
+
# an Hlevel block. An HLevel block is a wiki-like header block of text.
|
|
193
|
+
# For example:
|
|
194
|
+
# = Header 1 =
|
|
195
|
+
# == Header 2 ==
|
|
196
|
+
# [level] an integer from 1 to 6.
|
|
197
|
+
# [title] a string of the text found between the equal signs.
|
|
148
198
|
def parseHlevel(level, title)
|
|
149
199
|
state = @stack[-1]
|
|
150
200
|
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
204
|
+
|
|
205
|
+
while line
|
|
206
|
+
# Check if we have discovered another block in the form of an hlevel.
|
|
207
|
+
hl = Parser::hlevel(line)
|
|
153
208
|
|
|
154
|
-
if
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if hl && hl[0].to_i <= level
|
|
158
|
-
state.prevLine # Rewind. We steped onto another h block.
|
|
159
|
-
break
|
|
160
|
-
end
|
|
209
|
+
if hl && hl[0].to_i <= level
|
|
210
|
+
break
|
|
161
211
|
end
|
|
162
|
-
|
|
212
|
+
|
|
213
|
+
line = state.nextLine
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
block = Block.new(
|
|
217
|
+
state.block.ntxt,
|
|
218
|
+
state.block,
|
|
219
|
+
state.start,
|
|
220
|
+
state.offset)
|
|
221
|
+
|
|
222
|
+
subState = State.new(
|
|
223
|
+
state.lines,
|
|
224
|
+
block,
|
|
225
|
+
state.lineStart+1,
|
|
226
|
+
state.start + state.lines[state.lineStart].length + 1,
|
|
227
|
+
state.line)
|
|
163
228
|
|
|
164
|
-
block = Block.new(state.block.ntxt,
|
|
165
|
-
state.block,
|
|
166
|
-
state.start,
|
|
167
|
-
state.offset)
|
|
168
|
-
subState = state.lowerSubState
|
|
169
|
-
subState.block = block
|
|
170
229
|
@stack.push subState
|
|
171
230
|
parseLines
|
|
172
231
|
@stack.pop
|
|
173
232
|
state.consume
|
|
174
233
|
end # parseHlevel(leve, title)
|
|
175
234
|
|
|
235
|
+
# Parse blocks of text that are indented at the given level or greater.
|
|
236
|
+
# [indentLevel] an integer denoteing the number of characters this line is
|
|
237
|
+
# indented at.
|
|
238
|
+
# [text] the content of the line that was indented.
|
|
176
239
|
def parseIndent(indentLevel, text)
|
|
177
240
|
state = @stack[-1]
|
|
178
241
|
line = state.currLine
|
|
179
242
|
|
|
180
|
-
#
|
|
243
|
+
# Build the block. Update the offset.
|
|
181
244
|
block = Block.new(state.block.ntxt,
|
|
182
245
|
state.block,
|
|
183
246
|
state.start,
|
|
184
247
|
state.offset)
|
|
185
|
-
|
|
248
|
+
|
|
249
|
+
id = rand(100)
|
|
250
|
+
|
|
186
251
|
# Position state at the ed of the block.
|
|
187
252
|
# Blocks are ended by empty lines or lines with the = starting them.
|
|
188
253
|
while line
|
|
189
|
-
|
|
254
|
+
|
|
190
255
|
break unless line =~ /^(\s*)([^=\s].*)$/
|
|
191
256
|
|
|
192
257
|
nextIndentLevel = $~[1].length
|
|
193
258
|
nextLine = $~[2]
|
|
194
259
|
|
|
195
260
|
break if nextIndentLevel < indentLevel
|
|
196
|
-
|
|
261
|
+
|
|
197
262
|
if nextIndentLevel > indentLevel
|
|
198
263
|
# Advance to the next line after parsing a subblock.
|
|
199
|
-
subState =
|
|
200
|
-
|
|
264
|
+
subState = State.new(
|
|
265
|
+
state.lines,
|
|
266
|
+
block,
|
|
267
|
+
state.line,
|
|
268
|
+
state.start + state.offset,
|
|
269
|
+
state.lineEnd)
|
|
270
|
+
|
|
201
271
|
@stack.push subState
|
|
202
272
|
parseIndent(nextIndentLevel, nextLine)
|
|
203
273
|
@stack.pop
|
|
@@ -209,28 +279,35 @@ module Ntxt
|
|
|
209
279
|
end # if nextIndentLevel > indentLevel
|
|
210
280
|
|
|
211
281
|
end # while line
|
|
212
|
-
|
|
213
282
|
block.offset = state.offset
|
|
214
283
|
state.consume
|
|
215
284
|
end # parseIndent(indentLevel, text)
|
|
216
285
|
|
|
286
|
+
# This is the root of the parser's call tree after #parse sets up
|
|
287
|
+
# the parse. This plucks the State off the Parser.stack, obtains the
|
|
288
|
+
# State.currLine.
|
|
289
|
+
#
|
|
290
|
+
# When an indented line is found, #parseIndent is called.
|
|
291
|
+
# When a header line is found, #parseHlevel is caled.
|
|
292
|
+
# Otherwise, we move to the next line.
|
|
217
293
|
def parseLines
|
|
218
294
|
state = @stack[-1]
|
|
219
|
-
state.block.children = []
|
|
220
295
|
line = state.currLine
|
|
221
296
|
|
|
222
297
|
while line
|
|
223
298
|
tmp = Parser::hlevel(line)
|
|
224
|
-
|
|
299
|
+
|
|
225
300
|
if tmp
|
|
226
301
|
state.consume
|
|
227
302
|
parseHlevel(tmp[0].to_i, tmp[1])
|
|
303
|
+
line = state.currLine
|
|
228
304
|
elsif line =~ /^(\s*)(\S.*)$/
|
|
229
305
|
state.consume
|
|
230
306
|
parseIndent($~[1].length, $~[2])
|
|
307
|
+
line = state.currLine
|
|
308
|
+
else
|
|
309
|
+
line = state.nextLine
|
|
231
310
|
end # if tmp
|
|
232
|
-
|
|
233
|
-
line = state.nextLine
|
|
234
311
|
end # while line
|
|
235
312
|
end # parseLines
|
|
236
313
|
end
|
metadata
CHANGED
|
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
|
5
5
|
segments:
|
|
6
6
|
- 1
|
|
7
7
|
- 0
|
|
8
|
-
-
|
|
9
|
-
version: 1.0.
|
|
8
|
+
- 1
|
|
9
|
+
version: 1.0.1
|
|
10
10
|
platform: ruby
|
|
11
11
|
authors:
|
|
12
12
|
- Sam Baskinger
|
|
@@ -14,7 +14,7 @@ autorequire:
|
|
|
14
14
|
bindir: bin
|
|
15
15
|
cert_chain: []
|
|
16
16
|
|
|
17
|
-
date: 2011-
|
|
17
|
+
date: 2011-11-01 00:00:00 -05:00
|
|
18
18
|
default_executable:
|
|
19
19
|
dependencies: []
|
|
20
20
|
|