ntxt 1.0.0
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 +0 -0
- data/bin/ntxt +87 -0
- data/lib/ntxt.rb +3 -0
- data/lib/ntxt/block.rb +99 -0
- data/lib/ntxt/ntxt.rb +26 -0
- data/lib/ntxt/parser.rb +237 -0
- metadata +71 -0
data/README.rdoc
ADDED
File without changes
|
data/bin/ntxt
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'optparse'
|
5
|
+
require 'ntxt'
|
6
|
+
|
7
|
+
$configs = {
|
8
|
+
:cmd => 'tag',
|
9
|
+
:tag_string => ''
|
10
|
+
}
|
11
|
+
|
12
|
+
OptionParser.new do |opt|
|
13
|
+
|
14
|
+
opt.on('--trace', 'Trace through the file printing summaries.' ) do |v|
|
15
|
+
$configs[:cmd] = 'trace'
|
16
|
+
end
|
17
|
+
|
18
|
+
opt.on('-p','--print', 'Print the whole file one block at a time.' ) do |v|
|
19
|
+
$configs[:cmd] = 'print'
|
20
|
+
end
|
21
|
+
|
22
|
+
opt.on('-T', '--print_tags', 'Print all tags in the document.') do |v|
|
23
|
+
$configs[:cmd] = 'print_tags'
|
24
|
+
end
|
25
|
+
|
26
|
+
opt.on('-t', '--tag=String', 'Search and print the give so-tagged blocks.') do |v|
|
27
|
+
$configs[:cmd] = 'tag'
|
28
|
+
$configs[:tag_string] = v
|
29
|
+
end
|
30
|
+
|
31
|
+
opt.on('-s','--search=String', 'Search the text for.' ) do |v|
|
32
|
+
$configs[:cmd] = 'search'
|
33
|
+
$configs[:search_string] = v
|
34
|
+
end
|
35
|
+
end.parse! ARGV
|
36
|
+
|
37
|
+
if ARGV.length > 0
|
38
|
+
$configs[:filename] = ARGV.shift
|
39
|
+
else
|
40
|
+
puts "No file specified. Exiting"
|
41
|
+
exit 1
|
42
|
+
end
|
43
|
+
|
44
|
+
def printNonEmpty(txt)
|
45
|
+
if txt
|
46
|
+
txt = txt.strip
|
47
|
+
print txt, "\n" if txt.length > 0
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
$configs[:tag_string] = ARGV.shift if ARGV.length > 0
|
52
|
+
|
53
|
+
ntxt = File.open($configs[:filename]) { |io| Ntxt::Ntxt.new(io.read) }
|
54
|
+
|
55
|
+
case $configs[:cmd]
|
56
|
+
when 'print_tags'
|
57
|
+
# Notice that we are re-wrapping the tags back into square brackets.
|
58
|
+
puts "[#{ntxt.rootBlock.tags.sort.join('] [')}]"
|
59
|
+
when 'tag'
|
60
|
+
ntxt.walkText(
|
61
|
+
lambda { |txt, depth, block|
|
62
|
+
printNonEmpty txt if block.tags.join(', ').index( $configs[:tag_string])},
|
63
|
+
lambda { |depth, block| },
|
64
|
+
lambda { |depth, block| } )
|
65
|
+
when 'search'
|
66
|
+
ntxt.walkText(
|
67
|
+
lambda { |txt, depth, block|
|
68
|
+
printNonEmpty txt if txt && txt.index( $configs[:search_string])},
|
69
|
+
lambda { |depth, block| },
|
70
|
+
lambda { |depth, block| } )
|
71
|
+
when 'print'
|
72
|
+
ntxt.walkText(
|
73
|
+
lambda { |txt, depth, block| print txt },
|
74
|
+
lambda { |depth, block| },
|
75
|
+
lambda { |depth, block| } )
|
76
|
+
when 'trace'
|
77
|
+
ntxt.walkText(
|
78
|
+
lambda { |txt, depth, block| print txt },
|
79
|
+
lambda { |depth, block| puts "-----> #{depth} #{block.tags.join(',')}" },
|
80
|
+
lambda { |depth, block| puts "<----- #{depth} " } )
|
81
|
+
when ''
|
82
|
+
# nop
|
83
|
+
when nil
|
84
|
+
# nop
|
85
|
+
else
|
86
|
+
puts "Command #{$configs[:cmd]} not found."
|
87
|
+
end
|
data/lib/ntxt.rb
ADDED
data/lib/ntxt/block.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
|
2
|
+
module Ntxt
|
3
|
+
|
4
|
+
# Block constructor
|
5
|
+
# ntxtObj the root Ntxt object.
|
6
|
+
# parentBlock the parent Block object.
|
7
|
+
# startTxt the starting offset in ntxtObj.text where this starts
|
8
|
+
# stopTxt the offset in ntxtObje.text after the startTxt position.
|
9
|
+
# The block of text submitted must be a valid block, meaning, it may
|
10
|
+
# ONLY contain subblocks.
|
11
|
+
class Block
|
12
|
+
|
13
|
+
attr_accessor :children, :tags, :start, :offset, :ntxt, :parent
|
14
|
+
|
15
|
+
if RUBY_VERSION =~ /^1.8/
|
16
|
+
def self.blockReMatch(re, txt, offset)
|
17
|
+
re.match(txt[offset..-1])
|
18
|
+
end
|
19
|
+
else
|
20
|
+
def self.blockReMatch(re, txt, offset)
|
21
|
+
re.match(text, offset)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(ntxtObj, parentBlock=nil, startTxt=0, stopTxt=0)
|
26
|
+
@children = []
|
27
|
+
@tags = []
|
28
|
+
@start = startTxt
|
29
|
+
@offset = stopTxt || ntxtObj.text.length
|
30
|
+
@ntxt = ntxtObj
|
31
|
+
@parent = parentBlock
|
32
|
+
|
33
|
+
if @parent
|
34
|
+
re = /\s*\S/m
|
35
|
+
#m = re.match( ntxtObj.text, @start )
|
36
|
+
m = Block::blockReMatch(re, ntxtObj.text, @start)
|
37
|
+
if m
|
38
|
+
@indent = m[0].length
|
39
|
+
else
|
40
|
+
@indent = 0
|
41
|
+
end
|
42
|
+
@parent.children.push(self)
|
43
|
+
else
|
44
|
+
@indent = 0
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def addTag(tag)
|
49
|
+
@tags.push(tag)
|
50
|
+
@parent.addTag(tag) if @parent
|
51
|
+
end
|
52
|
+
|
53
|
+
def text
|
54
|
+
@ntxt.text[@start, @offset]
|
55
|
+
end
|
56
|
+
|
57
|
+
def is_root?
|
58
|
+
@parent
|
59
|
+
end
|
60
|
+
|
61
|
+
def walk(&y)
|
62
|
+
yield self
|
63
|
+
@children.each { |c| c.walk(&y) }
|
64
|
+
end
|
65
|
+
|
66
|
+
# printFunc is a lambda that takes the text, depth, and the node.
|
67
|
+
# enterChild is a lambda that takes depth and the node.
|
68
|
+
# exitChild is a lambda that takes depth and the node.
|
69
|
+
def walkText(printFunc, enterChild, exitChild)
|
70
|
+
walkTextHelper(printFunc, enterChild, exitChild, 0)
|
71
|
+
end
|
72
|
+
|
73
|
+
def walkTextHelper(printFunc, enterChild, exitChild, depth)
|
74
|
+
enterChild.call(depth, self)
|
75
|
+
|
76
|
+
if @children.length == 0
|
77
|
+
printFunc.call(text(), depth, self)
|
78
|
+
else
|
79
|
+
prevEnd = @start
|
80
|
+
|
81
|
+
@children.each do |child|
|
82
|
+
start = prevEnd
|
83
|
+
offset = child.start - start
|
84
|
+
printFunc.call(@ntxt.text[start, offset], depth, self)
|
85
|
+
child.walkTextHelper(printFunc, enterChild, exitChild, depth+1)
|
86
|
+
prevEnd = child.start + child.offset
|
87
|
+
end # @children.each
|
88
|
+
|
89
|
+
lastChild = @children[-1]
|
90
|
+
start = lastChild.start + lastChild.offset
|
91
|
+
offset = @start + @offset - start
|
92
|
+
|
93
|
+
printFunc.call(@ntxt.text[start, offset], depth, self)
|
94
|
+
end # else of if @children.length == 0
|
95
|
+
|
96
|
+
exitChild.call(depth, self)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/ntxt/ntxt.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
module Ntxt
|
3
|
+
|
4
|
+
# Root class that contains the text array that Blocks reference
|
5
|
+
# and the root block.
|
6
|
+
class Ntxt
|
7
|
+
attr_accessor :text, :rootBlock
|
8
|
+
|
9
|
+
def initialize(text)
|
10
|
+
@text = text
|
11
|
+
@rootBlock = (Parser.new).parse(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
# walkText(print, enter, exit)
|
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
|
+
def walkText(print, enter, exit)
|
23
|
+
@rootBlock.walkText(print, enter, exit)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/ntxt/parser.rb
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
require 'ntxt/block'
|
2
|
+
|
3
|
+
module Ntxt
|
4
|
+
class Parser
|
5
|
+
|
6
|
+
###########################################################################
|
7
|
+
class State
|
8
|
+
|
9
|
+
attr_accessor :lines, :block, :lineStart, :lineEnd, :line, :start, :offset
|
10
|
+
|
11
|
+
def initialize(lines, block, lineStart, start, lineEnd)
|
12
|
+
@lines = lines
|
13
|
+
@block = block
|
14
|
+
@lineStart = lineStart
|
15
|
+
@lineEnd = lineEnd
|
16
|
+
@line = lineStart
|
17
|
+
@start = start
|
18
|
+
@offset = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
# Return the current line.
|
22
|
+
def currLine
|
23
|
+
@lines[@line]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Shift the state to the next line and return that line.
|
27
|
+
# If this goes out of bounds of the text nil is returned.
|
28
|
+
def nextLine
|
29
|
+
nextLine = @line+1
|
30
|
+
if nextLine < @lineEnd
|
31
|
+
nextOffset = @offset + @lines[@line].length + 1
|
32
|
+
@offset = nextOffset
|
33
|
+
@line = nextLine
|
34
|
+
@lines[nextLine]
|
35
|
+
else
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end # nextLine
|
39
|
+
|
40
|
+
# Shift the state to the previous line and return that line.
|
41
|
+
# If this goes out of bounds of the text nil is returned.
|
42
|
+
def prevLine
|
43
|
+
nextLine = @line - 1
|
44
|
+
if nextLine >= @lineStart
|
45
|
+
nextOffset = @offset - @lines[nextLine].length - 1
|
46
|
+
@offset = nextOffset
|
47
|
+
@line = nextLine
|
48
|
+
@lines[nextLine]
|
49
|
+
else
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end # prevLine
|
53
|
+
|
54
|
+
# Shift the state starting points to the current position of
|
55
|
+
# what has been read in this state, effecitvely consuming that input.
|
56
|
+
def consume
|
57
|
+
@start = @start + @offset
|
58
|
+
@offset = 0
|
59
|
+
@lineStart = @line
|
60
|
+
end
|
61
|
+
|
62
|
+
# Create a new state that is framed from the lineStart+1 of this state
|
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
|
+
|
82
|
+
def to_s
|
83
|
+
"lineStart: %s, lineEnd: %s, line: %s, start: %s, offset: %s"%[
|
84
|
+
@lineStart, @lineEnd, @line, @start, @offset
|
85
|
+
]
|
86
|
+
end
|
87
|
+
|
88
|
+
# Seek this state's position to the tiven state's position.
|
89
|
+
def seek(state)
|
90
|
+
@line = state.line
|
91
|
+
@offset = (state.start + state.offset - @start).abs()
|
92
|
+
end # seek
|
93
|
+
|
94
|
+
end # Parser::State
|
95
|
+
###########################################################################
|
96
|
+
|
97
|
+
# Return an array in which the first element is the indent length and
|
98
|
+
# the second element is the contained text. Nil otherwise.
|
99
|
+
def self.hlevel(line)
|
100
|
+
case line
|
101
|
+
when /^\s*=([^=].*)=\s*$/
|
102
|
+
[ 1, $~[1] ]
|
103
|
+
when /^\s*==([^=].*)==\s*$/
|
104
|
+
[ 2, $~[1] ]
|
105
|
+
when /^\s*===([^=].*)===\s*$/
|
106
|
+
[ 3, $~[1] ]
|
107
|
+
when /^\s*====([^=].*)====\s*$/
|
108
|
+
[ 4, $~[1] ]
|
109
|
+
when /^\s*=====([^=].*)=====\s*$/
|
110
|
+
[ 5, $~[1] ]
|
111
|
+
when /^\s*======([^=].*)======\s*$/
|
112
|
+
[ 6, $~[1] ]
|
113
|
+
else
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
end # self.hlevel
|
117
|
+
|
118
|
+
def self.extractTags(block, line)
|
119
|
+
while line =~ /^\s*\[([^\[]+)\]/m
|
120
|
+
block.addTag($~[1])
|
121
|
+
matchLength = $~[0].length
|
122
|
+
line = line[matchLength,line.length - matchLength]
|
123
|
+
end
|
124
|
+
end # self.extractTags
|
125
|
+
|
126
|
+
def parse(ntxtObj)
|
127
|
+
|
128
|
+
# If ntxtObj isn't an Ntxt, create it as one.
|
129
|
+
( ntxtObj = Ntxt.new(ntxtObj) ) unless ntxtObj.is_a?( Ntxt )
|
130
|
+
|
131
|
+
lines = ntxtObj.text.split("\n")
|
132
|
+
|
133
|
+
rootBlock = Block.new(ntxtObj)
|
134
|
+
|
135
|
+
@stack = [ State.new( lines, rootBlock, 0, 0, lines.length ) ]
|
136
|
+
|
137
|
+
parseLines()
|
138
|
+
|
139
|
+
if @stack.length == 1
|
140
|
+
# parse success!
|
141
|
+
rootBlock
|
142
|
+
else
|
143
|
+
# parse failure.
|
144
|
+
nil
|
145
|
+
end
|
146
|
+
end # parse(ntxtObj)
|
147
|
+
|
148
|
+
def parseHlevel(level, title)
|
149
|
+
state = @stack[-1]
|
150
|
+
|
151
|
+
begin
|
152
|
+
line = state.nextLine
|
153
|
+
|
154
|
+
if line
|
155
|
+
hl = Parser::hlevel(line)
|
156
|
+
|
157
|
+
if hl && hl[0].to_i <= level
|
158
|
+
state.prevLine # Rewind. We steped onto another h block.
|
159
|
+
break
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end while line
|
163
|
+
|
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
|
+
@stack.push subState
|
171
|
+
parseLines
|
172
|
+
@stack.pop
|
173
|
+
state.consume
|
174
|
+
end # parseHlevel(leve, title)
|
175
|
+
|
176
|
+
def parseIndent(indentLevel, text)
|
177
|
+
state = @stack[-1]
|
178
|
+
line = state.currLine
|
179
|
+
|
180
|
+
# BUild the block. Update the offset.
|
181
|
+
block = Block.new(state.block.ntxt,
|
182
|
+
state.block,
|
183
|
+
state.start,
|
184
|
+
state.offset)
|
185
|
+
|
186
|
+
# Position state at the ed of the block.
|
187
|
+
# Blocks are ended by empty lines or lines with the = starting them.
|
188
|
+
while line
|
189
|
+
|
190
|
+
break unless line =~ /^(\s*)([^=\s].*)$/
|
191
|
+
|
192
|
+
nextIndentLevel = $~[1].length
|
193
|
+
nextLine = $~[2]
|
194
|
+
|
195
|
+
break if nextIndentLevel < indentLevel
|
196
|
+
|
197
|
+
if nextIndentLevel > indentLevel
|
198
|
+
# Advance to the next line after parsing a subblock.
|
199
|
+
subState = state.upperSubState()
|
200
|
+
subState.block = block
|
201
|
+
@stack.push subState
|
202
|
+
parseIndent(nextIndentLevel, nextLine)
|
203
|
+
@stack.pop
|
204
|
+
state.seek subState
|
205
|
+
line = state.currLine
|
206
|
+
else
|
207
|
+
Parser::extractTags(block, line)
|
208
|
+
line = state.nextLine
|
209
|
+
end # if nextIndentLevel > indentLevel
|
210
|
+
|
211
|
+
end # while line
|
212
|
+
|
213
|
+
block.offset = state.offset
|
214
|
+
state.consume
|
215
|
+
end # parseIndent(indentLevel, text)
|
216
|
+
|
217
|
+
def parseLines
|
218
|
+
state = @stack[-1]
|
219
|
+
state.block.children = []
|
220
|
+
line = state.currLine
|
221
|
+
|
222
|
+
while line
|
223
|
+
tmp = Parser::hlevel(line)
|
224
|
+
|
225
|
+
if tmp
|
226
|
+
state.consume
|
227
|
+
parseHlevel(tmp[0].to_i, tmp[1])
|
228
|
+
elsif line =~ /^(\s*)(\S.*)$/
|
229
|
+
state.consume
|
230
|
+
parseIndent($~[1].length, $~[2])
|
231
|
+
end # if tmp
|
232
|
+
|
233
|
+
line = state.nextLine
|
234
|
+
end # while line
|
235
|
+
end # parseLines
|
236
|
+
end
|
237
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ntxt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
version: 1.0.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Sam Baskinger
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-10-30 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: |
|
22
|
+
A collection of tools to assist Sam in coding.
|
23
|
+
|
24
|
+
email: basking2@yahoo.com
|
25
|
+
executables:
|
26
|
+
- ntxt
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files: []
|
30
|
+
|
31
|
+
files:
|
32
|
+
- README.rdoc
|
33
|
+
- lib/ntxt/block.rb
|
34
|
+
- lib/ntxt/parser.rb
|
35
|
+
- lib/ntxt/ntxt.rb
|
36
|
+
- lib/ntxt.rb
|
37
|
+
- bin/ntxt
|
38
|
+
has_rdoc: true
|
39
|
+
homepage: http://coffeesgone.wordpress.com
|
40
|
+
licenses: []
|
41
|
+
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
segments:
|
53
|
+
- 0
|
54
|
+
version: "0"
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.3.7
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: A parser and some tools for the ntxt text block format.
|
70
|
+
test_files: []
|
71
|
+
|