class ReaPack::Index::CLI

Constants

DEFAULTS
PROGRAM_NAME

Attributes

index[R]

Public Class Methods

new(argv = []) click to toggle source
# File lib/reapack/index/cli.rb, line 4
def initialize(argv = [])
  @opts = parse_options argv

  @git = ReaPack::Index::Git.new argv.first || Dir.pwd
  log "found git repository in #{@git.path}"

  read_config unless @opts[:noconfig]

  unless @opts[:output]
    @opts[:output] = expand_path DEFAULTS[:output], relative: true
  end

  @opts = DEFAULTS.merge @opts

  log Hash[@opts.sort].inspect

  @index = ReaPack::Index.new File.expand_path(@opts[:output])
  @index.amend = @opts[:amend]
  @index.strict = @opts[:strict]
  set_url_template
rescue Rugged::OSError, Rugged::RepositoryError, ReaPack::Index::Error => e
  $stderr.puts e.message
  throw :stop, false
end

Public Instance Methods

parse_options(args, basepath = nil) click to toggle source
# File lib/reapack/index/cli/options.rb, line 44
def parse_options(args, basepath = nil)
  opts = Hash.new

  OptionParser.new do |op|
    op.program_name = PROGRAM_NAME
    op.version = ReaPack::Index::VERSION
    op.banner = "Package indexer for git-based ReaPack repositories\n" +
      "Usage: #{PROGRAM_NAME} [options] [directory]"

    op.separator 'Modes:'

    op.on '-c', '--check', 'Test every package including uncommited changes and exit' do
      opts[:check] = true
    end

    op.on '-s', '--scan [PATH|COMMIT]', 'Scan new commits (default), a path or a specific commit' do |commit|
      opts[:check] = opts[:rebuild] = false
      opts[:scan] ||= []

      if commit
        opts[:scan] << expand_path(commit.strip, base: basepath, relative: true)
      else
        opts[:scan].clear
      end
    end

    op.on '--no-scan', 'Do not scan for new commits' do
      opts[:scan] = false
    end

    op.on '--rebuild', 'Clear the index and rescan the whole git history' do
      opts[:check] = false
      opts[:rebuild] = true
    end

    op.separator 'Indexer options:'

    op.on '-a', '--[no-]amend', 'Update existing versions' do |bool|
      opts[:amend] = bool
    end

    op.on '-i', '--ignore PATH', "Don't check or index any file starting with PATH" do |path|
      opts[:ignore] ||= []
      opts[:ignore] << expand_path(path, base: basepath)
    end

    op.on '-o', "--output FILE=#{DEFAULTS[:output]}",
        'Set the output filename and path for the index' do |file|
      opts[:output] = expand_path(file.strip, base: basepath, relative: true)
    end

    op.on '--[no-]strict', 'Enable strict validation mode' do |bool|
      opts[:strict] = bool
    end

    op.on '-U', "--url-template TEMPLATE=#{DEFAULTS[:url_template]}",
        'Set the template for implicit download links' do |tpl|
      opts[:url_template] = tpl.strip
    end

    op.separator 'Repository metadata:'

    op.on '-n', '--name NAME', 'Set the name shown in ReaPack for this repository' do |name|
      opts[:name] = name.strip
    end

    op.on '-l', '--link LINK', 'Add or remove a website link' do |link|
      opts[:links] ||= Array.new
      opts[:links] << [:website, link.strip]
    end

    op.on '--screenshot-link LINK', 'Add or remove a screenshot link' do |link|
      opts[:links] ||= Array.new
      opts[:links] << [:screenshot, link.strip]
    end

    op.on '--donation-link LINK', 'Add or remove a donation link' do |link|
      opts[:links] ||= Array.new
      opts[:links] << [:donation, link.strip]
    end

    op.on '--ls-links', 'Display the link list then exit' do
      opts[:lslinks] = true
    end

    op.on '-A', '--about=FILE', 'Set the about content from a file' do |file|
      opts[:about] = expand_path file.strip, base: basepath, relative: true
    end

    op.on '--remove-about', 'Remove the about content from the index' do
      opts[:rmabout] = true
    end

    op.on '--dump-about', 'Dump the raw about content in RTF and exit' do
      opts[:dump_about] = true
    end

    op.separator 'Misc options:'

    op.on '--[no-]progress', 'Enable or disable progress information' do |bool|
      opts[:progress] = bool
    end

    op.on '-V', '--[no-]verbose', 'Activate diagnosis messages' do |bool|
      opts[:verbose] = bool
    end

    op.on '-C', '--[no-]commit', 'Select whether to commit the modified index' do |bool|
      opts[:commit] = bool
    end

    op.on '--prompt-commit', 'Ask at runtime whether to commit the index' do
      opts[:commit] = nil
    end

    op.on '-m', "--commit-template MESSAGE",
      'Customize the commit message. Supported placeholder: $changelog' do |msg|
      opts[:message] = msg
    end

    op.on '-W', '--warnings', 'Enable warnings' do
      opts[:warnings] = true
    end

    op.on '-w', '--no-warnings', 'Turn off warnings' do
      opts[:warnings] = false
    end

    op.on '-q', '--[no-]quiet', 'Disable almost all output' do
      opts[:warnings] = false
      opts[:progress] = false
      opts[:verbose] = false
      opts[:quiet] = true
    end

    op.on '--no-config', 'Bypass the configuration files' do
      opts[:noconfig] = true
    end

    op.on_tail '-v', '--version', 'Display version information' do
      puts op.ver
      throw :stop, true
    end

    op.on_tail '-h', '--help', 'Prints this help' do
      puts op
      throw :stop, true
    end
  end.parse! args

  if basepath && !args.empty?
    raise OptionParser::InvalidOption, "#{args.first}"
  end

  opts
