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.
Files changed (4) hide show
  1. data/bin/ntxt +8 -5
  2. data/lib/ntxt/block.rb +9 -1
  3. data/lib/ntxt/parser.rb +97 -257
  4. 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.5"
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 down the tree.') do |v|
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 the whole parent block.') do |v|
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
- $configs[:tag_string] = ARGV.shift if ARGV.length > 0
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.
@@ -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 is_root?
95
+ def root?
88
96
  @parent.nil?
89
97
  end
90
98
 
@@ -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 self.hlevel(line)
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
- nil
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 # self.hlevel
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. [not a tag]
69
+ # [a tag] [another tag] Not a tag. [a tag]
155
70
  # No tag on this line.
156
- # No tag on this line either. [not a tag]
157
- def self.extractTags(block, line)
158
- while line =~ /^\s*\[([^\[]+)\]/m
159
- block.addTag($~[1])
160
- matchLength = $~[0].length
161
- line = line[matchLength,line.length - matchLength]
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
- @stack = [ State.new( lines, rootBlock, 0, 0, lines.length ) ]
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
- if @stack.length == 1
183
- # parse success!
184
- rootBlock
185
- else
186
- # parse failure.
187
- nil
188
- end
99
+ rootBlock
189
100
  end # parse(ntxtObj)
190
101
 
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.
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
- while line
206
- # Check if we have discovered another block in the form of an hlevel.
207
- hl = Parser::hlevel(line)
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
- subState = State.new(
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
- # Build the block. Update the offset.
242
- block = Block.new(state.block.ntxt,
243
- state.block,
244
- state.start,
245
- state.offset)
113
+ if hlvl
114
+ while @currentBlock.header >= hlvl[0] do
115
+ closeBlock
116
+ end
246
117
 
247
- # Position state at the ed of the block.
248
- # Blocks are ended by empty lines or lines with the = starting them.
249
- while line
118
+ @currentBlock = Block.new(
119
+ @currentBlock.ntxt,
120
+ @currentBlock,
121
+ @currentOffset)
122
+ @currentBlock.header = hlvl[0]
250
123
 
251
- break if Parser::hlevel(line)
252
- break unless line =~ /^(\s*)(..*)$/
124
+ hlvl[2].times { nextLine }
125
+ @currentIndent = 0
253
126
 
254
- nextIndentLevel = $~[1].length
255
- nextLine = $~[2]
256
-
257
- break if nextIndentLevel < indentLevel
127
+ next
128
+ end
258
129
 
259
- if nextIndentLevel > indentLevel
260
- # Advance to the next line after parsing a subblock.
261
- subState = State.new(
262
- state.lines,
263
- block,
264
- state.line,
265
- state.start + state.offset,
266
- state.lineEnd)
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
- @stack.push subState
269
- parseIndent(nextIndentLevel, nextLine)
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
- if tmp
298
- state.consume
299
- parseHlevel(tmp[0].to_i, tmp[1])
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.5
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-25 00:00:00.000000000 Z
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