lib/review/tocprinter.rb in review-4.1.0 vs lib/review/tocprinter.rb in review-4.2.0
- old
+ new
@@ -1,148 +1,277 @@
-# Copyright (c) 2008-2017 Minero Aoki, Kenshi Muto
-# 2002-2007 Minero Aoki
+# Copyright (c) 2008-2020 Minero Aoki, Kenshi Muto
+# 1999-2007 Minero Aoki
#
# This program is free software.
# You can distribute or modify this program under the terms of
# the GNU LGPL, Lesser General Public License version 2.1.
-# For details of LGPL, see the file "COPYING".
+# For details of the GNU LGPL, see the file "COPYING".
#
-require 'review/htmlutils'
-require 'review/tocparser'
+require 'review/book'
+require 'review/version'
+require 'optparse'
+require 'review/plaintextbuilder'
module ReVIEW
- class TOCPrinter
- def self.default_upper_level
- 99 # no one use 99 level nest
+ class PLAINTEXTTocBuilder < PLAINTEXTBuilder
+ def headline(level, label, caption)
+ if @chapter.is_a?(ReVIEW::Book::Part)
+ print "\x01H0\x01" # XXX: don't modify level value. level value will be handled in sec_counter#prefix()
+ else
+ print "\x01H#{level}\x01"
+ end
+ # embed header information for tocparser
+ super(level, label, caption)
end
- def initialize(print_upper, param, out = $stdout)
- @print_upper = print_upper
- @config = param
- @out = out
+ def base_block(type, lines, caption = nil)
+ puts "\x01STARTLIST\x01"
+ super(type, lines, caption)
+ puts "\x01ENDLIST\x01"
end
- def print_book(book)
- book.each_part { |part| print_part(part) }
+ def blank
+ @blank_seen = true
end
+ end
- def print_part(part)
- part.each_chapter { |chap| print_chapter(chap) }
+ class TOCPrinter
+ def self.execute(*args)
+ Signal.trap(:INT) { exit 1 }
+ if RUBY_PLATFORM !~ /mswin(?!ce)|mingw|cygwin|bccwin/
+ Signal.trap(:PIPE, 'IGNORE')
+ end
+ new.execute(*args)
+ rescue Errno::EPIPE
+ exit 0
end
- def print_chapter(chap)
- chap_node = TOCParser.chapter_node(chap)
- print_node(1, chap_node)
- print_children(chap_node)
+ def initialize
+ @logger = ReVIEW.logger
+ @config = ReVIEW::Configure.values
+ @yamlfile = 'config.yml'
+ @book = ReVIEW::Book::Base.load
+ @upper = 4
+ @indent = true
+ @buildonly = nil
+ @detail = nil
end
- def print?(level)
- level <= @print_upper
+ def execute(*args)
+ parse_options(args)
+ @book.config = ReVIEW::Configure.values
+ unless File.readable?(@yamlfile)
+ @logger.error("No such fiile or can't open #{@yamlfile}.")
+ exit 1
+ end
+ @book.load_config(@yamlfile)
+ I18n.setup(@config['language'])
+
+ if @detail
+ begin
+ require 'unicode/eaw'
+ @calc_char_width = true
+ rescue LoadError
+ @logger.warn('not found unicode/eaw library. page volume may be unreliable.')
+ @calc_char_width = nil
+ end
+ end
+
+ print_result(build_result_array)
end
- end
- class TextTOCPrinter < TOCPrinter
- private
+ def build_result_array
+ result_array = []
+ begin
+ @book.parts.each do |part|
+ if part.name.present? && (@buildonly.nil? || @buildonly.include?(part.name))
+ result_array.push({ part: 'start' })
+ if part.file?
+ result = build_chap(part)
+ result_array += parse_contents(part.name, @upper, result)
+ else
+ title = part.format_number + I18n.t('chapter_postfix') + part.title
+ result_array += [
+ { name: '', lines: 1, chars: title.size, list_lines: 0, text_lines: 1 },
+ { level: 0, headline: title, lines: 1, chars: title.size, list_lines: 0, text_lines: 1 }
+ ]
+ end
+ end
- def print_children(node)
- return unless print?(node.level + 1)
- node.each_section_with_index do |section, idx|
- unless section.blank?
- print_node(idx + 1, section)
- print_children(section)
+ part.chapters.each do |chap|
+ if @buildonly.nil? || @buildonly.include?(chap.name)
+ result = build_chap(chap)
+ result_array += parse_contents(chap.name, @upper, result)
+ end
+ end
+ if part.name.present? && (@buildonly.nil? || @buildonly.include?(part.name))
+ result_array.push({ part: 'end' })
+ end
end
+ rescue ReVIEW::FileNotFound => e
+ @logger.error e
+ exit 1
end
+
+ result_array
end
- def print_node(number, node)
- if node.chapter?
- vol = node.volume
- @out.printf("%3s %3dKB %6dC %5dL %s (%s)\n",
- chapnumstr(node.number),
- vol.kbytes, vol.chars, vol.lines,
- node.label, node.chapter_id)
- else ## for section node
- @out.printf("%17s %5dL %s\n",
- '', node.estimated_lines,
- " #{' ' * (node.level - 1)}#{number} #{node.label}")
+ def print_result(result_array)
+ result_array.each do |result|
+ if result[:part]
+ next
+ end
+
+ if result[:name]
+ # file information
+ if @detail
+ puts '============================='
+ printf("%6dC %5dL %5dP %s\n", result[:chars], result[:lines], calc_pages(result).ceil, result[:name])
+ puts '-----------------------------'
+ end
+ next
+ end
+
+ # section information
+ if @detail
+ printf('%6dC %5dL %5.1fP ', result[:chars], result[:lines], calc_pages(result))
+ end
+ if @indent && result[:level]
+ print ' ' * (result[:level] == 0 ? 0 : result[:level] - 1)
+ end
+ puts result[:headline]
end
end
- def chapnumstr(n)
- n ? sprintf('%2d.', n) : ' '
+ def calc_pages(result)
+ p = 0
+ p += result[:list_lines].to_f / @book.page_metric.list.n_lines
+ p += result[:text_lines].to_f / @book.page_metric.text.n_lines
+ p
end
- def volume_columns(level, volstr)
- cols = ['', '', '', nil]
- cols[level - 1] = volstr
- cols[0, 3] # does not display volume of level-4 section
+ def calc_linesize(l)
+ return l.size unless @calc_char_width
+ w = 0
+ l.split('').each do |c|
+ # XXX: should include A also?
+ if %i[Na H N].include?(Unicode::Eaw.property(c))
+ w += 0.5 # halfwidth
+ else
+ w += 1
+ end
+ end
+ w
end
- end
- class HTMLTOCPrinter < TOCPrinter
- include HTMLUtils
+ def parse_contents(name, upper, content)
+ headline_array = []
+ counter = { lines: 0, chars: 0, list_lines: 0, text_lines: 0 }
+ listmode = nil
- def print_book(book)
- @out.puts '<ul class="book-toc">'
- book.each_part { |part| print_part(part) }
- @out.puts '</ul>'
- end
+ content.split("\n").each do |l|
+ if l.start_with?("\x01STARTLIST\x01")
+ listmode = true
+ next
+ elsif l.start_with?("\x01ENDLIST\x01")
+ listmode = nil
+ next
+ elsif l =~ /\A\x01H(\d)\x01/
+ # headline
+ level = $1.to_i
+ l = $'
+ if level <= upper
+ if counter[:chars] > 0
+ headline_array.push(counter)
+ end
+ headline = l
+ counter = {
+ level: level,
+ headline: headline,
+ lines: 1,
+ chars: headline.size,
+ list_lines: 0,
+ text_lines: 1
+ }
+ next
+ end
+ end
- def print_part(part)
- if part.number
- @out.puts li(part.title)
- end
- super
- end
+ counter[:lines] += 1
+ counter[:chars] += l.size
- def print_chapter(chap)
- chap_node = TOCParser.chapter_node(chap)
- ext = chap.book.config['htmlext'] || 'html'
- path = chap.path.sub(/\.re/, '.' + ext)
- label = if chap_node.number && chap.on_chaps?
- "#{chap.number} #{chap.title}"
- else
- chap.title
- end
- @out.puts li(a_name(path, escape_html(label)))
- return unless print?(2)
- if print?(3)
- @out.puts chap_sections_to_s(chap_node)
- else
- @out.puts chapter_to_s(chap_node)
+ if listmode
+ # code list: calculate line wrapping
+ if l.size == 0
+ counter[:list_lines] += 1
+ else
+ counter[:list_lines] += (calc_linesize(l) - 1) / @book.page_metric.list.n_columns + 1
+ end
+ else
+ # normal paragraph: calculate line wrapping
+ if l.size == 0
+ counter[:text_lines] += 1
+ else
+ counter[:text_lines] += (calc_linesize(l) - 1) / @book.page_metric.text.n_columns + 1
+ end
+ end
end
- end
+ headline_array.push(counter)
- private
+ total_lines = 0
+ total_chars = 0
+ total_list_lines = 0
+ total_text_lines = 0
- def chap_sections_to_s(chap)
- return '' if chap.section_size < 1
- res = []
- res << '<ol>'
- chap.each_section { |sec| res << li(escape_html(sec.label)) }
- res << '</ol>'
- res.join("\n")
+ headline_array.each do |h|
+ next unless h[:lines]
+ total_lines += h[:lines]
+ total_chars += h[:chars]
+ total_list_lines += h[:list_lines]
+ total_text_lines += h[:text_lines]
+ end
+
+ headline_array.delete_if(&:empty?).
+ unshift({ name: name, lines: total_lines, chars: total_chars, list_lines: total_list_lines, text_lines: total_text_lines })
end
- def chapter_to_s(chap)
- res = []
- chap.each_section do |sec|
- res << li(escape_html(sec.label))
- next unless print?(4)
- next unless sec.section_size > 0
- res << '<ul>'
- sec.each_section { |node| res << li(escape_html(node.label)) }
- res << '</ul>'
+ def build_chap(chap)
+ compiler = ReVIEW::Compiler.new(ReVIEW::PLAINTEXTTocBuilder.new)
+ begin
+ compiler.compile(@book.chapter(chap.name))
+ rescue ReVIEW::ApplicationError => e
+ @logger.error e
+ exit 1
end
- res.join("\n")
end
- def li(content)
- "<li>#{content}</li>"
- end
-
- def a_name(name, label)
- %Q(<a name="#{name}">#{label}</a>)
+ def parse_options(args)
+ opts = OptionParser.new
+ opts.version = ReVIEW::VERSION
+ opts.on('--yaml=YAML', 'Read configurations from YAML file.') { |yaml| @yamlfile = yaml }
+ opts.on('-y', '--only file1,file2,...', 'list only specified files.') do |v|
+ @buildonly = v.split(/\s*,\s*/).map { |m| m.strip.sub(/\.re\Z/, '') }
+ end
+ opts.on('-l', '--level N', 'list upto N level (default=4)') do |n|
+ @upper = n.to_i
+ end
+ opts.on('-d', '--detail', 'show characters and lines of each section.') do
+ @detail = true
+ end
+ opts.on('--noindent', "don't indent headlines.") do
+ @indent = nil
+ end
+ opts.on('--help', 'print this message and quit.') do
+ puts opts.help
+ exit 0
+ end
+ begin
+ opts.parse!(args)
+ rescue OptionParser::ParseError => e
+ @logger.error e.message
+ $stderr.puts opts.help
+ exit 1
+ end
end
end
end