swish/commit

ADDED: CSV client support

authorJan Wielemaker
Fri Mar 6 16:16:11 2015 +0100
committerJan Wielemaker
Fri Mar 6 16:16:11 2015 +0100
commitea259dc12482779ddd4434137e9e54362ee73be6
treeaffde956589302cc6d9b4c9d3795a93177bed444
parentb080ebfb870634cdeffd388d68ecf44bd8c4341c
Diff style: patch stat
diff --git a/Makefile b/Makefile
index 392accd..095b54a 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,10 @@
 # Create a ClioPatria SWISH package from the SWISH distribution.
 
 FONTDIR=web/bower_components/bootstrap/dist/fonts
-DIRS=lib/swish lib/swish/render web/icons web/help $(FONTDIR)
+DIRS=lib/swish lib/swish/render web/icons web/help client $(FONTDIR)
 SWISHLIB=storage.pl page.pl help.pl examples.pl config.pl gitty.pl \
 	 highlight.pl render.pl template_hint.pl search.pl form.pl \
-	 include.pl
+	 include.pl csv.pl
 RENDER=table.pl
 LIBS=	$(addprefix lib/swish/, $(SWISHLIB)) \
 	$(addprefix lib/swish/render/, $(RENDER))
@@ -16,14 +16,19 @@ HELP=$(addprefix web/help/, $(notdir $(wildcard src/web/help/*.html)))
 FONTFILES=glyphicons-halflings-regular.ttf \
 	  glyphicons-halflings-regular.woff
 FONTS=$(addprefix $(FONTDIR)/, $(FONTFILES))
+CLIENTFILES=swish-ask.sh
+CLIENTS=$(addprefix client/, $(CLIENTFILES))
 
-all:	$(DIRS) $(LIBS) $(JS) $(CSS) $(ICONS) $(HELP) $(FONTS)
+all:	$(DIRS) $(LIBS) $(JS) $(CSS) $(ICONS) $(HELP) $(FONTS) $(CLIENTS)
 
 $(DIRS):
 	mkdir -p $@
 
 lib/swish/%: src/lib/%
 	rsync -u $< $@
+client/%: src/client/%
+	sed -e 's/:3050}/:3020}/' -e 's/-prolog}/-rdf}/' $< > $@
+	chmod +x $@
 
 web/js/swish-min.js: src/web/js/swish-min.js
 	rsync -u $< $@
diff --git a/applications/swish.pl b/applications/swish.pl
index ff6c135..bee004e 100644
--- a/applications/swish.pl
+++ b/applications/swish.pl
@@ -34,11 +34,13 @@
 :- use_module(library(http/http_dispatch)).
 :- use_module(library(http/http_server_files)).
 :- use_module(library(http/http_json)).
+:- use_module(rdfql(sparql_csv_result)).
 
 :- use_module(library(swish/config)).
 :- use_module(library(swish/page), []).
 :- use_module(library(swish/storage)).
 :- use_module(library(swish/include)).
+:- use_module(library(swish/csv)).
 :- use_module(library(swish/examples)).
 :- use_module(library(swish/help)).
 :- use_module(library(swish/highlight)).
@@ -68,6 +70,21 @@ swish_config:config(tabled_results, true).
 swish_config:config(application,    swish).
 
 
+		 /*******************************
+		 *	        CSV		*
+		 *******************************/
+
+:- multifile swish_csv:write_answers/2.
+
+swish_csv:write_answers(Answers, VarTerm) :-
+        Answers = [H|_],
+        functor(H, rdf, _), !,
+        sparql_write_csv_result(
+            current_output,
+            select(VarTerm, Answers),
+            []).
+
+
                  /*******************************
                  *   CREATE SWISH APPLICATION   *
                  *******************************/
diff --git a/client/swish-ask.sh b/client/swish-ask.sh
new file mode 100755
index 0000000..d009dd5
--- /dev/null
+++ b/client/swish-ask.sh
@@ -0,0 +1,86 @@
+#!/bin/bash
+#
+# Ask information from a Pengines/SWISH server from the shell.
+#
+# This program allows you to download query  results from a SWISH server
+# as CSV data.
+
+server=${SWISH_SERVER-http://localhost:3020}
+srctext=
+format=${SWISH_FORMAT-rdf}
+program=$(basename $0)
+
+usage()
+{
+cat << _EOM_
+Usage: $program "[--server=URL] [--format=rdf|prolog]" file.pl ... projection query
+
+Where
+
+  - server is by default "$server".  Environment: SWISH_SERVER
+  - format is by default "$format".  Environment: SWISH_FORMAT
+  - file.pl ... are files saved in SWISH.  Zero or more files are allowed
+  - projection is a comma-separated list of Prolog variables that define
+    the CSV columns.
+  - query is a Prolog goal using the variables from projection.
+
+For example
+
+  $program X 'X is 1<<100'
+  X
+  1267650600228229401496703205376
+
+  $program factbook.pl Code,Country 'country(Country,Code)'
+  Code,Country
+  af,http://www4.wiwiss.fu-berlin.de/factbook/resource/Afghanistan
+  ax,http://www4.wiwiss.fu-berlin.de/factbook/resource/Akrotiri
+  ...
+_EOM_
+}
+
+done=false
+while [ $done = false ]; do
+    case "$1" in
+        --server=*)
+            server=$(echo $1 | sed 's/.*=//')
+            shift
+            ;;
+	--format=*)
+	    format=$(echo $1 | sed 's/.*=//')
+	    case "$format" in
+	        rdf|prolog)
+		    ;;
+		*)
+		    usage
+		    exit 1
+		    ;;
+	    esac
+            shift
+            ;;
+	*.pl)
+            script=$(echo $1 | sed 's/.*=//')
+	    srctext+=":- include('$script'). "
+	    shift
+	    ;;
+	*)
+	    done=true
+	    ;;
+    esac
+done
+
+vars="$1"
+query="$2"
+
+if [ -z "$vars" -o -z "$query" ]; then
+  usage
+  exit 1
+fi
+
+curl -s \
+     -d ask="$query" \
+     -d template="$format($vars)" \
+     -d application="swish" \
+     -d src_text="$srctext" \
+     -d format=csv \
+     -d chunk=100000000 \
+     $server/pengine/create
diff --git a/lib/swish/csv.pl b/lib/swish/csv.pl
new file mode 100644
index 0000000..88b484b
--- /dev/null
+++ b/lib/swish/csv.pl
@@ -0,0 +1,109 @@
+/*  Part of SWI-Prolog
+
+    Author:        Jan Wielemaker
+    E-mail:        J.Wielemaker@cs.vu.nl
+    WWW:           http://www.swi-prolog.org
+    Copyright (C): 2015, VU University Amsterdam
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License
+    as published by the Free Software Foundation; either version 2
+    of the License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+    As a special exception, if you link this library with other files,
+    compiled with a Free Software compiler, to produce an executable, this
+    library does not by itself cause the resulting executable to be covered
+    by the GNU General Public License. This exception does not however
+    invalidate any other reasons why the executable file might be covered by
+    the GNU General Public License.
+*/
+
+:- module(swish_csv, []).
+:- use_module(library(pengines), []).
+:- use_module(library(pairs)).
+:- use_module(library(csv)).
+:- use_module(library(apply)).
+:- use_module(library(pprint)).
+
+/** <module> Support CSV output from a Pengines server
+
+This module defines the result-format  `csv`   for  Pengines.  It allows
+SWISH users to post a query against the   core  Prolog system or a saved
+SWISH program and obtain the results using   a simple web client such as
+`curl`. An example shell script is provided in =client/swish-ask.sh=.
+
+@tbd	Provide streaming output
+*/
+
+:- multifile
+	pengines:write_result/3,
+	write_answers/2.
+
+%%	pengines:write_result(+Format, +Event, +VarNames) is semidet.
+%
+%	Hook into library(pengines) that  makes   pengines  support  CSV
+%	output.
+
+pengines:write_result(csv, Event, VarNames) :-
+	csv(Event, VarNames).
+
+csv(create(_Id, Features), VarNames) :- !,
+	memberchk(answer(Answer), Features),
+	csv(Answer, VarNames).
+csv(destroy(_Id, Wrapped), VarNames) :- !,
+	csv(Wrapped, VarNames).
+csv(success(_Id, Answers, _Time, _More), VarNames) :- !,
+	VarTerm =.. [row|VarNames],
+	success(Answers, VarTerm).
+csv(error(_Id, Error), _VarNames) :- !,
+	message_to_string(Error, Msg),
+	format('Status: 400 Bad request~n'),
+	format('Content-type: text/plain~n~n'),
+	format('ERROR: ~w~n', [Msg]).
+csv(output(_Id, message(_Term, _Class, HTML, _Where)), _VarNames) :- !,
+	format('Status: 400 Bad request~n'),
+	format('Content-type: text/html~n~n'),
+	format('<html>~n~s~n</html>~n', [HTML]).
+csv(Event, _VarNames) :-
+	print_term(Event, [output(user_error)]).
+
+success(Answers, VarTerm) :-
+	write_answers(Answers, VarTerm), !.
+success(Answers, VarTerm) :-
+	maplist(csv_answer, Answers, Rows),
+	format('Content-type: text/csv~n~n'),
+	csv_write_stream(current_output, [VarTerm|Rows], []).
+
+csv_answer(JSON, Row) :-
+	is_dict(JSON), !,
+	dict_pairs(JSON, _, Pairs),
+	pairs_values(Pairs, Values),
+	maplist(csv_value, Values, CVSValues),
+	Row =.. [row|CVSValues].
+csv_answer(RowIn, Row) :-
+	compound(RowIn), !,
+	RowIn =.. [_|Values],
+	maplist(csv_value, Values, CVSValues),
+	Row =.. [row|CVSValues].
+
+csv_value(Var, '') :-
+	var(Var), !.
+csv_value(Number, Number) :-
+	number(Number), !.
+csv_value(Atom, Atom) :-
+	atom(Atom), !.
+csv_value(String, String) :-
+	string(String), !.
+csv_value(Term, Value) :-
+	term_string(Term, Value).
+
+