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 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
@@ -0,0 +1,3 @@
1
+ require 'ntxt/block'
2
+ require 'ntxt/parser'
3
+ require 'ntxt/ntxt'
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
@@ -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
+