diff --git a/phpunit.xml b/phpunit.xml
index 248d904..1ba583d 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,6 +1,6 @@
-
+
tests
diff --git a/tests/Webservice/Formatter/FormatterTest.php b/tests/Webservice/Formatter/FormatterTest.php
new file mode 100644
index 0000000..f9895db
--- /dev/null
+++ b/tests/Webservice/Formatter/FormatterTest.php
@@ -0,0 +1,78 @@
+ class)
+ */
+ protected static function registerFormats(array $formats) {
+ foreach($formats as $f) {
+ self::$formats[$f] = get_called_class();
+ }
+ }
+
+ /**
+ * @return Formatter The formatter to use for this request
+ */
+ public static function getFormatter() {
+ self::loadFormatters();
+ $format = self::getFormatFromHeader();
+
+ return new $format();
+ }
+
+ /**
+ * Load all formatters in the current directory
+ */
+ private static function loadFormatters() {
+ preg_match('/(.+)\\\([a-zA-Z0-9]+)/', get_called_class(), $parts);
+ $us = $parts[2];
+ $namespace = $parts[1];
+
+ $base = __DIR__.'/';
+ $ext = '.php';
+ $files = glob(sprintf('%s%s%s', $base, '*', $ext));
+ foreach($files as $f) {
+ $c = str_replace(array($base, $ext), '', $f);
+ if($c !== $us) {
+ $c = $namespace.'\\'.$c;
+ call_user_func(array($c, 'init'));
+ }
+ }
+ }
+
+ /**
+ * @return string The class name to instantiate in accord to the Accept header
+ */
+ private static function getFormatFromHeader() {
+ //TODO this is ugly
+ return 'BSR\Webservice\Formatter\Json';
+ if(isset($_SERVER['HTTP_ACCEPT'])) {
+ $formats = array_map(function($f) {
+ $parts = explode(';', $f);
+ $parts[1] = (isset($parts[1]) ? (float) preg_replace('/[^0-9\.]/', '', $parts[1]) : 1.0) * 100;
+ return $parts;
+ }, explode(',', $_SERVER['HTTP_ACCEPT']));
+
+ usort($formats, function($a, $b) { return $b[1] - $a[1]; });
+
+ foreach($formats as $f) {
+ if(isset(self::$formats[$f[0]])) {
+ return self::$formats[$f[0]];
+ }
+ }
+
+ }
+
+ return 'BSR\Webservice\Formatter\Json';
+ }
+
+ /**
+ * Output the content for the given data
+ * @param array $data
+ */
+ abstract public function render($data);
+}
diff --git a/tests/Webservice/Formatter/HtmlTest.php b/tests/Webservice/Formatter/HtmlTest.php
new file mode 100644
index 0000000..e545d60
--- /dev/null
+++ b/tests/Webservice/Formatter/HtmlTest.php
@@ -0,0 +1,157 @@
+ $limit) {
+ $v = substr($v, 0, $limit).$ellipsis;
+ }
+ return $v;
+ }
+
+ protected function formatValue($v) {
+ if(is_numeric($v)) {
+ return $v;
+ }
+ if(is_bool($v)) {
+ return '';
+ }
+ if(is_string($v) && strpos($v, 'http') !== false) {
+ $url = $v;
+ $v = $this->truncate($v, '...');
+ return "$v";
+ }
+
+ return $this->truncate(print_r($v, true));
+ }
+
+ protected function result($data) {
+ // the format is array('result' => array('function name' => DATA))
+ // so take the first element of the 'result' array
+ $func = key($data['result']);
+ $data = reset($data['result']);
+ $data = is_array($data) ? $data : array();
+ $title = $func;
+
+ $content = '';
+ $after = '';
+
+ if($func == 'NewSearch') {
+ $content .= 'Count : '.$data['count'].'
';
+ $after .= 'Extra :
'.print_r($data['facets'], true).'
';
+
+ unset($data['count']);
+ unset($data['facets']);
+ }
+
+ $first = reset($data);
+ $single = ! is_array($first);
+ $columns = array();
+
+ $content .= '';
+ if($single) {
+ $content .= "| Field | Value |
";
+ } else {
+ $columns = array_keys($first);
+
+ $title .= ' ('.count($data).' results)';
+
+ $content .= '';
+ foreach($columns as $k) {
+ $content .= "| $k | ";
+ }
+ $content .= '
';
+ }
+ $content .= '';
+ if($single) {
+ foreach($data as $k => $v) {
+ $content .= "| $k | ".$this->formatValue($v)." |
";
+ }
+ } else {
+ foreach($data as $row) {
+ $content .= '';
+ foreach($columns as $c) {
+ $content .= '| '.$this->formatValue(isset($row[$c]) ? $row[$c] : '').' | ';
+ }
+ $content .= '
';
+ }
+ }
+ $content .= '
'.$after;
+
+ return array(
+ 'title' => $title,
+ 'content' => $content,
+ 'status' => 'success',
+ );
+ }
+
+ protected function error($data) {
+ $code = $data['error']['code'];
+ $name = $data['error']['name'];
+ $msg = $data['error']['reason'];
+
+ return array(
+ 'title' => 'Error',
+ 'content' => "[$code] $name : $msg
",
+ 'status' => 'warning',
+ );
+ }
+
+ protected function failure($data) {
+ $code = $data['failure']['code'];
+ $name = $data['failure']['reason'];
+
+ return array(
+ 'title' => 'Failure',
+ 'content' => "[$code] $name
",
+ 'status' => 'danger',
+ );
+ }
+
+ public function render($data)
+ {
+ $type = key($data);
+
+ if (method_exists($this, $type)) {
+ $context = call_user_func_array(array($this, $type), array($data));
+ } else {
+ $context = array(
+ 'title' => 'Formatter error',
+ 'content' => 'Unable to render this
',
+ 'status' => 'info',
+ );
+ }
+ $info = Logger::data();
+ $context['time'] = $info['time'];
+
+ $panel = static::template($context, 'panel');
+
+ if(isset($data['extra'])) {
+ $panel .= $data['extra'];
+ }
+
+ echo static::template(array(
+ 'version' => $info['version'],
+ 'title' => $context['title'],
+ 'content' => $panel,
+ ));
+ }
+
+ public static function template(array $context = array(), $template = 'layout') {
+ $html = file_get_contents(sprintf('templates/%s.html', $template));
+
+ $patterns = array_map(function($p) { return "{{ $p }}"; }, array_keys($context));
+ return str_replace($patterns, array_values($context), $html);
+ }
+}
diff --git a/tests/Webservice/Formatter/JsonTest.php b/tests/Webservice/Formatter/JsonTest.php
new file mode 100644
index 0000000..45d46a8
--- /dev/null
+++ b/tests/Webservice/Formatter/JsonTest.php
@@ -0,0 +1,17 @@
+getDocComment();
+ $params = $rm->getParameters();
+
+ preg_match_all('/@param\s+(?P[^\s]*?)\s*\$(?P[^\s]+?)\s+(?P[\w\s]*)/', $doc, $parametersDoc, PREG_SET_ORDER);
+ preg_match('/@return\s+(?P[^\s]*?)\s+(?P[\w\s]*)/', $doc, $returnDoc);
+
+ $doc = array_filter(array_map(function($l) {
+ $l = trim($l, " \t\n\r\0\x0B");
+ if(strpos($l, '/**') === 0 || strpos($l, '*/') === 0) {
+ $l = '';
+ }
+ $l = trim($l, "* ");
+ if(strpos($l, '@') === 0) {
+ $l = '';
+ }
+ return $l;
+ }, explode("\n", $doc)));
+ $doc = nl2br(implode("\n", $doc));
+
+ $paramsHtml = '';
+ foreach($params as $p) {
+ foreach($parametersDoc as $d) {
+ if(isset($d['name']) && $p->name == $d['name']) {
+ $paramsHtml .= Html::template(array(
+ 'name' => $p->name,
+ 'optional' => $p->isDefaultValueAvailable() ? '(optional)' : '',
+ 'type' => isset($d['type']) ? $d['type'] : '',
+ 'doc' => isset($d['doc']) ? $d['doc'] : '',
+ ), 'param_help');
+ }
+ }
+ }
+
+ return Html::template(array(
+ 'func' => $func,
+ 'help' => $doc,
+ 'parameters' => $paramsHtml,
+ 'return' => Html::template(array(
+ 'type' => isset($returnDoc['type']) ? $returnDoc['type'] : '',
+ 'doc' => isset($returnDoc['doc']) ? $returnDoc['doc'] : '',
+ ), 'return_help'),
+ ), 'func_help');
+ }
+
+
+ public static function content(WebService $ws) {
+ $rc = new \ReflectionClass($ws);
+
+ $methods = array_filter(array_map(function(\ReflectionMethod $m) {
+ if($m->getName() == 'Run') {
+ // this is a method from WebService directly and is of not interests for the help
+ return '';
+ }
+ return $m->getName();
+ }, $rc->getMethods(\ReflectionMethod::IS_PUBLIC)));
+
+ $html = '';
+ foreach($methods as $m) {
+ $html .= static::func($ws, $m);
+ }
+
+ return $html;
+ }
+
+ public static function exception(WebException $e, WebService $ws, $func) {
+ return static::func($ws, $func);
+ }
+}
diff --git a/src/Webservice/Logger.php b/tests/Webservice/Logger.php
similarity index 100%
rename from src/Webservice/Logger.php
rename to tests/Webservice/Logger.php
diff --git a/tests/Webservice/RendererTest.php b/tests/Webservice/RendererTest.php
new file mode 100644
index 0000000..407fefe
--- /dev/null
+++ b/tests/Webservice/RendererTest.php
@@ -0,0 +1,29 @@
+ 'Ok',
+ 400 => 'Bad request',
+ 404 => 'Not Found',
+ 403 => 'Not Authorized',
+ 500 => 'Server Error',
+ );
+
+ public function __construct() {
+ ob_start();
+ }
+
+ public function render($status, $data) {
+ header(sprintf('HTTP/1.0 %s %s', $status, self::$statusMessages[$status]));
+ header("Access-Control-Allow-Origin: *");
+ ob_clean();
+ flush();
+
+ $formatter = Formatter::getFormatter();
+ $formatter->render($data);
+ }
+}