rescue OptionParser::ParseError => e
  $stderr.puts "#{PROGRAM_NAME}: #{e.message}"
  $stderr.puts "Try '#{PROGRAM_NAME} --help' for more information."
  throw :stop, false
end
read_config() click to toggle source
# File lib/reapack/index/cli/options.rb, line 25
def read_config
  CONFIG_SEARCH.each {|dir|
    dir = expand_path dir
    path = File.expand_path '.reapack-index.conf', dir

    log 'reading configuration from %s' % path

    unless File.readable? path
      log 'configuration file is unreadable, skipping'
      next
    end

    opts = Shellwords.split File.read(path)
    @opts = parse_options(opts, dir).merge @opts
  }
rescue ArgumentError => e
  raise ReaPack::Index::Error, e.message
end
run() click to toggle source
# File lib/reapack/index/cli.rb, line 29
def run
  if @opts[:check]
    return do_check
  end

  if @opts[:lslinks]
    print_links
    return true
  end

  if @opts[:dump_about]
    print @index.about
    return true
  end

  do_name; do_about; eval_links; do_scan

  unless @index.modified?
    $stderr.puts 'Nothing to do!' unless @opts[:quiet]
    return true
  end

  # changelog will be cleared by Index#write!
  changelog = @index.changelog
  puts changelog unless @opts[:quiet]

  @index.write!
  commit changelog
  true
end

Private Instance Methods

bump_progress() click to toggle source
# File lib/reapack/index/cli.rb, line 306
def bump_progress
  @done += 1
  print_progress
end
check_name() click to toggle source
# File lib/reapack/index/cli.rb, line 180
def check_name
  if @index.name.empty?
    warn 'This index is unnamed. ' \
      'Run the following command to set a name of your choice:' \
      "\n  #{File.basename $0} --name 'FooBar Scripts'"
  end
end
commit(changelog) click to toggle source
# File lib/reapack/index/cli.rb, line 260
def commit(changelog)
  return unless case @opts[:commit]
  when false, true
    @opts[:commit]
  else
    prompt 'Commit the new index?'
  end

  message = @opts[:message].gsub '$changelog', changelog
  @git.create_commit message, [@index.path]
  $stderr.puts 'commit created'
end
do_about() click to toggle source
# File lib/reapack/index/cli.rb, line 188
def do_about
  path = @opts[:about]

  unless path
    @index.about = String.new if @opts[:rmabout]
    return
  end

  log "converting #{path} into RTF..."

  # look for the file in the working directory, not on the repository root
  @index.about = File.read(path)
rescue Errno::ENOENT => e
  warn '--about: ' + e.message.sub(' @ rb_sysopen', '')
rescue ReaPack::Index::Error => e
  warn e.message
end
do_check() click to toggle source
# File lib/reapack/index/cli.rb, line 206
def do_check
  check_name

  @index.clear
  failures = []

  pkgs = Hash[Find.find(@git.path).sort.map {|abs|
    rel = @git.relative_path abs
    @index.files << rel

    next if !File.file?(abs) || ignored?(abs) || !ReaPack::Index.is_package?(rel)

    [abs, rel]
  }.compact]

  # reiterate over the pkg list after registering every file
  pkgs.each_pair {|abs, rel|
    begin
      @index.scan rel, MetaHeader.from_file(abs)

      if @opts[:verbose]
        $stderr.puts '%s: passed' % rel
      elsif !@opts[:quiet]
        $stderr.print '.'
      end
    rescue ReaPack::Index::Error => e
      if @opts[:verbose]
        $stderr.puts '%s: failed' % rel
      elsif !@opts[:quiet]
        $stderr.print 'F'
      end

      failures << "%s failed:\n%s" % [rel, indent(e.message).yellow]
    end
  }

  $stderr.puts "\n" unless @opts[:quiet] || @opts[:verbose]

  failures.each_with_index {|msg, index|
    $stderr.puts unless @opts[:quiet] && index == 0
    $stderr.puts '%d) %s' % [index + 1, msg]
  }

  unless @opts[:quiet]
    $stderr.puts "\n"
    $stderr.puts 'Finished checks for %d package%s with %d failure%s' % [
      pkgs.size, pkgs.size == 1 ? '' : 's',
      failures.size, failures.size == 1 ? '' : 's',
    ]
  end

  failures.empty?
