review 5.1.1 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby-tex.yml +5 -1
  3. data/.github/workflows/ruby-win.yml +5 -1
  4. data/.github/workflows/ruby.yml +5 -1
  5. data/.rubocop.yml +6 -2
  6. data/NEWS.ja.md +48 -0
  7. data/NEWS.md +48 -1
  8. data/bin/review-compile +8 -15
  9. data/bin/review-preproc +28 -34
  10. data/doc/config.yml.sample +2 -0
  11. data/doc/writing_vertical.ja.md +6 -0
  12. data/lib/review/builder.rb +34 -40
  13. data/lib/review/compiler.rb +59 -43
  14. data/lib/review/epubmaker.rb +24 -38
  15. data/lib/review/epubmaker/producer.rb +3 -4
  16. data/lib/review/exception.rb +7 -0
  17. data/lib/review/htmlbuilder.rb +51 -20
  18. data/lib/review/idgxmlbuilder.rb +19 -18
  19. data/lib/review/idgxmlmaker.rb +12 -13
  20. data/lib/review/img_math.rb +11 -18
  21. data/lib/review/index_builder.rb +6 -15
  22. data/lib/review/latexbuilder.rb +38 -43
  23. data/lib/review/loggable.rb +27 -0
  24. data/lib/review/logger.rb +48 -0
  25. data/lib/review/makerhelper.rb +5 -1
  26. data/lib/review/markdownbuilder.rb +5 -4
  27. data/lib/review/pdfmaker.rb +23 -21
  28. data/lib/review/plaintextbuilder.rb +8 -8
  29. data/lib/review/preprocessor.rb +94 -296
  30. data/lib/review/preprocessor/directive.rb +35 -0
  31. data/lib/review/preprocessor/line.rb +34 -0
  32. data/lib/review/preprocessor/repository.rb +177 -0
  33. data/lib/review/rstbuilder.rb +1 -1
  34. data/lib/review/template.rb +5 -1
  35. data/lib/review/textmaker.rb +12 -13
  36. data/lib/review/tocprinter.rb +1 -1
  37. data/lib/review/topbuilder.rb +7 -7
  38. data/lib/review/version.rb +1 -1
  39. data/lib/review/webmaker.rb +13 -14
  40. data/review.gemspec +1 -1
  41. data/samples/sample-book/src/lib/tasks/review.rake +3 -1
  42. data/samples/sample-book/src/lib/tasks/z01_copy_sty.rake +2 -1
  43. data/samples/syntax-book/lib/tasks/z01_copy_sty.rake +2 -1
  44. data/templates/latex/review-jlreq/review-base.sty +3 -5
  45. data/templates/latex/review-jlreq/review-jlreq.cls +4 -3
  46. data/templates/latex/review-jsbook/review-base.sty +3 -3
  47. data/templates/latex/review-jsbook/review-jsbook.cls +1 -1
  48. data/test/test_builder.rb +8 -8
  49. data/test/test_epubmaker.rb +13 -8
  50. data/test/test_epubmaker_cmd.rb +13 -2
  51. data/test/test_htmlbuilder.rb +68 -26
  52. data/test/test_idgxmlbuilder.rb +31 -23
  53. data/test/test_latexbuilder.rb +30 -22
  54. data/test/test_latexbuilder_v2.rb +18 -10
  55. data/test/test_plaintextbuilder.rb +30 -22
  56. data/test/test_preprocessor.rb +188 -1
  57. data/test/test_topbuilder.rb +26 -18
  58. metadata +12 -8
@@ -61,7 +61,11 @@ module ReVIEW
61
61
  exts = options[:exts] || %w[png gif jpg jpeg svg pdf eps ai tif psd]
62
62
  exts_str = exts.join('|')
