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 containing parsed statements @return [RDF::Repository]
Public Class Methods
@param [String] msg @return [void]
# File lib/rdf/cli.rb, line 458 def self.abort(msg) Kernel.abort "#{basename}: #{msg}" end
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
@return [String]
# File lib/rdf/cli.rb, line 220 def self.basename() File.basename($0) end
@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
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
@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
@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 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
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