review 2.1.0 → 2.2.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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +293 -6
  4. data/.rubocop_todo.yml +3 -608
  5. data/.travis.yml +6 -13
  6. data/README.md +5 -3
  7. data/Rakefile +6 -6
  8. data/bin/review-catalog-converter +2 -2
  9. data/bin/review-check +1 -1
  10. data/bin/review-compile +1 -2
  11. data/bin/review-init +6 -3
  12. data/bin/review-validate +3 -3
  13. data/bin/review-vol +2 -1
  14. data/doc/NEWS.ja.md +138 -25
  15. data/doc/NEWS.md +137 -25
  16. data/doc/config.yml.sample +2 -2
  17. data/doc/config.yml.sample-simple +1 -1
  18. data/doc/format.ja.md +86 -5
  19. data/doc/format.md +67 -2
  20. data/doc/makeindex.ja.md +95 -0
  21. data/doc/makeindex.md +97 -0
  22. data/doc/sample.css +214 -0
  23. data/lib/epubmaker.rb +6 -6
  24. data/lib/epubmaker/epubcommon.rb +19 -47
  25. data/lib/epubmaker/epubv2.rb +3 -1
  26. data/lib/epubmaker/epubv3.rb +4 -26
  27. data/lib/epubmaker/producer.rb +46 -46
  28. data/lib/epubmaker/zip_exporter.rb +86 -0
  29. data/lib/review/book/base.rb +13 -15
  30. data/lib/review/book/chapter.rb +2 -1
  31. data/lib/review/book/compilable.rb +9 -9
  32. data/lib/review/book/image_finder.rb +13 -13
  33. data/lib/review/book/index.rb +2 -2
  34. data/lib/review/book/volume.rb +2 -2
  35. data/lib/review/builder.rb +57 -1
  36. data/lib/review/catalog.rb +2 -2
  37. data/lib/review/compiler.rb +15 -7
  38. data/lib/review/configure.rb +11 -0
  39. data/lib/review/epubmaker.rb +403 -401
  40. data/lib/review/ewbbuilder.rb +16 -16
  41. data/lib/review/htmlbuilder.rb +42 -58
  42. data/lib/review/htmltoc.rb +1 -1
  43. data/lib/review/htmlutils.rb +50 -4
  44. data/lib/review/i18n.rb +2 -2
  45. data/lib/review/idgxmlbuilder.rb +30 -47
  46. data/lib/review/latexbuilder.rb +86 -41
  47. data/lib/review/latexutils.rb +19 -19
  48. data/lib/review/markdownbuilder.rb +16 -4
  49. data/lib/review/md2inaobuilder.rb +0 -9
  50. data/lib/review/pdfmaker.rb +91 -48
  51. data/lib/review/preprocessor.rb +1 -1
  52. data/lib/review/rstbuilder.rb +763 -0
  53. data/lib/review/sec_counter.rb +7 -9
  54. data/lib/review/tocparser.rb +3 -3
  55. data/lib/review/tocprinter.rb +5 -5
  56. data/lib/review/topbuilder.rb +48 -56
  57. data/lib/review/version.rb +1 -1
  58. data/lib/review/webmaker.rb +6 -7
  59. data/review.gemspec +1 -0
  60. data/templates/latex/layout.tex.erb +27 -2
  61. data/test/assets/test_template.tex +10 -1
  62. data/test/book_test_helper.rb +1 -2
  63. data/test/run_test.rb +10 -0
  64. data/test/sample-book/src/style.css +215 -0
  65. data/test/sample-book/src/vendor/jumoline/lppl.txt +416 -0
  66. data/test/test_book.rb +0 -1
  67. data/test/test_catalog.rb +1 -0
  68. data/test/test_converter.rb +1 -1
  69. data/test/test_epub3maker.rb +44 -51
  70. data/test/test_epubmaker.rb +82 -38
  71. data/test/test_epubmaker_cmd.rb +1 -1
  72. data/test/test_extentions_hash.rb +8 -1
  73. data/test/test_htmlbuilder.rb +411 -18
  74. data/test/test_i18n.rb +17 -0
  75. data/test/test_idgxmlbuilder.rb +88 -3
  76. data/test/test_image_finder.rb +18 -0
  77. data/test/test_index.rb +2 -0
  78. data/test/test_latexbuilder.rb +96 -8
  79. data/test/test_makerhelper.rb +2 -2
  80. data/test/test_markdownbuilder.rb +22 -1
  81. data/test/test_md2inaobuilder.rb +0 -5
  82. data/test/test_pdfmaker.rb +54 -36
  83. data/test/test_pdfmaker_cmd.rb +1 -1
  84. data/test/test_rstbuilder.rb +356 -0
  85. data/test/test_textutils.rb +14 -4
  86. data/test/test_topbuilder.rb +23 -4
  87. data/test/test_zip_exporter.rb +113 -0
  88. metadata +28 -2
