class RDF::CLI

Individual formats can modify options by updating {Reader.options} or {Writer.options}. Format-specific commands are taken from {Format.cli_commands} for each loaded format, which returns an array of lambdas taking arguments and options.

Other than `help`, all commands parse an input file.

Multiple commands may be added in sequence to execute a pipeline.

@example Creating Reader-specific options:

class Reader
  def self.options
    [
      RDF::CLI::Option.new(
        symbol: :canonicalize,
        datatype: TrueClass,
        on: ["--canonicalize"],
        description: "Canonicalize input/output.") {true},
      RDF::CLI::Option.new(
        symbol: :uri,
        datatype: RDF::URI,
        on: ["--uri STRING"],
        description: "URI.") {|v| RDF::URI(v)},
    ]
  end

@example Creating Format-specific commands:

class Format
  def self.cli_commands
    {
      count: {
        description: "",
        parse: true,
        lambda: ->(argv, opts) {}
      },
    }
  end

@example Adding a command manually

class MyCommand
  RDF::CLI.add_command(:count, description: "Count statements") do |argv, opts|
    count = 0
    RDF::CLI.parse(argv, opts) do |reader|
      reader.each_statement do |statement|
        count += 1
      end
    end
    $stdout.puts "Parsed #{count} statements"
  end
end

Format-specific commands should verify that the reader and/or output format are appropriate for the command.

Constants

COMMANDS

@private

Attributes

repository[RW]

Repository containing parsed statements @return [RDF::Repository]

Public Class Methods

abort(msg) click to toggle source

@param [String] msg @return [void]

# File lib/rdf/cli.rb, line 458
def self.abort(msg)
  Kernel.abort "#{basename}: #{msg}"
end
add_command(command, options = {}, &block) click to toggle source

Add a command.