63
63
  if !is_converted && fname =~ /\.(#{exts_str})$/i
64
- FileUtils.cp("#{from_dir}/#{fname}", to_dir)
64
+ if options[:use_symlink]
65
+ FileUtils.ln_s(File.realpath("#{from_dir}/#{fname}"), to_dir)
66
+ else
67
+ FileUtils.cp("#{from_dir}/#{fname}", to_dir)
68
+ end
65
69
  image_files << "#{from_dir}/#{fname}"
66
70
  end
67
71
  end
@@ -18,6 +18,7 @@ module ReVIEW
18
18
  end
19
19
 
20
20
  def builder_init_file
21
+ super
21
22
  @noindent = nil
22
23
  @blank_seen = nil
23
24
  @ul_indent = 0
@@ -245,7 +246,7 @@ module ReVIEW
245
246
  def inline_img(id)
246
247
  "#{I18n.t('image')}#{@chapter.image(id).number}"
247
248
  rescue KeyError
248
- error "unknown image: #{id}"
249
+ app_error "unknown image: #{id}"
249
250
  end
250
251
 
251
252
  def inline_dtp(str)
@@ -264,14 +265,14 @@ module ReVIEW
264
265
  anchor = 'h' + n.tr('.', '-')
265
266
  %Q(<a href="##{anchor}">#{str}</a>)
266
267
  else
267
- warn 'MARKDOWNBuilder does not support links to other chapters'
268
+ warn 'MARKDOWNBuilder does not support links to other chapters', location: location
268
269
  str
269
270
  end
270
271
  else
271
272
  str
272
273
  end
273
274
  rescue KeyError
274
- error "unknown headline: #{id}"
275
+ app_error "unknown headline: #{id}"
275
276
  end
276
277
 
277
278
  def indepimage(_lines, id, caption = '', _metric = nil)
@@ -401,7 +402,7 @@ module ReVIEW
401
402
  begin
402
403
  "![](#{@chapter.image(id).path.sub(%r{\A\./}, '')})"
403
404
  rescue
404
- warn "image not bound: #{id}"
405
+ warn "image not bound: #{id}", location: location
405
406
  %Q(<pre>missing image: #{id}</pre>)
406
407
  end
407
408
  end
@@ -24,11 +24,12 @@ require 'review/makerhelper'
24
24
  require 'review/template'
25
25
  require 'review/latexbox'
26
26
  require 'review/call_hook'
27
+ require 'review/loggable'
27
28
 
28
29
  module ReVIEW
29
30
  class PDFMaker
30
- include FileUtils
31
31
  include ReVIEW::LaTeXUtils
32
+ include Loggable
32
33
  include ReVIEW::CallHook
33
34
 
34
35
  attr_accessor :config, :basedir
@@ -38,13 +39,14 @@ module ReVIEW
38
39
  @logger = ReVIEW.logger
39
40
  @input_files = Hash.new { |h, key| h[key] = '' }
40
41
  @mastertex = '__REVIEW_BOOK__'
42
+ @compile_errors = nil
41
43
  end
42
44
 
43
45
  def system_with_info(*args)
44
46
  @logger.info args.join(' ')
45
47
  out, status = Open3.capture2e(*args)
46
48
  unless status.success?
47
- @logger.error "execution error\n\nError log:\n" + out
49
+ error "execution error\n\nError log:\n#{out}"
48
50
  end
49
51
  end
50
52
 
@@ -52,19 +54,10 @@ module ReVIEW
52
54
  @logger.info args.join(' ')
53
55
  out, status = Open3.capture2e(*args)
54
56
  unless status.success?
55
- error "failed to run command: #{args.join(' ')}\n\nError log:\n" + out
57
+ error! "failed to run command: #{args.join(' ')}\n\nError log:\n#{out}"
56
58
  end
57
59
  end
58
60
 
59
- def error(msg)
60
- @logger.error msg
61
- exit 1
62
- end
63
-
64
- def warn(msg)
65
- @logger.warn msg
66
- end
67
-
68
61
  def pdf_filepath
69
62
  File.join(@basedir, @config['bookname'] + '.pdf')
70
63
  end
@@ -92,7 +85,7 @@ module ReVIEW
92
85
  if ignore_errors
93
86
  @logger.info 'compile error, but try to generate PDF file'
94
87
  else
95
- error 'compile error, No PDF file output.'
88
+ error! 'compile error, No PDF file output.'
96
89
  end
97
90
  end
98
91
 
@@ -126,7 +119,7 @@ module ReVIEW
126
119
 
127
120
  def execute(*args)
128
121
  cmd_config, yamlfile = parse_opts(args)
129
- error "#{yamlfile} not found." unless File.exist?(yamlfile)
122
+ error! "#{yamlfile} not found." unless File.exist?(yamlfile)
130
123
 
131
124
  @config = ReVIEW::Configure.create(maker: 'pdfmaker',
132
125
  yamlfile: yamlfile,
@@ -155,7 +148,7 @@ module ReVIEW
155
148
  rescue ApplicationError => e
156
149
  raise if @config['debug']
157
150
 
158
- error(e.message)
151
+ error! e.message
159
152
  end
160
153
  end
161
154
 
@@ -217,7 +210,7 @@ module ReVIEW
217
210
  makeindex_dic = ReVIEW::Configure.values['pdfmaker']['makeindex_dic']
218
211
  else
219
212
  unless @config['texcommand'].present?
220
- error "texcommand isn't defined."
213
+ error! "texcommand isn't defined."
221
214
  end
222
215
  texcommand = @config['texcommand']
223
216
  dvicommand = @config['dvicommand']
@@ -280,6 +273,9 @@ module ReVIEW
280
273
  @config['usepackage'] = ''
281
274
  @config['usepackage'] = "\\usepackage{#{@config['texstyle']}}" if @config['texstyle']
282
275
 
276
+ if @config['pdfmaker']['use_symlink']
277
+ logger.info 'use symlink'
278
+ end
283
279
  copy_images(@config['imagedir'], File.join(@path, @config['imagedir']))
284
280
  copy_sty(File.join(Dir.pwd, 'sty'), @path)
285
281
  copy_sty(File.join(Dir.pwd, 'sty'), @path, 'fd')
@@ -293,7 +289,7 @@ module ReVIEW
293
289
  FileUtils.cp(File.join(@path, "#{@mastertex}.pdf"), pdf_filepath)
294
290
  @logger.success("built #{File.basename(pdf_filepath)}")
295
291
  ensure
296
- remove_entry_secure(@path) unless @config['debug']
292
+ FileUtils.remove_entry_secure(@path) unless @config['debug']
297
293
  end
298
294
  end
299
295
 
@@ -303,8 +299,8 @@ module ReVIEW
303
299
  @converter.convert(filename + '.re', File.join(@path, filename + '.tex'))
304
300
  rescue => e
305
301
  @compile_errors = true
306
- warn "compile error in #{filename}.tex (#{e.class})"
307
- warn e.message
302
+ error "compile error in #{filename}.tex (#{e.class})"
303
+ error e.message
308
304
  end
309
305
  end
310
306
 
@@ -314,7 +310,11 @@ module ReVIEW
314
310
  return unless File.exist?(from)
315
311
 
316
312
  Dir.mkdir(to)
317
- ReVIEW::MakerHelper.copy_images_to_dir(from, to)
313
+ if @config['pdfmaker']['use_symlink']
314
+ ReVIEW::MakerHelper.copy_images_to_dir(from, to, use_symlink: true)
315
+ else
316
+ ReVIEW::MakerHelper.copy_images_to_dir(from, to)
317
+ end
318
318
  end
319
319
 
320
320
  def make_custom_page(file)
@@ -457,7 +457,7 @@ module ReVIEW
457
457
  begin
458
458
  @boxsetting = ReVIEW::LaTeXBox.new.tcbox(@config)
459
459
  rescue ReVIEW::ConfigError => e
460
- error e
460
+ error! e
461
461
  end
462
462
  end
463
463
  end
@@ -503,6 +503,8 @@ module ReVIEW
503
503
  File.open(File.join(copybase, fname.sub(/\.erb\Z/, '')), 'w') do |f|
504
504
  f.print erb_content(File.join(dirname, fname))
505
505
  end
506
+ elsif @config['pdfmaker']['use_symlink']
507
+ FileUtils.ln_s(File.join(dirname, fname), copybase)
506
508
  else
507
509
  FileUtils.cp(File.join(dirname, fname), copybase)
508
510
  end
@@ -42,12 +42,12 @@ module ReVIEW
42
42
  end
43
43
 
44
44
  def builder_init_file
45
+ super
45
46
  @section = 0
46
47
  @subsection = 0
47
48
  @subsubsection = 0
48
49
  @subsubsubsection = 0
49
50
  @blank_seen = true
50
- @sec_counter = SecCounter.new(5, @chapter)
51
51
  end
52
52
  private :builder_init_file
53
53
 
@@ -169,7 +169,7 @@ module ReVIEW
169
169
  list_header(id, caption, lang)
170
170
  end
171
171
  rescue KeyError
172
- error "no such list: #{id}"
172
+ app_error "no such list: #{id}"
173
173
  end
174
174
  blank
175
175
  end
@@ -238,7 +238,7 @@ module ReVIEW
238
238
  list_header(id, caption, lang)
239
239
  end
240
240
  rescue KeyError
241
- error "no such list: #{id}"
241
+ app_error "no such list: #{id}"
242
242
  end
243
243
  blank
244
244
  end
@@ -337,7 +337,7 @@ module ReVIEW
337
337
  def inline_fn(id)
338
338
  " 注#{@chapter.footnote(id).number} "
339
339
  rescue KeyError
340
- error "unknown footnote: #{id}"
340
+ app_error "unknown footnote: #{id}"
341
341
  end
342
342
 
343
343
  def compile_ruby(base, _ruby)
@@ -401,7 +401,7 @@ module ReVIEW
401
401
  def inline_bib(id)
402
402
  %Q(#{@chapter.bibpaper(id).number} )
403
403
  rescue KeyError
404
- error "unknown bib: #{id}"
404
+ app_error "unknown bib: #{id}"
405
405
  end
406
406
 
407
407
  def inline_hd_chap(chap, id)
@@ -412,7 +412,7 @@ module ReVIEW
412
412
  I18n.t('hd_quote_without_number', compile_inline(chap.headline(id).caption))
413
413
  end
414
414
  rescue KeyError
415
- error "unknown headline: #{id}"
415
+ app_error "unknown headline: #{id}"
416
416
  end
417
417
 
418
418
  def noindent
@@ -680,7 +680,7 @@ module ReVIEW
680
680
  if @book.config['chapref']
681
681
  chs2 = @book.config['chapref'].split(',')
682
682
  if chs2.size != 3
683
- error '--chapsplitter must have exactly 3 parameters with comma.'
683
+ app_error '--chapsplitter must have exactly 3 parameters with comma.'
684
684
  end
685
685
  chs = chs2
686
686
  end
@@ -694,7 +694,7 @@ module ReVIEW
694
694
  end
695
695
  end
696
696
  rescue KeyError
697
- error "unknown chapter: #{id}"
697
+ app_error "unknown chapter: #{id}"
698
698
  end
699
699
 
700
700
  def source(lines, caption = nil, _lang = nil)
@@ -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
+ if direc['eval']
74
+ ent = evaluate(path, ent)
75
+ else
76
+ ent = @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