@@ -0,0 +1,86 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Copyright (c) 2010-2016 Kenshi Muto and Masayoshi Takahashi
4
+ #
5
+ # This program is free software.
6
+ # You can distribute or modify this program under the terms of
7
+ # the GNU LGPL, Lesser General Public License version 2.1.
8
+ # For details of the GNU LGPL, see the file "COPYING".
9
+ #
10
+
11
+ require 'pathname'
12
+ begin
13
+ require 'zip'
14
+ rescue LoadError
15
+ ## I cannot find rubyzip library, so I use external zip command.
16
+ warn "rubyzip not found, so use external zip command"
17
+ end
18
+
19
+ module EPUBMaker
20
+
21
+ ##
22
+ # Export into zip file for EPUB producer.
23
+ #
24
+ class ZipExporter
25
+
26
+ attr_reader :tmpdir
27
+
28
+ def initialize(tmpdir, params)
29
+ @tmpdir = tmpdir
30
+ @params = params
31
+ end
32
+
33
+ def export_zip(epubfile)
34
+ if defined?(Zip)
35
+ export_zip_rubyzip(epubfile)
36
+ else
37
+ export_zip_extcmd(epubfile)
38
+ end
39
+ end
40
+
41
+ def export_zip_extcmd(epubfile)
42
+ stage1 = @params["epubmaker"]["zip_stage1"].to_s.split
43
+ path1 = stage1[0] || "zip"
44
+ opt1 = stage1[1] || "-0Xq"
45
+ stage2 = @params["epubmaker"]["zip_stage2"].to_s.split
46
+ path2 = stage2[0] || "zip"
47
+ opt2 = stage2[1] || "-Xr9Dq"
48
+
49
+ Dir.chdir(tmpdir) do |d|
50
+ system(path1, opt1, epubfile, "mimetype")
51
+ addpath = @params["epubmaker"]["zip_addpath"]
52
+ if addpath
53
+ system(path2, opt2, epubfile, "META-INF", "OEBPS", addpath)
54
+ else
55
+ system(path2, opt2, epubfile, "META-INF", "OEBPS")
56
+ end
57
+ end
58
+ end
59
+
60
+ def export_zip_rubyzip(epubfile)
61
+ Dir.chdir(tmpdir) do |d|
62
+ Zip::OutputStream.open(epubfile) do |epub|
63
+ root_pathname = Pathname.new(tmpdir)
64
+ # relpath = Pathname.new(File.join(tmpdir,'mimetype')).relative_path_from(root_pathname)
65
+ epub.put_next_entry('mimetype', nil, nil, Zip::Entry::STORED)
66
+ epub << "application/epub+zip"
67
+
68
+ export_zip_rubyzip_addpath(epub, File.join(tmpdir,'META-INF'), root_pathname)
69
+ export_zip_rubyzip_addpath(epub, File.join(tmpdir,'OEBPS'), root_pathname)
70
+ if @params["zip_addpath"].present?
71
+ export_zip_rubyzip_addpath(epub, File.join(tmpdir, @params["zip_addpath"]), root_pathname)
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ def export_zip_rubyzip_addpath(epub, dirname, rootdir)
78
+ Dir[File.join(dirname,'**','**')].each do |path|
79
+ next if File.directory?(path)
80
+ relpath = Pathname.new(path).relative_path_from(rootdir)
81
+ epub.put_next_entry(relpath)
82
+ epub << File.binread(path)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -15,6 +15,9 @@ module ReVIEW
15
15
  class Base
