#!/usr/bin/env ruby require 'rubygems' $:.unshift("../../lib", __FILE__) require 'logger' require 'sparql' begin require 'linkeddata' rescue LoadError require 'rdf/ntriples' end require 'getoptlong' def display_results(res, **options) puts res.inspect if options[:verbose] puts case res when RDF::Graph then res.dump(options.fetch(:format, :ttl), base_uri: query.base_uri, prefixes: query.prefixes, standard_prefixes: true) when RDF::Literal then res.inspect else case options[:format] when :json then res.to_json when :html then res.to_html when :xml then res.to_xml when :csv then res.to_csv when :tsv then res.to_tsv else res.map {|s| s.bindings.map {|k,v| "#{k}: #{v}"}}.join("\n") end end end def run(input, **options) if options[:debug] puts "input graph:\n#{options[:dataset].dump(:trig, standard_prefixes: true)}\n" if options[:dataset] puts "query:\n#{input}\n" end options[:dataset] ||= RDF::Repository.new if options[:verbose] puts ("\nSPARQL:\n" + input) end query = if options[:sse] SPARQL::Algebra.parse(input, logger: options[:logger], update: options[:update]) else # Only do grammar debugging if we're generating SSE SPARQL::Grammar.parse(input, **options) end puts ("\nSSE:\n" + query.to_sse) if options[:debug] if options[:parse_only] case options[:format] when :sparql puts ("\nSPARQL:\n" + query.to_sparql) when nil, :sse puts ("\nSSE:\n" + query.to_sse) else $stderr.puts "Unknown output format for parsing: #{options[:format]}. Use 'sse' or 'sparql'" end else res = query.execute(options[:dataset], logger: options[:logger]) display_results(res, **options) end end def server(options) require 'sinatra/sparql' repository = options.fetch(:dataset, RDF::Repository.new) app = Sinatra.new do register Sinatra::SPARQL set :repository, repository before do options[:logger].info "#{request.request_method} [#{request.path_info}], " + params.merge(Accept: request.accept.map(&:to_s)).map {|k,v| "#{k}=#{v}" unless k.to_s == "content"}.join(" ") end get '/' do if params["query"] query = params["query"].to_s.match(/^http:/) ? RDF::Util::File.open_file(params["query"]) : ::CGI.unescape(params["query"].to_s) SPARQL.execute(query, settings.repository) else settings.sparql_options.replace(standard_prefixes: true) settings.sparql_options.merge!(:prefixes => { ssd: "http://www.w3.org/ns/sparql-service-description#", void: "http://rdfs.org/ns/void#" }) service_description(repo: settings.repository, endpoint: url) end end post '/' do SPARQL.execute(params['query'], settings.repository) end end Rack::Server.start(app: app, Port: options.fetch(:port, 9292)) rescue LoadError $stderr.puts "Running SPARQL server requires Rack to be in environment: #{$!.message}" end def usage puts "Usage: #{File.basename($0)} execute [options] query-file Execute a query against the specified dataset" puts " #{File.basename($0)} parse [options] query-file Parse a query into SPARQL S-Expressions (SSE)" puts " #{File.basename($0)} query [options] end-point query-file Run the query against a remote end-point" puts " #{File.basename($0)} server [options] dataset-file Start a server initialized from the specified dataset" puts "Options:" puts " --dataset: File containing RDF graph or dataset" puts " --debug: Display detailed debug output" puts " --execute,-e: Use option argument as the SPARQL input if no query-file given" puts " --format: Output format for results (json, xml, csv, tsv, html, sparql, sse, or another RDF format)" puts " --port,-p Port on which to run server; defaults to 9292" puts " --sse: Query input is in SSE format" puts " --update: Process query as a SPARQL Update" puts " --verbose: Display details of processing" puts " --help,-?: This message" exit(0) end cmd, input = ARGV.shift, nil opts = GetoptLong.new( ["--dataset", GetoptLong::REQUIRED_ARGUMENT], ["--debug", GetoptLong::NO_ARGUMENT], ["--execute", "-e", GetoptLong::REQUIRED_ARGUMENT], ["--format", GetoptLong::REQUIRED_ARGUMENT], ["--port", "-p", GetoptLong::REQUIRED_ARGUMENT], ["--sse", GetoptLong::NO_ARGUMENT], ["--update", GetoptLong::NO_ARGUMENT], ["--verbose", GetoptLong::NO_ARGUMENT], ["--help", "-?", GetoptLong::NO_ARGUMENT] ) logger = Logger.new(STDERR) logger.level = Logger::WARN logger.formatter = lambda {|severity, datetime, progname, msg| "%5s %s\n" % [severity, msg]} options = { dataset: RDF::Repository.new, logger: logger, } opts.each do |opt, arg| case opt when '--dataset' then options[:dataset].load(arg, rdfstar: true) when '--debug' then options[:debug] = true ; logger.level = Logger::DEBUG when '--execute' then input = arg when '--format' then options[:format] = arg.to_sym when '--port' then options[:port] = arg.to_i when '--sse' then options[:sse] = true when '--update' then options[:update] = true when '--verbose' then options[:verbose] = true ; logger.level = Logger::INFO when "--help" then usage end end unless %w(execute query parse server help).include?(cmd) $stderr.puts "Unrecognized command #{cmd}" usage end case cmd when 'execute', 'parse' options[:parse_only] = true if cmd == 'parse' input ||= ARGV.empty? ? $stdin.read : RDF::Util::File.open_file(ARGV.first).read run(input, **options) when 'query' endpoint = ARGV.shift unless endpoint $stderr.puts "Expected SPARQL endpoint URL" usage end input ||= ARGV.empty? ? $stdin.read : RDF::Util::File.open_file(ARGV.first).read SPARQL::Client.new(endpoint) do |client| res = client.query(input) display_results(res, **options) end when 'server' if data_file = ARGV.shift options[:dataset] = RDF::Repository.load(data_file) end server(options) else usage end