class ReVIEW::Update

Constants

DVI_COMMAND
DVI_OPTIONS
EPUB_VERSION
HTML_VERSION
TARGET_VERSION

should be

TEX_COMMAND
TEX_DOCUMENTCLASS
TEX_DOCUMENTCLASS_BAD
TEX_DOCUMENTCLASS_OPTS
TEX_OPTIONS

Attributes

catalog_ymls[R]
config_ymls[R]
epub_ymls[R]
force[RW]
locale_ymls[R]
specified_template[RW]
tex_ymls[R]

Public Class Methods

execute(*args) click to toggle source
# File lib/review/update.rb, line 18
def self.execute(*args)
  new.execute(*args)
end
new() click to toggle source
# File lib/review/update.rb, line 37
def initialize
  @template = '__DEFAULT__'
  @specified_template = nil
  @force = nil
  @logger = ReVIEW.logger
  @review_dir = File.dirname(File.expand_path('..', __dir__))
  @config_ymls = []
  @locale_ymls = []
  @catalog_ymls = []
  @tex_ymls = []
  @epub_ymls = []

  @backup = true
end

Public Instance Methods

check_old_catalogs(dir) click to toggle source
# File lib/review/update.rb, line 208
def check_old_catalogs(dir)
  files = Dir.glob(File.join(dir, '*')).map do |fname|
    if %w[PREDEF CHAPS POSTDEF PART].include?(File.basename(fname))
      File.basename(fname)
    else
      return nil
    end
  end.compact

  unless files.empty?
    @logger.error t("!! %s file(s) is obsoleted. Run 'review-catalog-converter' to convert to 'catalog.yml' and remove old files. Aborted. !!", files.join(', '))
    raise ApplicationError
  end
end
check_own_files(dir) click to toggle source
# File lib/review/update.rb, line 227
def check_own_files(dir)
  if File.exist?(File.join(dir, 'layouts/layout.tex.erb'))
    # rubocop:disable Style/SoleNestedConditional
    unless confirm('** There is custom layouts/layout.tex.erb file. Updating may break to make PDF until you fix layout.tex.erb. Do you really proceed to update? **', [], nil)
      raise ApplicationError
    end
    # rubocop:enable Style/SoleNestedConditional
  end

  if File.exist?(File.join(dir, 'review-ext.rb'))
    @logger.info t('** There is review-ext.rb file. You need to update it by yourself. **')
  end
end
confirm(message, args = [], default = true) click to toggle source
# File lib/review/update.rb, line 95
def confirm(message, args = [], default = true)
  if @force
    @logger.info t(message, args)
    if default
      @logger.info ' yes'
      return true
    else
      @logger.info 'no'
      return nil
    end
  end

  loop do
    print t(message, args)
    if default
      print ' [y]/n '
    else
      print ' y/[n] '
    end
    case gets.chomp.downcase
    when 'yes', 'y'
      return true
    when 'no', 'n'
      return nil
    when ''
      return default
    end
  end