@param [#to_sym] command @param [Hash{Symbol => String}] options @option options [String] description @option options [String] help string to display for help @option options [Boolean] parse parse input files in to Repository, or not. @option options [Array<RDF::CLI::Option>] options specific to this command @yield argv, opts @yieldparam [Array<String>] argv @yieldparam [Hash] opts @yieldreturn [void]

# File lib/rdf/cli.rb, line 412
def self.add_command(command, options = {}, &block)
  options[:lambda] = block if block_given?
  COMMANDS[command.to_sym] ||= options
end
basename() click to toggle source

@return [String]

# File lib/rdf/cli.rb, line 220
def self.basename() File.basename($0) end
commands() click to toggle source

@return [Array<String>] list of executable commands

# File lib/rdf/cli.rb, line 385
def self.commands
  # First, load commands from other formats
  unless @commands_loaded
    RDF::Format.each do |format|
      format.cli_commands.each do |command, options|
        options = {lambda: options} unless options.is_a?(Hash)
        add_command(command, options)
      end
    end
    @commands_loaded = true
  end
  COMMANDS.keys.map(&:to_s).sort
end
exec(args, options = {}) click to toggle source

Execute one or more commands, parsing input as necessary

@param [Array<String>] args @return [Boolean]

# File lib/rdf/cli.rb, line 342
def self.exec(args, options = {})
  out = options[:output] || $stdout
  out.set_encoding(Encoding::UTF_8) if out.respond_to?(:set_encoding) && RUBY_PLATFORM == "java"
  cmds, args = args.partition {|e| commands.include?(e.to_s)}

  if cmds.empty?
    usage(options.fetch(:option_parser, self.options))
    abort "No command given"
  end

  if cmds.first == 'help'
    on_cmd = cmds[1]
    if on_cmd && COMMANDS.fetch(on_cmd.to_sym, {})[:help]
      usage(options.fetch(:option_parser, self.options), banner: "Usage: #{self.basename.split('/').last} #{COMMANDS[on_cmd.to_sym][:help]}")
    else
      usage(options.fetch(:option_parser, self.options))
    end
    return
  end

  @repository = RDF::Repository.new

  # Parse input files if any command requires it
  if cmds.any? {|c| COMMANDS[c.to_sym][:parse]}
    start = Time.new
    count = 0
    self.parse(args, options) do |reader|
      @repository << reader
    end
    secs = Time.new - start
    $stdout.puts "Parsed #{repository.count} statements with #{@readers.join(', ')} in #{secs} seconds @ #{count/secs} statements/second."
  end

  # Run each command in sequence
  cmds.each do |command|
    COMMANDS[command.to_sym][:lambda].call(args, options)
  end
rescue ArgumentError => e
  abort e.message
end
formats(reader: false, writer: false) click to toggle source

@return [Array<String>] list of available formats

# File lib/rdf/cli.rb, line 419
def self.formats(reader: false, writer: false)
  f = RDF::Format.sort_by(&:to_sym).each.
    select {|f| (reader ? f.reader : (writer ? f.writer : (f.reader || f.writer)))}.
    inject({}) do |memo, reader|
      memo.merge(reader.to_sym => reader.name)
  end
  sym_len = f.keys.map {|k| k.to_s.length}.max
  f.map {|s, t| "%*s: %s" % [sym_len, s, t]}
end
options(&block) click to toggle source

@yield [options] @yieldparam [OptionParser] @return [OptionParser]

# File lib/rdf/cli.rb, line 226
def self.options(&block)
  options = OptionParser.new
  logger = Logger.new($stderr)
  logger.level = Logger::ERROR
  logger.formatter = lambda {|severity, datetime, progname, msg| "#{severity} #{msg}\n"}
  opts = options.options = {
    debug:          false,
    evaluate:       nil,
    format:         nil,
    output:         $stdout,
    output_format:  :ntriples,
    logger:         logger
  }

  # Add default Reader and Writer options
  RDF::Reader.options.each do |cli_opt|
    next if opts.has_key?(cli_opt.symbol)
    on_args = cli_opt.on || []
    on_args << cli_opt.description if cli_opt.description
    options.on(*on_args) do |arg|
      opts[cli_opt.symbol] = cli_opt.call(arg)
    end
  end
  RDF::Writer.options.each do |cli_opt|
    next if opts.has_key?(cli_opt.symbol)
    on_args = cli_opt.on || []
    on_args << cli_opt.description if cli_opt.description
    options.on(*on_args) do |arg|
      opts[cli_opt.symbol] = cli_opt.call(arg)
    end
  end

  # Command-specific options
  if block_given?
    case block.arity
      when 1 then block.call(options)
      else options.instance_eval(&block)
    end
  end
  options.banner = "Usage: #{self.basename} command+ [options] [args...]"

  options.on('-d', '--debug',   'Enable debug output for troubleshooting.') do
    opts[:logger].level = Logger::DEBUG
  end

  options.on("-e", "--evaluate STRING", "Evaluate argument as RDF input, if no files are specified") do |arg|
    opts[:evaluate] = arg
  end

  options.on("--input-format FORMAT", "--format FORMAT", "Format of input file, uses heuristic if not specified") do |arg|
    unless reader = RDF::Reader.for(arg.downcase.to_sym)
      self.abort "No reader found for #{arg.downcase.to_sym}. Available readers:\n  #{self.formats(reader: true).join("\n  ")}"
    end

    # Add format-specific reader options
    reader.options.each do |cli_opt|
      next if opts.has_key?(cli_opt.symbol)
      on_args = cli_opt.on || []
      on_args << cli_opt.description if cli_opt.description
      options.on(*on_args) do |arg|
        opts[cli_opt.symbol] = cli_opt.call(arg)
      end
    end
    opts[:format] = arg.downcase.to_sym
  end

  options.on("-o", "--output FILE", "File to write output, defaults to STDOUT") do |arg|
    opts[:output] = File.open(arg, "w")
  end

  options.on("--output-format FORMAT", "Format of output file, defaults to NTriples") do |arg|
    unless writer = RDF::Writer.for(arg.downcase.to_sym)
      self.abort "No writer found for #{arg.downcase.to_sym}. Available writers:\n  #{self.formats(writer: true).join("\n  ")}"
    end

    # Add format-specific writer options
    writer.options.each do |cli_opt|
      next if opts.has_key?(cli_opt.symbol)
      on_args = cli_opt.on || []
      on_args << cli_opt.description if cli_opt.description
      options.on(*on_args) do |arg|
        opts[cli_opt.symbol] = cli_opt.call(arg)
      end
    end
    opts[:output_format] = arg.downcase.to_sym
  end

  options.on_tail("-h", "--help", "Show this message") do
    self.usage(options)
    exit(0)
  end

  begin
    options.parse!
  rescue OptionParser::InvalidOption => e
    abort e
  end

  options
end
parse(files, options = {}) { |reader| ... } click to toggle source

Parse each file, $stdin or specified string in `options` yielding a reader

@param [Array<String>] files @yield [reader] @yieldparam [RDF::Reader] @return [nil]

# File lib/rdf/cli.rb, line 437
def self.parse(files, options = {}, &block)
  if files.empty?
    # If files are empty, either use options[:execute]
    input = options[:evaluate] ? StringIO.new(options[:evaluate]) : $stdin
    input.set_encoding(options.fetch(:encoding, Encoding::UTF_8))
    RDF::Reader.for(options[:format] || :ntriples).new(input, options) do |reader|
      yield(reader)
    end
  else
    files.each do |file|
      RDF::Reader.open(file, options) do |reader|
        (@readers ||= []) << reader.class.to_s
        yield(reader)
      end
    end
  end
end
usage(options, banner: nil) click to toggle source

Output usage message

# File lib/rdf/cli.rb, line 329
def self.usage(options, banner: nil)
  options.banner = banner if banner
  $stdout.puts options
  $stdout.puts "Note: available commands and options may be different depending on selected --input-format and/or --output-format."
  $stdout.puts "Available commands:\n\t#{self.commands.join("\n\t")}"
  $stdout.puts "Available formats:\n\t#{(self.formats).join("\n\t")}"
end