16
16
 
17
17
  attr_writer :config
18
+ attr_writer :parts
19
+ attr_writer :catalog
20
+ attr_reader :basedir
18
21
 
19
22
  def self.load_default
20
23
  warn 'Book::Base.load_default() is obsoleted. Use Book::Base.load().'
@@ -51,6 +54,7 @@ module ReVIEW
51
54
  @config = ReVIEW::Configure.values
52
55
  @catalog = nil
53
56
  @read_part = nil
57
+ @warn_old_files = {} # XXX for checking CHAPS, PREDEF, POSTDEF
54
58
  end
55
59
 
56
60
  def bib_file
@@ -99,10 +103,6 @@ module ReVIEW
99
103
  @parts ||= read_parts()
100
104
  end
101
105
 
102
- def parts=(parts)
103
- @parts = parts
104
- end
105
-
106
106
  def parts_in_file
107
107
  parts.find_all{|part|
108
108
  part if part.present? and part.file?
@@ -198,10 +198,6 @@ module ReVIEW
198
198
  @catalog
199
199
  end
200
200
 
201
- def catalog=(catalog)
202
- @catalog = catalog
203
- end
204
-
205
201
  def read_CHAPS
206
202
  if catalog
207
203
  catalog.chaps
@@ -298,10 +294,6 @@ module ReVIEW
298
294
  end
299
295
  end
300
296
 
301
- def basedir
302
- @basedir
303
- end
304
-
305
297
  private
306
298
 
307
299
  def read_parts
@@ -344,9 +336,9 @@ module ReVIEW
344
336
  end
345
337
  end
346
338
 
347
- chap = read_CHAPS()\
348
- .strip.lines.map {|line| line.strip }.join("\n").split(/\n{2,}/)\
349
- .map {|part_chunk|
339
+ chap = read_CHAPS().
340
+ strip.lines.map {|line| line.strip }.join("\n").split(/\n{2,}/).
341
+ map {|part_chunk|
350
342
  chaps = part_chunk.split.map {|chapid|
351
343
  Chapter.new(self, (num += 1), chapid, "#{@basedir}/#{chapid}")
352
344
  }
@@ -396,6 +388,12 @@ module ReVIEW
396
388
  end
397
389
 
398
390
  def read_FILE(filename)
391
+ if !@warn_old_files[filename]
392
+ @warn_old_files[filename] = true
393
+ if !caller().any?{|item| item =~ %r|/review/test/test_|}
394
+ warn "!!! #{filename} is obsoleted. please use catalog.yml."
395
+ end
396
+ end
399
397
  res = ""
400
398
  File.open("#{@basedir}/#{filename}", 'r:BOM|utf-8') do |f|
401
399
  while line = f.gets
@@ -83,7 +83,7 @@ module ReVIEW
83
83
  return "#{@number}" if @number < 1 || @number > 27
84
84
  if @book.config["appendix_format"]
85
85
  raise ReVIEW::ConfigError,
86
- "'appendix_format:' in config.yml is obsoleted."
86
+ "'appendix_format:' in config.yml is obsoleted."
87
87
  end
88
88
 
89
89
  i18n_appendix = I18n.get("appendix")
@@ -121,6 +121,7 @@ module ReVIEW
121
121
  end
122
122
 
123
123
  private
124
+
124
125
  def on_FILE?(contents)
125
126
  contents.lines.map(&:strip).include?("#{id()}#{@book.ext()}")
126
127
  end
@@ -2,7 +2,7 @@
2
2
  # $Id: book.rb 4315 2009-09-02 04:15:24Z kmuto $
3
3
  #
4
4
  # Copyright (c) 2002-2008 Minero Aoki
5
- # 2009 Minero Aoki, Kenshi Muto
5
+ # 2009-2017 Minero Aoki, Kenshi Muto
6
6
  #
7
7
  # This program is free software.
8
8
  # You can distribute or modify this program under the terms of
@@ -118,29 +118,29 @@ module ReVIEW
118
118
  def numberless_image_index
119
119
  @numberless_image_index ||=
120
120
  NumberlessImageIndex.parse(lines(), id(),
121
- "#{book.basedir}/#{@book.image_dir}",
122
- @book.image_types, @book.config['builder'])
121
+ "#{book.basedir}/#{@book.config['imagedir']}",
122
+ @book.image_types, @book.config['builder'])
123
123
  end
124
124
 
125
125
  def image_index
126
126
  @image_index ||= ImageIndex.parse(lines(), id(),
127
- "#{book.basedir}/#{@book.image_dir}",
128
- @book.image_types, @book.config['builder'])
127
+ "#{book.basedir}/#{@book.config['imagedir']}",
128
+ @book.image_types, @book.config['builder'])
129
129
  @image_index
130
130
  end
131
131
 
132
132
  def icon_index
133
133
  @icon_index ||= IconIndex.parse(lines(), id(),
134
- "#{book.basedir}/#{@book.image_dir}",
135
- @book.image_types, @book.config['builder'])
134
+ "#{book.basedir}/#{@book.config['imagedir']}",
135
+ @book.image_types, @book.config['builder'])
136
136
  @icon_index