end
convert_documentclass_opts(yml, cls, prev_opts) click to toggle source
# File lib/review/update.rb, line 413
def convert_documentclass_opts(yml, cls, prev_opts)
  # XXX: at this time, review-jsbook and review-jlreq uses same parameters
  opts = []
  flag = true
  case cls
  when 'review-jsbook' # at this time, it ignores keyval
    prev_opts.split(/\s*,\s*/).each do |v|
      case v
      when 'a4j', 'a5j', 'b4j', 'b5j', 'a3paper', 'a4paper', 'a5paper', 'a6paper', 'b4paper', 'b5paper', 'b6paper', 'letterpaper', 'legalpaper', 'executivepaper'
        opts << "paper=#{v.sub('j', '').sub('paper', '')}"
      when /[\d.]+ptj/ # not cared...
        opts << "fontsize=#{v.sub('j', '')}"
      when /[\d.]+pt/, /[\d.]+Q/
        opts << "fontsize=#{v}"
      when 'landscape', 'oneside', 'twoside', 'vartwoside', 'onecolumn',
           'twocolumn', 'titlepage', 'notitlepage', 'openright',
           'openany', 'leqno', 'fleqn', 'disablejfam', 'draft', 'final',
           'mingoth', 'winjis', 'jis', 'papersize', 'english', 'report',
           'jslogo', 'nojslogo'
        # pass-through
        opts << v
      when 'uplatex', 'nomag', 'usemag', 'nomag*', 'tombow', 'tombo', 'mentuke', 'autodetect-engine'
        # can be ignored
        next
      else
        flag = nil
      end
    end
    opts << 'media=print'
    opts << 'cover=false'
  when 'review-jlreq'
    # at this time, only think about jsbook->jlreq
    prev_opts.split(/\s*,\s*/).each do |v|
      case v
      when 'a4j', 'a5j', 'b4j', 'b5j', 'a3paper', 'a4paper', 'a5paper', 'a6paper', 'b4paper', 'b5paper', 'b6paper', 'letterpaper', 'legalpaper', 'executivepaper'
        opts << "paper=#{v.sub('j', '').sub('paper', '')}"
      when /[\d.]+ptj/ # not cared...
        opts << "fontsize=#{v.sub('j', '')}"
      when /[\d.]+pt/, /[\d.]+Q/
        opts << "fontsize=#{v}"
      when 'landscape', 'oneside', 'twoside', 'onecolumn', 'twocolumn', 'titlepage', 'notitlepage', 'openright', 'openany', 'leqno', 'fleqn', 'draft', 'final', 'report'
        # pass-through
        opts << v
      when 'uplatex', 'nomag', 'usemag', 'nomag*', 'tombow', 'tombo', 'mentuke', 'autodetect-engine'
        # can be ignored
        next
      else
        # 'vartwoside', 'disablejfam', 'mingoth', 'winjis', 'jis', 'papersize', 'english', 'jslogo', 'nojslogo'
        flag = nil
      end
    end
    opts << 'media=print'
    opts << 'cover=false'
  else
    flag = nil
    @logger.error t("%s: ** '%s' is unknown class. Ignored. **", [File.basename(yml), cls])
  end
  return flag, opts.join(',')
end
execute(*args) click to toggle source
# File lib/review/update.rb, line 52
def execute(*args)
  parse_options(args)
  dir = Dir.pwd

  parse_ymls(dir)
  check_old_catalogs(dir)

  show_version

  if @config_ymls.empty?
    @logger.error t("!! No *.yml file with 'review_version' was found. Aborted. !!")
    raise ApplicationError
  end

  check_own_files(dir)
  update_version
  update_rakefile(dir)
  update_epub_version
  update_locale
  update_tex_parameters

  if @template
    if @template == '__DEFAULT__'
      @template = TEX_DOCUMENTCLASS[0]
    end
    update_tex_stys(@template, dir)
  end

  update_tex_command
  update_dvi_command

  puts t('Finished.')
rescue ApplicationError
  exit 1
end
parse_options(args) click to toggle source
# File lib/review/update.rb, line 134
def parse_options(args)
  opts = OptionParser.new
  opts.version = ReVIEW::VERSION
  opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} [option]"
  opts.on('-h', '--help', 'print this message and quit.') do
    puts opts.help
    exit 0
  end
  opts.on('--latex-template name', 'specify LaTeX template name. (default: review-jsbook)') do |tname|
    @specified_template = tname
  end

  begin
    opts.parse!(args)
  rescue OptionParser::ParseError => e
    @logger.error e.message
    $stderr.puts opts.help
    raise ApplicationError
  end

  if @specified_template
    tdir = File.join(@review_dir, 'templates/latex', @specified_template)
    unless File.exist?(tdir)
      @logger.error "!! #{tdir} not found. Aborted. !!"
      raise ApplicationError
    end
  end
end
parse_ymls(dir) click to toggle source
# File lib/review/update.rb, line 163
def parse_ymls(dir)
  language = 'en'

  Dir.glob(File.join(dir, '*.yml')).sort.each do |yml|
    begin
      config = YAMLLoader.safe_load_file(yml)
      if config['language'].present?
        language = config['language']
      end

      if config['review_version'].present?
        @config_ymls.push(yml)
      end
      if config['texdocumentclass'].present? ||
         config['texcommand'].present? ||
         config['texoptions'].present? ||
         config['dvicommand'].present? ||
         config['dvioptions'].present? ||
         config['pdfmaker'].present?
        @tex_ymls.push(yml)
      end
      if config['epubmaker'].present? || config['epubversion'].present? ||
         config['htmlversion'].present?
        @epub_ymls.push(yml)
      end
      if config['locale'].present?
        @locale_ymls.push(yml)
      end
      if config['PREDEF'].present? || config['CHAPS'].present? ||
         config['APPENDIX'].present? || config['POSTDEF'].present?
        @catalog_ymls.push(yml)
      end
    rescue Psych::SyntaxError
      @logger.error "!! #{yml} is broken. Ignored. !!"
    end
  end
  I18n.setup(language)

  @config_ymls.uniq!
  @locale_ymls.uniq!
  @catalog_ymls.uniq!
  @tex_ymls.uniq!
  @epub_ymls.uniq!
