review 5.1.1 → 5.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby-tex.yml +6 -2
  3. data/.github/workflows/ruby-win.yml +6 -2
  4. data/.github/workflows/ruby.yml +6 -2
  5. data/.rubocop.yml +5 -319
  6. data/NEWS.ja.md +149 -0
  7. data/NEWS.md +149 -1
  8. data/README.md +9 -8
  9. data/bin/review +1 -1
  10. data/bin/review-catalog-converter +15 -15
  11. data/bin/review-check +7 -7
  12. data/bin/review-compile +14 -23
  13. data/bin/review-index +1 -1
  14. data/bin/review-preproc +29 -35
  15. data/bin/review-validate +2 -2
  16. data/doc/config.yml.sample +9 -1
  17. data/doc/config.yml.sample-simple +1 -1
  18. data/doc/format.ja.md +29 -3
  19. data/doc/format.md +32 -3
  20. data/doc/writing_vertical.ja.md +6 -0
  21. data/lib/review/book/base.rb +3 -3
  22. data/lib/review/book/book_unit.rb +13 -3
  23. data/lib/review/book/chapter.rb +1 -1
  24. data/lib/review/book/index.rb +7 -4
  25. data/lib/review/book/part.rb +12 -13
  26. data/lib/review/book/volume.rb +1 -1
  27. data/lib/review/builder.rb +92 -65
  28. data/lib/review/catalog.rb +6 -5
  29. data/lib/review/compiler.rb +76 -57
  30. data/lib/review/configure.rb +5 -2
  31. data/lib/review/epub2html.rb +12 -12
  32. data/lib/review/epubmaker/content.rb +1 -1
  33. data/lib/review/epubmaker/epubcommon.rb +47 -45
  34. data/lib/review/epubmaker/epubv2.rb +2 -1
  35. data/lib/review/epubmaker/epubv3.rb +5 -4
  36. data/lib/review/epubmaker/producer.rb +6 -7
  37. data/lib/review/epubmaker/reviewheaderlistener.rb +1 -1
  38. data/lib/review/epubmaker.rb +56 -67
  39. data/lib/review/exception.rb +7 -0
  40. data/lib/review/extentions/string.rb +1 -1
  41. data/lib/review/htmlbuilder.rb +90 -34
  42. data/lib/review/htmlutils.rb +17 -17
  43. data/lib/review/i18n.rb +3 -3
  44. data/lib/review/i18n.yml +6 -0
  45. data/lib/review/idgxmlbuilder.rb +61 -39
  46. data/lib/review/idgxmlmaker.rb +27 -26
  47. data/lib/review/img_math.rb +12 -18
  48. data/lib/review/index_builder.rb +94 -53
  49. data/lib/review/init.rb +4 -4
  50. data/lib/review/latexbuilder.rb +84 -76
  51. data/lib/review/lineinput.rb +3 -3
  52. data/lib/review/location.rb +1 -1
  53. data/lib/review/loggable.rb +27 -0
  54. data/lib/review/logger.rb +69 -21
  55. data/lib/review/makerhelper.rb +8 -4
  56. data/lib/review/markdownbuilder.rb +21 -12
  57. data/lib/review/pdfmaker.rb +63 -42
  58. data/lib/review/plaintextbuilder.rb +16 -15
  59. data/lib/review/preprocessor/directive.rb +35 -0
  60. data/lib/review/preprocessor/line.rb +34 -0
  61. data/lib/review/preprocessor/repository.rb +177 -0
  62. data/lib/review/preprocessor.rb +94 -296
  63. data/lib/review/rstbuilder.rb +12 -3
  64. data/lib/review/template.rb +5 -1
  65. data/lib/review/textmaker.rb +32 -31
  66. data/lib/review/textutils.rb +5 -6
  67. data/lib/review/tocprinter.rb +12 -7
  68. data/lib/review/topbuilder.rb +96 -19
  69. data/lib/review/update.rb +16 -8
  70. data/lib/review/version.rb +1 -1
  71. data/lib/review/volumeprinter.rb +9 -9
  72. data/lib/review/webmaker.rb +45 -46
  73. data/lib/review/webtocprinter.rb +10 -10
  74. data/lib/review/yamlloader.rb +35 -2
  75. data/review.gemspec +2 -1
  76. data/samples/sample-book/src/config.yml +0 -1
  77. data/samples/sample-book/src/lib/tasks/review.rake +3 -1
  78. data/samples/sample-book/src/lib/tasks/z01_copy_sty.rake +2 -1
  79. data/samples/syntax-book/ch02.re +9 -0
  80. data/samples/syntax-book/lib/tasks/z01_copy_sty.rake +2 -1
  81. data/templates/html/_titlepage.html.erb +9 -17
  82. data/templates/latex/config.erb +3 -0
  83. data/templates/latex/review-jlreq/review-base.sty +4 -5
  84. data/templates/latex/review-jlreq/review-jlreq.cls +39 -5
  85. data/templates/latex/review-jsbook/review-base.sty +9 -3
  86. data/templates/latex/review-jsbook/review-jsbook.cls +32 -5
  87. data/templates/opf/opf_manifest_epubv2.opf.erb +1 -1
  88. data/templates/opf/opf_manifest_epubv3.opf.erb +1 -1
  89. data/test/assets/syntax_book_index_detail.txt +10 -8
  90. data/test/assets/test_template.tex +4 -1
  91. data/test/assets/test_template_backmatter.tex +4 -1
  92. data/test/book_test_helper.rb +10 -10
  93. data/test/test_book_chapter.rb +25 -2
  94. data/test/test_builder.rb +10 -8
  95. data/test/test_epub3maker.rb +3 -3
  96. data/test/test_epubmaker.rb +27 -37
  97. data/test/test_epubmaker_cmd.rb +14 -3
  98. data/test/test_htmlbuilder.rb +111 -31
  99. data/test/test_idgxmlbuilder.rb +41 -33
  100. data/test/test_idgxmlmaker_cmd.rb +1 -1
  101. data/test/test_img_math.rb +11 -2
  102. data/test/test_index.rb +30 -4
  103. data/test/test_latexbuilder.rb +46 -25
  104. data/test/test_latexbuilder_v2.rb +18 -10
  105. data/test/test_markdownbuilder.rb +13 -0
  106. data/test/test_pdfmaker.rb +19 -0
  107. data/test/test_pdfmaker_cmd.rb +10 -10
  108. data/test/test_plaintextbuilder.rb +46 -22
  109. data/test/test_preprocessor.rb +188 -1
  110. data/test/test_rstbuilder.rb +13 -0
  111. data/test/test_textmaker_cmd.rb +1 -1
  112. data/test/test_topbuilder.rb +195 -29
  113. data/test/test_yamlloader.rb +28 -42
  114. metadata +11 -6
