ntxt 1.0.5 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
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