end
rewrite_documentclass_opts_by_flag(flag, yml, old_opts, modified_opts) click to toggle source
# File lib/review/update.rb, line 386
def rewrite_documentclass_opts_by_flag(flag, yml, old_opts, modified_opts)
  if flag # successfully converted
    @logger.info t("%s: previous 'texdocumentclass' option '%s' is safely replaced with '%s'.", [File.basename(yml), old_opts, modified_opts])
  else # something wrong
    unless confirm("%s: previous 'texdocumentclass' option '%s' couldn't be converted fully. '%s' is suggested. Do you really proceed?", [File.basename(yml), old_opts, modified_opts], nil)
      @template = nil
      return nil
    end
  end

  rewrite_yml(yml, 'texdocumentclass', %Q(["#{@template}", "#{modified_opts}"]))
end
rewrite_yml(yml, key, val) click to toggle source
# File lib/review/update.rb, line 125
def rewrite_yml(yml, key, val)
  content = File.read(yml)
  content.gsub!(/^(\s*)#{key}:.*$/, '\1' + "#{key}: #{val}")
  if @backup
    FileUtils.mv(yml, "#{yml}-old")
  end
  File.write(yml, content)
end
show_version() click to toggle source
# File lib/review/update.rb, line 223
def show_version
  puts t('** review-update updates your project to %s **', ReVIEW::VERSION)
end
t(message, args = []) click to toggle source
# File lib/review/update.rb, line 88
def t(message, args = [])
  unless I18n.get(message)
    I18n.set(message, message) # just copy
  end
  I18n.t(message, args)
end
update_dvi_command() click to toggle source
# File lib/review/update.rb, line 540
def update_dvi_command
  @tex_ymls.each do |yml|
    config = YAMLLoader.safe_load_file(yml)
    if !config['dvicommand'] || config['dvicommand'] !~ /\s+-/
      next
    end

    # option should be moved to dvioptions
    cmd, opts = config['dvicommand'].split(/\s+-/, 2)
    opts = "-#{opts}"

    unless confirm("%s: 'dvicommand' has options ('%s'). Move it to 'dvioptions'?", [File.basename(yml), opts])
      next
    end

    if config['dvioptions'].present?
      config['dvioptions'] += " #{opts}"
      rewrite_yml(yml, 'dvicommand', %Q("#{cmd}"))
      rewrite_yml(yml, 'dvioptions', %Q("#{config['dvioptions']}"))
    else
      rewrite_yml(yml, 'dvicommand', %Q("#{cmd}"\ndvioptions: "#{DVI_OPTIONS} #{opts}"))
    end
  end
end
update_epub_version() click to toggle source
# File lib/review/update.rb, line 297
def update_epub_version
  @epub_ymls.each do |yml|
    config = YAMLLoader.safe_load_file(yml)
    if config['epubversion'].present? && config['epubversion'].to_f < EPUB_VERSION.to_f
      # rubocop:disable Style/SoleNestedConditional
      if confirm("%s: Update '%s' to '%s' from '%s'?", [File.basename(yml), 'epubversion', EPUB_VERSION, config['epubversion']])
        rewrite_yml(yml, 'epubversion', EPUB_VERSION)
      end
      # rubocop:enable Style/SoleNestedConditional
    end
    if !config['htmlversion'].present? || config['htmlversion'].to_f >= HTML_VERSION.to_f
      next
    end

    if confirm("%s: Update '%s' to '%s' from '%s'?", [File.basename(yml), 'htmlversion', HTML_VERSION, config['htmlversion']])
      rewrite_yml(yml, 'htmlversion', HTML_VERSION)
    end
  end
end
update_locale() click to toggle source
# File lib/review/update.rb, line 317
def update_locale
  @locale_ymls.each do |yml|
    config = YAMLLoader.safe_load_file(yml)
    if !config['chapter_quote'].present? || config['chapter_quote'].scan('%s').size != 1
      next
    end

    v = config['chapter_quote'].sub('%s', '%s %s')
    if confirm("%s: 'chapter_quote' now takes 2 values. Update '%s' to '%s'?", [File.basename(yml), config['chapter_quote'], v])
      rewrite_yml(yml, 'chapter_quote', v)
    end
  end
end
update_rakefile(dir) click to toggle source
# File lib/review/update.rb, line 259
def update_rakefile(dir)
  taskdir = File.join(dir, 'lib/tasks')
  FileUtils.mkdir_p(taskdir)

  master_rakefile = File.join(@review_dir, 'samples/sample-book/src/Rakefile')

  target_rakefile = File.join(dir, 'Rakefile')
  if File.exist?(target_rakefile)
    if Digest::SHA256.hexdigest(File.read(target_rakefile)) != Digest::SHA256.hexdigest(File.read(master_rakefile))
      # rubocop:disable Style/SoleNestedConditional
      if confirm('%s will be overridden with Re:VIEW version (%s). Do you really proceed?', ['Rakefile', master_rakefile])
        FileUtils.mv(target_rakefile, "#{target_rakefile}-old")
        FileUtils.cp(master_rakefile, target_rakefile)
      end
      # rubocop:enable Style/SoleNestedConditional
    end
  else
    @logger.info t('new file %s is created.', [target_rakefile]) unless @force
    FileUtils.cp(master_rakefile, target_rakefile)
  end

  master_rakefile = File.join(@review_dir, 'samples/sample-book/src/lib/tasks/review.rake')
  target_rakefile = File.join(taskdir, 'review.rake')
  if File.exist?(target_rakefile)
    # rubocop:disable Style/SoleNestedConditional
    if Digest::SHA256.hexdigest(File.read(target_rakefile)) != Digest::SHA256.hexdigest(File.read(master_rakefile))
      if confirm('%s will be overridden with Re:VIEW version (%s). Do you really proceed?', ['lib/tasks/review.rake', master_rakefile])
        FileUtils.mv(target_rakefile, "#{target_rakefile}-old")
        FileUtils.cp(master_rakefile, target_rakefile)
      end
    end
    # rubocop:enable Style/SoleNestedConditional
  else
    @logger.info t('new file %s is created.', [target_rakefile]) unless @force
    FileUtils.cp(master_rakefile, target_rakefile)
  end
end
update_review_jsbook_opts(yml, old_opts) click to toggle source
# File lib/review/update.rb, line 399
def update_review_jsbook_opts(yml, old_opts)
  modified_opts = old_opts.gsub(/Q=([^,]+)/, 'fontsize=\1Q').
                  gsub(/W=([^,]+)/, 'line_length=\1zw').
                  gsub(/L=([^,]+)/, 'number_of_lines=\1').
                  gsub(/H=([^,]+)/, 'baselineskip=\1H').
                  gsub(/head=([^,]+)/, 'head_space=\1')

  if modified_opts == old_opts
    return nil
  end

  rewrite_documentclass_opts_by_flag(true, yml, old_opts, modified_opts)
end
update_tex_command() click to toggle source
# File lib/review/update.rb, line 515
def update_tex_command
  @tex_ymls.each do |yml|
    config = YAMLLoader.safe_load_file(yml)
    if !config['texcommand'] || config['texcommand'] !~ /\s+-/
      next
    end

    # option should be moved to texoptions
    cmd, opts = config['texcommand'].split(/\s+-/, 2)
    opts = "-#{opts}"

    unless confirm("%s: 'texcommand' has options ('%s'). Move it to 'texoptions'?", [File.basename(yml), opts])
      next
    end

    if config['texoptions'].present?
      config['texoptions'] += " #{opts}"
      rewrite_yml(yml, 'texcommand', %Q("#{cmd}"))
      rewrite_yml(yml, 'texoptions', %Q("#{config['texoptions']}"))
    else
      rewrite_yml(yml, 'texcommand', %Q("#{cmd}"\ntexoptions: "#{TEX_OPTIONS} #{opts}"))
    end
  end
end
update_tex_parameters() click to toggle source
# File lib/review/update.rb, line 331
def update_tex_parameters
  @tex_ymls.each do |yml|
    config = YAMLLoader.safe_load_file(yml)
    unless config['texdocumentclass']
      next
    end

    if TEX_DOCUMENTCLASS.include?(config['texdocumentclass'][0])
      if @specified_template.present? && config['texdocumentclass'][0] != @specified_template
        # want to use other template?
        @logger.error t("%s: !! 'texdocumentclass' uses new class '%s' already, but you specified '%s'. This tool can't handle such migration. Ignored. !!", [File.basename(yml), config['texdocumentclass'][0], @specified_template])
        @template = nil
      else
        @template = config['texdocumentclass'][0]

        if @template == 'review-jsbook'
          update_review_jsbook_opts(yml, config['texdocumentclass'][1])
        end
      end

      # no need to update
      next
    end

    if TEX_DOCUMENTCLASS_BAD.include?(config['texdocumentclass'][0])
      cno = TEX_DOCUMENTCLASS_BAD.index(config['texdocumentclass'][0])

      if @specified_template && @specified_template != TEX_DOCUMENTCLASS[cno]
        # not default, manually selected
        unless confirm("%s: 'texdocumentclass' uses the old class '%s'. By default it is migrated to '%s', but you specify '%s'. Do you really migrate 'texdocumentclass' to '%s'?",
                       [File.basename(yml), TEX_DOCUMENTCLASS_BAD[cno],
                        TEX_DOCUMENTCLASS[cno],
                        @specified_template, @specified_template])
          @template = nil
          next
        end
        @template = @specified_template
      else
        # default migration
        @template = TEX_DOCUMENTCLASS[cno]
        unless confirm("%s: 'texdocumentclass' uses the old class '%s'. By default it is migrated to '%s'. Do you really migrate 'texdocumentclass' to '%s'?", [File.basename(yml), TEX_DOCUMENTCLASS_BAD[cno], @template, @template])
          @template = nil
          next
        end
      end

      flag, modified_opts = convert_documentclass_opts(yml, @template, config['texdocumentclass'][1])
      rewrite_documentclass_opts_by_flag(flag, yml, config['texdocumentclass'][1], modified_opts)
    else
      @template = nil
      @logger.error t("%s: ** 'texdocumentclass' specifies '%s'. Because this is unknown class for this tool, you need to update it by yourself if it won't work. **", [File.basename(yml), config['texdocumentclass'][0]])
    end
  end
end
update_tex_stys(template, dir) click to toggle source
# File lib/review/update.rb, line 473
def update_tex_stys(template, dir)
  texmacrodir = File.join(dir, 'sty')
  FileUtils.mkdir_p(texmacrodir)

  tdir = File.join(@review_dir, 'templates/latex', template)
  Dir.glob(File.join(tdir, '*.*')).each do |master_styfile|
    target_styfile = File.join(texmacrodir, File.basename(master_styfile))

    unless File.exist?(target_styfile)
      # just copy
      @logger.info t('new file %s is created.', [target_styfile]) unless @force
      FileUtils.cp(master_styfile, target_styfile)
      next
    end
    if File.basename(target_styfile) == 'review-custom.sty'
      next
    end

    if Digest::SHA256.hexdigest(File.read(target_styfile)) == Digest::SHA256.hexdigest(File.read(master_styfile))
      # same
      next
    end

    if confirm('%s will be overridden with Re:VIEW version (%s). Do you really proceed?', [target_styfile, master_styfile])
      FileUtils.mv(target_styfile, "#{target_styfile}-old")
      FileUtils.cp(master_styfile, target_styfile)
    end
  end

  if template == 'review-jsbook'
    unless File.exist?(File.join(texmacrodir, 'jsbook.cls'))
      @logger.info t('new file %s is created.', [File.join(texmacrodir, 'jsbook.cls')]) unless @force
      FileUtils.cp(File.join(@review_dir, 'vendor/jsclasses/jsbook.cls'), File.join(texmacrodir, 'jsbook.cls'))
    end

    unless File.exist?(File.join(texmacrodir, 'gentombow.sty'))
      @logger.info t('new file %s is created.', [File.join(texmacrodir, 'gentombow.sty')]) unless @force
      FileUtils.cp(File.join(@review_dir, 'vendor/gentombow/gentombow.sty'), File.join(texmacrodir, 'gentombow.sty'))
    end
  end
end
update_version() click to toggle source
# File lib/review/update.rb, line 241
def update_version
  @config_ymls.each do |yml|
    config = YAMLLoader.safe_load_file(yml)
    if config['review_version'].to_f.round(1) == TARGET_VERSION.to_f.round(1)
      next
    end

    flag = true
    if config['review_version'].to_f > TARGET_VERSION.to_f
      flag = nil
    end

    if confirm("%s: Update '%s' to '%s'?", [File.basename(yml), 'review_version', TARGET_VERSION], flag)
      rewrite_yml(yml, 'review_version', TARGET_VERSION)
    end
  end
end