137
137
  end
138
138
 
139
139
  def indepimage_index
140
140
  @indepimage_index ||=
141
141
  IndepImageIndex.parse(lines(), id(),
142
- "#{book.basedir}/#{@book.image_dir}",
143
- @book.image_types, @book.config['builder'])
142
+ "#{book.basedir}/#{@book.config['imagedir']}",
143
+ @book.image_types, @book.config['builder'])
144
144
  end
145
145
 
146
146
  def bibpaper(id)
@@ -22,7 +22,7 @@ module ReVIEW
22
22
  end
23
23
 
24
24
  def get_entries
25
- Dir.glob(File.join(@basedir, "**/*.*"))
25
+ Dir.glob(File.join(@basedir, "**{,/*/**}/*.*")).uniq
26
26
  end
27
27
 
28
28
  def add_entry(path)
@@ -46,23 +46,23 @@ module ReVIEW
46
46
 
47
47
  def target_list(id)
48
48
  [
49
- # 1. <basedir>/<builder>/<chapid>/<id>.<ext>
50
- "#{@basedir}/#{@builder}/#{@chapid}/#{id}",
49
+ # 1. <basedir>/<builder>/<chapid>/<id>.<ext>
50
+ "#{@basedir}/#{@builder}/#{@chapid}/#{id}",
51
51
 
52
- # 2. <basedir>/<builder>/<chapid>-<id>.<ext>
53
- "#{@basedir}/#{@builder}/#{@chapid}-#{id}",
52
+ # 2. <basedir>/<builder>/<chapid>-<id>.<ext>
53
+ "#{@basedir}/#{@builder}/#{@chapid}-#{id}",
54
54
 
55
- # 3. <basedir>/<builder>/<id>.<ext>
56
- "#{@basedir}/#{@builder}/#{id}",
55
+ # 3. <basedir>/<builder>/<id>.<ext>
56
+ "#{@basedir}/#{@builder}/#{id}",
57
57
 
58
- # 4. <basedir>/<chapid>/<id>.<ext>
59
- "#{@basedir}/#{@chapid}/#{id}",
58
+ # 4. <basedir>/<chapid>/<id>.<ext>
59
+ "#{@basedir}/#{@chapid}/#{id}",
60
60
 
61
- # 5. <basedir>/<chapid>-<id>.<ext>
62
- "#{@basedir}/#{@chapid}-#{id}",
61
+ # 5. <basedir>/<chapid>-<id>.<ext>
62
+ "#{@basedir}/#{@chapid}-#{id}",
63
63
 
64
- # 6. <basedir>/<id>.<ext>
65
- "#{@basedir}/#{id}"
64
+ # 6. <basedir>/<id>.<ext>
65
+ "#{@basedir}/#{id}"
66
66
  ]
67
67
  end
68
68
 
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
  #
3
3
  # Copyright (c) 2002-2007 Minero Aoki
4
- # 2008-2009 Minero Aoki, Kenshi Muto
4
+ # 2008-2017 Minero Aoki, Kenshi Muto
5
5
  #
6
6
  # This program is free software.
7
7
  # You can distribute or modify this program under the terms of
@@ -58,7 +58,7 @@ module ReVIEW
58
58
  @index.fetch(id)
59
59
  rescue