@@ -0,0 +1,177 @@
1
+ # Copyright (c) 2010-2021 Minero Aoki, Kenshi Muto
2
+ #
3
+ # This program is free software.
4
+ # You can distribute or modify this program under the terms of
5
+ # the GNU LGPL, Lesser General Public License version 2.1.
6
+ # For details of the GNU LGPL, see the file "COPYING".
7
+ #
8
+
9
+ require 'review/textutils'
10
+ require 'review/loggable'
11
+
12
+ module ReVIEW
13
+ class Preprocessor
14
+ class Repository
15
+ include TextUtils
16
+ include Loggable
17
+
18
+ def initialize(param)
19
+ @repository = {}
20
+ @config = param
21
+ @leave_content = nil
22
+ @logger = ReVIEW.logger
23
+ end
24
+
25
+ def fetch_file(file)
26
+ file_descripter(file)['file']
27
+ end
28
+
29
+ def fetch_range(file, name)
30
+ fetch(file, 'range', name)
31
+ end
32
+
33
+ def fetch(file, type, name)
34
+ table = file_descripter(file)[type] or return nil
35
+ table[name]
36
+ end
37
+
38
+ private
39
+
40
+ def file_descripter(fname)
41
+ @leave_content = File.extname(fname) == '.re'
42
+ return @repository[fname] if @repository[fname]
43
+
44
+ @repository[fname] = git?(fname) ? parse_git_blob(fname) : parse_file(fname)
45
+ end
46
+
47
+ def git?(fname)
48
+ fname.start_with?('git|')
49
+ end
50
+
51
+ def parse_git_blob(g_obj)
52
+ IO.popen('git show ' + g_obj.sub(/\Agit\|/, ''), 'r') do |f|
53
+ @inf = f
54
+ return _parse_file(f)
55
+ end
56
+ end
57
+
58
+ def parse_file(fname)
59
+ File.open(fname, 'rt:BOM|utf-8') do |f|
60
+ @inf = f
61
+ return _parse_file(f)
62
+ end
63
+ end
64
+
65
+ def _parse_file(f)
66
+ whole = []
67
+ repo = { 'file' => whole }
68
+ curr = { 'WHOLE' => whole }
69
+ lineno = 1
70
+ yacchack = false # remove ';'-only lines.
71
+ opened = [['(not opened)', '(not opened)']] * 3
72
+
73
+ f.each do |line|
74
+ begin
75
+ case line
76
+ when /(?:\A\#@|\#@@)([a-z]+)_(begin|end)\((.*)\)/
77
+ type = check_type($1)
78
+ direction = $2
79
+ spec = check_spec($3)
80
+ case direction
81
+ when 'begin'
82
+ key = "#{type}/#{spec}"
83
+ if curr[key]
84
+ app_error "begin x2: #{key}"
85
+ end
86
+ (repo[type] ||= {})[spec] = curr[key] = []
87
+ when 'end'
88
+ curr.delete("#{type}/#{spec}") or
89
+ app_error "end before begin: #{type}/#{spec}"
90
+ else
91
+ app_error 'must not happen'
92
+ end
93
+
94
+ when %r{(?:\A\#@|\#@@)([a-z]+)/(\w+)\{}
95
+ type = check_type($1)
96
+ spec = check_spec($2)
97
+ key = "#{type}/#{spec}"
98
+ if curr[key]
99
+ app_error "begin x2: #{key}"
100
+ end
101
+ (repo[type] ||= {})[spec] = curr[key] = []
102
+ opened.push([type, spec])
103
+
104
+ when %r{(?:\A\#@|\#@@)([a-z]+)/(\w+)\}}
105
+ type = check_type($1)
106
+ spec = check_spec($2)
107
+ curr.delete("#{type}/#{spec}") or
108
+ app_error "end before begin: #{type}/#{spec}"
109
+ opened.delete("#{type}/#{spec}")
110
+
111
+ when /(?:\A\#@|\#@@)\}/
112
+ type, spec = opened.last
113
+ curr.delete("#{type}/#{spec}") or
114
+ app_error "closed before open: #{type}/#{spec}"
115
+ opened.pop
116
+
117
+ when /(?:\A\#@|\#@@)yacchack/
118
+ yacchack = true
119
+
120
+ when /\A\#@-/ # does not increment line number.
121
+ line = canonical($')
122
+ curr.each_value { |list| list.push(Line.new(nil, line)) }
123
+
124
+ else
125
+ next if yacchack && (line.strip == ';')
126
+
127
+ line = canonical(line)
128
+ curr.each_value { |list| list.push(Line.new(lineno, line)) }
129
+ lineno += 1
130
+ end
131
+ rescue ApplicationError => e
132
+ @has_errors = true
133
+ error e.message, location: location
134
+ end
135
+ end
136
+ if curr.size > 1
137
+ curr.delete('WHOLE')
138
+ curr.each { |range, lines| warn "#{@inf.path}: unclosed range: #{range} (begin @#{lines.first.number})" }
139
+ @has_errors = true
140
+ end
141
+
142
+ if @has_errors
143
+ error! 'repository failed.'
144
+ end
145
+
146
+ repo
147
+ end
148
+
149
+ def canonical(line)
150
+ if @leave_content
151
+ return line
152
+ end
153
+
154
+ tabwidth = @config['tabwidth'] || 8
155
+ if tabwidth > 0
156
+ detab(line, tabwidth).rstrip + "\n"
157
+ else
158
+ line
159
+ end
160
+ end
161
+
162
+ def check_type(type)
163
+ app_error "wrong type: #{type.inspect}" unless Preprocessor::TYPES.index(type)
164
+ type
165
+ end
166
+
167
+ def check_spec(spec)
168
+ app_error "wrong spec: #{spec.inspect}" unless /\A\w+\z/.match?(spec)
169
+ spec
170
+ end
171
+
172
+ def location
173
+ "#{@inf.path}:#{@inf.lineno}"
174
+ end
175
+ end
176
+ end
177
+ end
@@ -9,119 +9,109 @@
9
9
 
10
10
  require 'review/textutils'
11
11
  require 'review/exception'
12
- require 'nkf'
12
+ require 'review/preprocessor/directive'
13
+ require 'review/preprocessor/line'
14
+ require 'review/preprocessor/repository'
15
+ require 'review/loggable'
16
+ require 'open3'
13
17
 
14
18
  module ReVIEW
15
- module ErrorUtils
16
- def init_errorutils(f)
17
- @errutils_file = f
18
- @errutils_err = false
19
- end
20
-
21
- def warn(msg)
22
- @logger.warn "#{location}: #{msg}"
23
- end
24
-
25
- def error(msg)
26
- @errutils_err = true
27
- raise ApplicationError, "#{location}: #{msg}"
28
- end
29
-
30
- def location
31
- "#{filename}:#{lineno}"
32
- end
33
-
34
- def filename
35
- @errutils_file.path
36
- end
37
-
38
- def lineno
39
- @errutils_file.lineno
40
- end
41
- end
42
-
43
19
  class Preprocessor
44
- include ErrorUtils
20
+ include Loggable
21
+
22
+ TYPES = %w[file range].freeze
23
+ KNOWN_DIRECTIVES = %w[require provide warn ok].freeze
24
+ INF_INDENT = 9999
45
25
 
46
- def initialize(repo, param)
47
- @repository = repo
48
- @config = param
26
+ def initialize(param)
27
+ @repository = ReVIEW::Preprocessor::Repository.new(param)
28
+ @config = param ## do not use params in this class; only used in Repository
49
29
  @logger = ReVIEW.logger
50
30
  @leave_content = nil
51
31
  end
52
32
 
53
- def process(inf, outf)
54
- init_errorutils(inf)
55
- @f = outf
56
- begin
57
- preproc(inf)
58
- rescue Errno::ENOENT => e
59
- error e.message
33
+ def process(path)
34
+ File.open(path) do |inf|
35
+ @inf = inf
36
+ @f = StringIO.new
37
+ begin
38
+ preproc(@inf)
39
+ rescue Errno::ENOENT => e
40
+ error! e.message
41
+ end
42
+ @f.string
60
43
  end
61
44
  end
62
45
 
63
46
  private
64
47
 
65
- TYPES = %w[file range].freeze
66
-
67
48
  def preproc(f)
68
- init_vars
69
- f.each_line do |line|
70
- case line
71
- when /\A\#@\#/, /\A\#\#\#\#/
72
- @f.print line
73
-
74
- when /\A\#@defvar/
75
- @f.print line
76
- direc = parse_directive(line, 2)
77
- defvar(*direc.args)
49
+ @vartable = {}
50
+ @has_errors = false
78
51
 
79
- when /\A\#@mapoutput/
80
- direc = parse_directive(line, 1, 'stderr')
81
- @f.print line
82
- get_output(expand(direc.arg), direc['stderr']).each { |out| @f.print out.string }
83
- skip_list(f)
84
-
85
- when /\A\#@mapfile/
86
- direc = parse_directive(line, 1, 'eval')
87
- path = expand(direc.arg)
88
- @leave_content = File.extname(path) == '.re'
89
- if direc['eval']
90
- ent = evaluate(path, ent)
91
- else
92
- ent = @repository.fetch_file(path)
93
- end
94
- replace_block(f, line, ent, false) # FIXME: turn off lineno: tmp
52
+ f.each_line do |line|
53
+ begin
54
+ case line
55
+ when /\A\#@\#/, /\A\#\#\#\#/
56
+ @f.print line
95
57
 
96
- when /\A\#@map(?:range)?/
97
- direc = parse_directive(line, 2, 'unindent')
98
- path = expand(direc.args[0])
99
- @leave_content = File.extname(path) == '.re'
100
- ent = @repository.fetch_range(path, direc.args[1]) or
101
- error "unknown range: #{path}: #{direc.args[1]}"
102
- ent = (direc['unindent'] ? unindent(ent, direc['unindent']) : ent)
103
- replace_block(f, line, ent, false) # FIXME: turn off lineno: tmp
58
+ when /\A\#@defvar/
59
+ @f.print line
60
+ direc = parse_directive(line, 2)
61
+ defvar(*direc.args)
104
62
 
105
- when /\A\#@end/
106
- error 'unbaranced #@end'
63
+ when /\A\#@mapoutput/
64
+ direc = parse_directive(line, 1, 'stderr')
65
+ @f.print line
66
+ get_output(expand(direc.arg), direc['stderr']).each { |out| @f.print out.string }
67
+ skip_list(f)
68
+
69
+ when /\A\#@mapfile/
70
+ direc = parse_directive(line, 1, 'eval')
71
+ path = expand(direc.arg)
72
+ @leave_content = File.extname(path) == '.re'
73
+ ent = if direc['eval']
74
+ evaluate(path, ent)
75
+ else
76
+ @repository.fetch_file(path)
77
+ end
78
+ replace_block(f, line, ent, false) # FIXME: turn off lineno: tmp
79
+
80
+ when /\A\#@map(?:range)?/
81
+ direc = parse_directive(line, 2, 'unindent')
82
+ path = expand(direc.args[0])
83
+ @leave_content = File.extname(path) == '.re'
84
+ ent = @repository.fetch_range(path, direc.args[1]) or
85
+ app_error "unknown range: #{path}: #{direc.args[1]}"
86
+ ent = (direc['unindent'] ? unindent(ent, direc['unindent']) : ent)
87
+ replace_block(f, line, ent, false) # FIXME: turn off lineno: tmp
88
+
89
+ when /\A\#@end/
90
+ app_error 'unbaranced #@end'
91
+
92
+ when /\A\#@/
93
+ op = line.slice(/@(\w+)/, 1)
94
+ warn "unknown directive: #{line.strip}", location: location unless known_directive?(op)
95
+ if op == 'warn'
96
+ warn line.strip.sub(/\#@warn\((.+)\)/, '\1'), location: location
97
+ end
98
+ @f.print line
107
99
 
108
- when /\A\#@/
109
- op = line.slice(/@(\w+)/, 1)
110
- warn "unknown directive: #{line.strip}" unless known_directive?(op)
111
- if op == 'warn'
112
- warn line.strip.sub(/\#@warn\((.+)\)/, '\1')
100
+ when /\A\s*\z/ # empty line
101
+ @f.puts
102
+ else # rubocop:disable Lint/DuplicateBranch
103
+ @f.print line
113
104
  end
114
- @f.print line
115
-
116
- when /\A\s*\z/ # empty line
117
- @f.puts
118
- else # rubocop:disable Lint/DuplicateBranch
119
- @f.print line
105
+ rescue ApplicationError => e
106
+ @has_errors = true
107
+ error e.message, location: location
120
108
  end
121
109
  end
122
- end
123
110
 
124
- KNOWN_DIRECTIVES = %w[require provide warn ok].freeze
111
+ if @has_erros
112
+ error! 'preprocessor failed.'
113
+ end
114
+ end
125
115
 
126
116
  def known_directive?(op)
127
117
  KNOWN_DIRECTIVES.index(op)
@@ -151,47 +141,23 @@ module ReVIEW
151
141
  return nil
152
142
  when %r{\A//\}}
153
143
  unless @leave_content
154
- warn '//} seen in list'
144
+ warn '//} seen in list', location: location
155
145
  @f.print line
156
146
  return nil
157
147
  end
158
148
  when /\A\#@\w/
159
- warn "#{line.slice(/\A\#@\w+/)} seen in list"
149
+ warn "#{line.slice(/\A\#@\w+/)} seen in list", location: location
160
150
  @f.print line
161
151
  when /\A\#@/
162
152
  @f.print line
163
153
  end
164
154
  end
165
- error "list reached end of file (beginning line = #{begline})"
166
- end
167
-
168
- class Directive
169
- def initialize(op, args, opts)
170
- @op = op
171
- @args = args
172
- @opts = opts
173
- end
174
-
175
- attr_reader :op
176
- attr_reader :args
177
- attr_reader :opts
178
-
179
- def arg
180
- @args.first
181
- end
182
-
183
- def opt
184
- @opts.first
185
- end
186
-
187
- def [](key)
188
- @opts[key]
189
- end
155
+ app_error "list reached end of file (beginning line = #{begline})"
190
156
  end
191
157
 
192
158
  def parse_directive(line, argc, *optdecl)
193
159
  m = /\A\#@(\w+)\((.*?)\)(?:\[(.*?)\])?\z/.match(line.strip) or
194
- error "wrong directive: #{line.strip}"
160
+ app_error "wrong directive: #{line.strip}"
195
161
  op = m[1]
196
162
  args = m[2].split(/,\s*/)
197
163
  opts = parse_optargs(m[3])
@@ -200,12 +166,12 @@ module ReVIEW
200
166
  if argc == -1
201
167
  # Any number of arguments are allowed.
202
168
  elsif args.size != argc
203
- error 'wrong arg size'
169
+ app_error 'wrong arg size'
204
170
  end
205
171
  if opts
206
172
  wrong_opts = opts.keys - optdecl
207
173
  unless wrong_opts.empty?
208
- error "wrong option: #{wrong_opts.keys.join(' ')}"
174
+ app_error "wrong option: #{wrong_opts.keys.join(' ')}"
209
175
  end
210
176
  end
211
177
  Directive.new(op, args, opts || {})
@@ -237,10 +203,6 @@ module ReVIEW
237
203
  end
238
204
  end
239
205
 
240
- def init_vars
241
- @vartable = {}
242
- end
243
-
244
206
  def defvar(name, value)
245
207
  @vartable[name] = value
246
208
  end
@@ -258,8 +220,6 @@ module ReVIEW
258
220
  chunk.map { |line| line.edit { |s| s.sub(re, '') } }
259
221
  end
260
222
 
261
- INF_INDENT = 9999
262
-
263
223
  def minimum_indent(chunk)
264
224
  n = chunk.map { |line| line.empty? ? INF_INDENT : line.num_indent }.min
265
225
  n == INF_INDENT ? 0 : n
@@ -277,8 +237,6 @@ module ReVIEW
277
237
  end
278
238
  end
279
239
 
280
- require 'open3'
281
-
282
240
  def get_output(cmd, use_stderr)
283
241
  out = err = nil
284
242
  Open3.popen3(cmd) do |_stdin, stdout, stderr|
@@ -292,182 +250,22 @@ module ReVIEW
292
250
  if err && !err.empty?
293
251
  $stderr.puts '[unexpected stderr message]'
294
252
  err.each { |line| $stderr.print line }
295
- error 'get_output: got unexpected output'
253
+ app_error 'get_output: got unexpected output'
296
254
  end
297
255
  num = 0
298
256
  out.map { |line| Line.new(num += 1, line) }
299
257
  end
300
- end
301
-
302
- class Line
303
- def initialize(number, string)
304
- @number = number
305
- @string = string
306
- end
307
-
308
- attr_reader :number
309
- attr_reader :string
310
- alias_method :to_s, :string
311
-
312
- def edit
313
- self.class.new(@number, yield(@string))
314
- end
315
-
316
- def empty?
317
- @string.strip.empty?
318
- end
319
-
320
- def num_indent
321
- @string.slice(/\A\s*/).size
322
- end
323
- end
324
-
325
- class Repository
326
- include TextUtils
327
- include ErrorUtils
328
-
329
- def initialize(param)
330
- @repository = {}
331
- @config = param
332
- @logger = ReVIEW.logger
333
- end
334
-
335
- def fetch_file(file)
336
- file_descripter(file)['file']
337
- end
338
-
339
- def fetch_range(file, name)
340
- fetch(file, 'range', name)
341
- end
342
-
343
- def fetch(file, type, name)
344
- table = file_descripter(file)[type] or return nil
345
- table[name]
346
- end
347
258
 
348
- private
349
-
350
- def file_descripter(fname)
351
- @leave_content = File.extname(fname) == '.re'
352
- return @repository[fname] if @repository[fname]
353
-
354
- @repository[fname] = git?(fname) ? parse_git_blob(fname) : parse_file(fname)
355
- end
356
-
357
- def git?(fname)
358
- fname.start_with?('git|')
359
- end
360
-
361
- def parse_git_blob(g_obj)
362
- IO.popen('git show ' + g_obj.sub(/\Agit\|/, ''), 'r') do |f|
363
- init_errorutils(f)
364
- return _parse_file(f)
365
- end
366
- end
367
-
368
- def parse_file(fname)
369
- File.open(fname, 'rt:BOM|utf-8') do |f|
370
- init_errorutils(f)
371
- return _parse_file(f)
372
- end
373
- end
374
-
375
- def _parse_file(f)
376
- whole = []
377
- repo = { 'file' => whole }
378
- curr = { 'WHOLE' => whole }
379
- lineno = 1
380
- yacchack = false # remove ';'-only lines.
381
- opened = [['(not opened)', '(not opened)']] * 3
382
-
383
- f.each do |line|
384
- case line
385
- when /(?:\A\#@|\#@@)([a-z]+)_(begin|end)\((.*)\)/
386
- type = check_type($1)
387
- direction = $2
388
- spec = check_spec($3)
389
- case direction
390
- when 'begin'
391
- key = "#{type}/#{spec}"
392
- if curr[key]
393
- error "begin x2: #{key}"
394
- end
395
- (repo[type] ||= {})[spec] = curr[key] = []
396
- when 'end'
397
- curr.delete("#{type}/#{spec}") or
398
- error "end before begin: #{type}/#{spec}"
399
- else
400
- raise 'must not happen'
401
- end
402
-
403
- when %r{(?:\A\#@|\#@@)([a-z]+)/(\w+)\{}
404
- type = check_type($1)
405
- spec = check_spec($2)
406
- key = "#{type}/#{spec}"
407
- if curr[key]
408
- error "begin x2: #{key}"
409
- end
410
- (repo[type] ||= {})[spec] = curr[key] = []
411
- opened.push([type, spec])
412
-
413
- when %r{(?:\A\#@|\#@@)([a-z]+)/(\w+)\}}
414
- type = check_type($1)
415
- spec = check_spec($2)
416
- curr.delete("#{type}/#{spec}") or
417
- error "end before begin: #{type}/#{spec}"
418
- opened.delete("#{type}/#{spec}")
419
-
420
- when /(?:\A\#@|\#@@)\}/
421
- type, spec = opened.last
422
- curr.delete("#{type}/#{spec}") or
423
- error "closed before open: #{type}/#{spec}"
424
- opened.pop
425
-
426
- when /(?:\A\#@|\#@@)yacchack/
427
- yacchack = true
428
-
429
- when /\A\#@-/ # does not increment line number.
430
- line = canonical($')
431
- curr.each_value { |list| list.push(Line.new(nil, line)) }
432
-
433
- else
434
- next if yacchack && (line.strip == ';')
435
-
436
- line = canonical(line)
437
- curr.each_value { |list| list.push(Line.new(lineno, line)) }
438
- lineno += 1
439
- end
440
- end
441
- if curr.size > 1
442
- curr.delete('WHOLE')
443
- curr.each { |range, lines| @logger.warn "#{filename}: unclosed range: #{range} (begin @#{lines.first.number})" }
444
- raise ApplicationError, 'ERROR'
445
- end
446
-
447
- repo
448
- end
449
-
450
- def canonical(line)
451
- if @leave_content
452
- return line
453
- end
454
-
455
- tabwidth = @config['tabwidth'] || 8
456
- if tabwidth > 0
457
- detab(line, tabwidth).rstrip + "\n"
458
- else
459
- line
460
- end
259
+ def location
260
+ "#{filename}:#{lineno}"
461
261
  end
462
262
 
463
- def check_type(type)
464
- error "wrong type: #{type.inspect}" unless Preprocessor::TYPES.index(type)
465
- type
263
+ def filename
264
+ @inf.path
466
265
  end
467
266
 
468
- def check_spec(spec)
469
- error "wrong spec: #{spec.inspect}" unless /\A\w+\z/ =~ spec
470
- spec
267
+ def lineno
268
+ @inf.lineno
471
269
  end
472
270
  end
473
271
  end