end
do_name() click to toggle source
# File lib/reapack/index/cli.rb, line 173
def do_name
  @index.name = @opts[:name] if @opts[:name]
  check_name
rescue ReaPack::Index::Error => e
  warn '--name: ' + e.message
end
do_scan() click to toggle source
# File lib/reapack/index/cli.rb, line 70
def do_scan
  commits = if @opts[:rebuild]
    @index.clear
    @git.commits
  elsif @opts[:scan] == false
    []
  elsif @opts[:scan].empty?
    @git.commits_since @index.last_commit
  else
    @index.auto_bump_commit = false

    # call process_commit only once per commit instead of once per --scan argument
    commits = Hash.new
    @opts[:scan].map {|hash|
      files = @git.last_commits_for @git.relative_path(hash)
      if !files.empty?
        files.each {|commit, files|
          commits[commit] = Array.new unless commits.has_key? commit # keep nil
          commits[commit].concat files unless commits[commit].nil?
        }
      elsif c = @git.get_commit(hash)
        commits[c] = nil
      else
        $stderr.puts "--scan: bad file or revision: '%s'" % hash
        throw :stop, false
      end
    }
    commits.to_a
  end

  unless commits.empty?
    progress_wrapper commits.size do
      commits.each {|args| process_commit *args }
    end
  end
end
expand_path(path, **options) click to toggle source
# File lib/reapack/index/cli.rb, line 331
def expand_path(path, **options)
  # expand from the repository root or from the current directory if
  # the repository is not yet initialized
  path = File.expand_path path, options[:base] || (@git ? @git.path : Dir.pwd)

  if options[:relative]
    root = Pathname.new Dir.pwd
    file = Pathname.new path

    file.relative_path_from(root).to_s
  else
    path
  end
end
ignored?(path) click to toggle source
# File lib/reapack/index/cli.rb, line 321
def ignored?(path)
  path = path + '/'

  @opts[:ignore].each {|pattern|
    return true if path.start_with? pattern + '/'
  }

  false
end
indent(input) click to toggle source
# File lib/reapack/index/cli.rb, line 346
def indent(input)
  output = String.new
  input.each_line {|l| output += "\x20\x20#{l}" }
  output
end
log(line) click to toggle source
# File lib/reapack/index/cli.rb, line 284
def log(line)
  $stderr.puts line if @opts[:verbose]
end
print_progress() click to toggle source
process_commit(commit, files = nil) click to toggle source
# File lib/reapack/index/cli.rb, line 107
def process_commit(commit, files = nil)
  if @opts[:verbose]
    log 'processing %s: %s' % [commit.short_id, commit.summary]
  end

  @index.commit = commit.id
  @index.time = commit.time
  @index.files = commit.filelist

  commit.each_diff
    .select {|diff|
      (files.nil? || files.include?(diff.file)) &&
        (not ignored? expand_path(diff.file)) &&
        ReaPack::Index.is_package?(diff.file)
    }
    .sort_by {|diff|
      diff.status == :deleted || diff.new_header[:noindex] ? 0 : 1
    }
    .each {|diff| process_diff diff }
ensure
  bump_progress
end
process_diff(diff) click to toggle source
# File lib/reapack/index/cli.rb, line 130
def process_diff(diff)
  log "-> indexing #{diff.status} file #{diff.file}"

  if diff.status == :deleted
    @index.remove diff.file
  else
    begin
      @index.scan diff.file, diff.new_header
    rescue ReaPack::Index::Error => e
      warn "#{diff.file}:\n#{indent e.message}"
    end
  end
end
progress_wrapper(total, &block) click to toggle source
# File lib/reapack/index/cli.rb, line 299
def progress_wrapper(total, &block)
  @done, @total = 0, total
  print_progress
  block[]
  $stderr.print "\n" if @add_nl
end
prompt(question, &block) click to toggle source
# File lib/reapack/index/cli.rb, line 273
def prompt(question, &block)
  $stderr.print "#{question} [y/N] "
  answer = $stdin.getch
  $stderr.puts answer

  yes = answer.downcase == 'y'
  block[] if block_given? && yes

  yes
end
set_url_template() click to toggle source
# File lib/reapack/index/cli.rb, line 61
def set_url_template
  tpl = @opts[:url_template]
  is_custom = tpl != DEFAULTS[:url_template]

  @index.url_template = is_custom ? tpl : @git.guess_url_template
rescue ReaPack::Index::Error => e
  warn '--url-template: ' + e.message if is_custom
end
warn(line) click to toggle source
# File lib/reapack/index/cli.rb, line 288
def warn(line)
  return unless @opts[:warnings]

  if @add_nl
    $stderr.puts
    @add_nl = false
  end

  $stderr.puts "warning: #{line}".yellow
end