60
60
  if @index.keys.map{|i| i.split(/\|/).last }.flatten. # unfold all ids
61
- reduce(Hash.new(0)){|h, i| h[i] += 1; h}. # number of occurrences
61
+ each_with_object(Hash.new(0)){|i, h| h[i] += 1}. # number of occurrences
62
62
  select{|k, v| k == id && v > 1 }.present? # detect duplicated
63
63
  raise KeyError, "key '#{id}' is ambiguous for #{self.class}"
64
64
  end
@@ -58,8 +58,8 @@ module ReVIEW
58
58
 
59
59
  def +(other)
60
60
  Volume.new(@bytes + other.bytes,
61
- @chars + other.chars,
62
- @lines + other.lines)
61
+ @chars + other.chars,
62
+ @lines + other.lines)
63
63
  end
64
64
 
65
65
  end
@@ -25,6 +25,7 @@ module ReVIEW
25
25
  def pre_paragraph
26
26
  nil
27
27
  end
28
+
28
29
  def post_paragraph
29
30
  nil
30
31
  end
@@ -46,6 +47,7 @@ module ReVIEW
46
47
  @output = StringIO.new
47
48
  @book = @chapter.book if @chapter.present?
48
49
  @tabwidth = nil
50
+ @tsize = nil
49
51
  if @book && @book.config && @book.config["tabwidth"]
50
52
  @tabwidth = @book.config["tabwidth"]
51
53
  end
@@ -83,6 +85,21 @@ module ReVIEW
83
85
  end
84
86
  private :headline_prefix
85
87
 
88
+ ## for //firstlinenum[num]
89
+ def firstlinenum(num)
90
+ @first_line_num = num.to_i
91
+ end
92
+
93
+ def get_line_num
94
+ if !@first_line_num
95
+ return 1
96
+ end
97
+ line_num = @first_line_num
98
+ @first_line_num = nil
99
+
100
+ line_num
101
+ end
102
+
86
103
  def list(lines, id, caption, lang = nil)
87
104
  begin
88
105
  list_header id, caption, lang
@@ -327,6 +344,20 @@ module ReVIEW
327
344
  end
328
345
  end
329
346
 
347
+ def embed(lines, arg = nil)
348
+ if arg
349
+ builders = arg.gsub(/^\s*\|/, "").gsub(/\|\s*$/, "").gsub(/\s/, "").split(/,/)
350
+ c = target_name
351
+ if builders.include?(c)
352
+ print lines.join()
353
+ else
354
+ ""
355
+ end
356
+ else
357
+ print lines.join()
358
+ end
359
+ end
360
+
330
361
  def warn(msg)
331
362
  $stderr.puts "#{@location}: warning: #{msg}"
332
363
  end
@@ -350,7 +381,7 @@ module ReVIEW
350
381
  params.each do |param|
351
382
  if param =~ /\A.+?::/
352
383
  if param =~ /\A#{type}::/
353
- param.sub!(/\A#{type}::/, '')
384
+ param.sub!(/\A#{type}::/, '')
354
385
  else
355
386
  next
356
387
  end
@@ -435,6 +466,18 @@ module ReVIEW
435
466
  def ul_item_end
436
467
  end
437
468
 
469
+ def tsize(str)
470
+ if matched = str.match(/\A\|(.*?)\|(.*)/)
471
+ builders = matched[1].split(/,/).map{|i| i.gsub(/\s/, '') }
472
+ c = self.class.to_s.gsub(/ReVIEW::/, '').gsub(/Builder/, '').downcase
473
+ if builders.include?(c)
474
+ @tsize = matched[2]
475
+ end
476
+ else
477
+ @tsize = str
478
+ end
479
+ end
480
+
438
481
  def inline_raw(args)
439
482
  if matched = args.match(/\|(.*?)\|(.*)/)
440
483
  builders = matched[1].split(/,/).map{|i| i.gsub(/\s/, '') }
@@ -449,6 +492,19 @@ module ReVIEW
449
492
  end
450
493
  end
451
494
 
495
+ def inline_embed(args)
496
+ if matched = args.match(/\|(.*?)\|(.*)/)
497
+ builders = matched[1].split(/,/).map{|i| i.gsub(/\s/, '') }
498
+ if builders.include?(target_name)
499
+ matched[2]
500
+ else
501
+ ""
502
+ end
503
+ else
504
+ args
505
+ end
506
+ end
507
+
452
508
  ## override TextUtils::detab
453
509
  def detab(str, num = nil)
454
510
  if num
@@ -35,12 +35,12 @@ module ReVIEW
35
35
  if entry.is_a? Hash
36
36
  entry.keys
37
37
  end
38
- }.flatten.reject{|entry| entry.nil?}.join("\n")
38
+ }.flatten.compact.join("\n")
39
39
  end
40
40
 
41
41
  def parts_with_chaps
42
42
  return "" unless @yaml["CHAPS"]
43
- @yaml["CHAPS"].flatten.reject{|entry| entry.nil?}
43
+ @yaml["CHAPS"].flatten.compact
44
44
  end
45
45
 
46
46
  def appendix
@@ -161,6 +161,7 @@ module ReVIEW
161
161
  defblock :tip, 0..1
162
162
  defblock :box, 0..1
163
163
  defblock :comment, 0..1, true
164
+ defblock :embed, 0..1
164
165
 
165
166
  defsingle :footnote, 2
166
167
  defsingle :noindent, 0
@@ -175,6 +176,7 @@ module ReVIEW
175
176
  defsingle :tsize, 1
176
177
  defsingle :include, 1
177
178
  defsingle :olnum, 1
179
+ defsingle :firstlinenum, 1
178
180
 
179
181
  definline :chapref
180
182
  definline :chap
@@ -229,11 +231,12 @@ module ReVIEW
229
231
  definline :comment
230
232
  definline :include
231
233
  definline :tcy
234
+ definline :embed
232
235
 
233
236
  private
234
237
 
235
238
  def do_compile
236
- f = LineInput.new(Preprocessor::Strip.new(StringIO.new(@chapter.content)))
239
+ f = LineInput.new(StringIO.new(@chapter.content))
237
240
  @strategy.bind self, @chapter, Location.new(@chapter.basename, f)
238
241
  tagged_section_init
239
242
  while f.next?
@@ -265,7 +268,7 @@ module ReVIEW
265
268
  warn "`//' seen but is not valid command: #{line.strip.inspect}"
266
269
  if block_open?(line)
267
270
  warn "skipping block..."
268
- read_block(f)
271
+ read_block(f, false)
269
272
  end
270
273
  else
271
274
  if f.peek.strip.empty?
@@ -431,8 +434,9 @@ module ReVIEW
431
434
  def read_command(f)
432
435
  line = f.gets
433
436
  name = line.slice(/[a-z]+/).to_sym
437
+ ignore_inline = (name == :embed)
434
438
  args = parse_args(line.sub(%r<\A//[a-z]+>, '').rstrip.chomp('{'), name)
435
- lines = block_open?(line) ? read_block(f) : nil
439
+ lines = block_open?(line) ? read_block(f, ignore_inline) : nil
436
440
  return name, args, lines
437
441
  end
438
442
 
@@ -440,12 +444,16 @@ module ReVIEW
440
444
  line.rstrip[-1,1] == '{'
441
445
  end
442
446
 
443
- def read_block(f)
447
+ def read_block(f, ignore_inline)
444
448
  head = f.lineno
445
449
  buf = []
446
450
  f.until_match(%r<\A//\}>) do |line|
447
- unless line =~ /\A\#@/
448
- buf.push text(line.rstrip)
451
+ if ignore_inline
452
+ buf.push line
453
+ else
454
+ unless line =~ /\A\#@/
455
+ buf.push text(line.rstrip)
456
+ end
449
457
  end
450
458
  end
451
459
  unless %r<\A//\}> =~ f.peek
@@ -523,7 +531,7 @@ module ReVIEW
523
531
  end
524
532
  result = @strategy.nofunc_text(words.shift)
525
533
  until words.empty?
526
- result << compile_inline(words.shift.gsub(/\\\}/, '}'))
534
+ result << compile_inline(words.shift.gsub(/\\\}/, '}').gsub(/\\\\/, '\\'))
527
535
  result << @strategy.nofunc_text(words.shift)
528
536
  end
529
537
  result