Browse Source

Started readme generation and added missing files

Joachim M. Giæver 6 years ago
parent
commit
c5a5754761

+ 4 - 0
markdown/manifest.yml

@@ -0,0 +1,4 @@
+name: markdown
+
+global:
+    'readme.twig':  'README.md'

+ 13 - 0
markdown/readme.twig

@@ -0,0 +1,13 @@
+{% block content %}
+
+{% block content_header %}
+    {{- project.config('title')|default('Read me')}}
+    {{- "\n===" }}
+{% endblock %}
+
+{% block toc %}
+    {%- block toc_title %}# Table of content{% endblock %}
+    {%- include 'toc.twig' %}
+{% endblock %}
+    
+{% endblock %}

+ 2 - 0
markdown/toc.twig

@@ -0,0 +1,2 @@
+
+{{toc(tree, 0)|raw}}

+ 86 - 0
sami.conf.php

@@ -0,0 +1,86 @@
+<?php
+
+class Markdown_Sami_Twig_Extension extends Twig_Extension {
+    public function getFunctions() {
+        return array(
+            new Twig_Function("toc", array($this, "toc")),
+            new Twig_Function("href", array($this, "href")),
+            new Twig_Function("dep", array($this, "dep"))
+        );
+    }
+
+    private static function dep($ref) {
+        return sprintf("%s%s", $ref->getName(), $ref->getDeprecated() ? "(DEP)" : "");
+    }
+
+    private static function tab(int $depth) {
+        return str_repeat("\t", $depth);
+    }
+
+    public static function href(string $ltxt, string $lurl, string $desc = null) {
+        $desc = $desc ? sprintf(' "%s"', $desc) : null;
+        return sprintf("[%s](%s%s)", $ltxt, str_replace("\\", "_", $lurl), $desc);
+    }
+
+    public static function toc(array $tree, int $depth) {
+
+        $append = (function(string &$into, int $depth, int $idx, string $from) {
+            $into .= sprintf("%s%d. %s\n", self::tab($depth), $idx + 1, $from);
+        });
+
+        $str = "";
+
+        foreach ($tree as $key => $elem) {
+            $append($str, $depth, $key, self::href($elem[0], sprintf("#%s", $elem[1]), $elem[1])); 
+
+            if (isset($elem[2]) && !empty($elem[2])) {
+                $last = array();
+                $pos = 0;
+                
+                foreach($elem[2] as $key2 => $elem2) {
+                    if (isset($elem2[2]) && !empty($elem2[2]))
+                        $last[$key2] = $elem2;
+                    else
+                        $append($str, ($depth+1), $pos++, self::href(self::dep($elem2[1]), sprintf("#%s", $elem2[1]), $elem2[1]));
+                }
+
+                foreach($last as $key2 => $elem2) {
+                    $append($str, ($depth+1), $pos + $key2, self::href($elem2[0], sprintf("#%s", $elem2[1]), $elem2[1]));
+                    $str .= self::toc($elem2[2], ($depth+2));
+                }
+            }
+        }
+
+        return $str;
+    }
+}
+
+use Sami\Sami;
+use Sami\RemoteRepository\GitHubRemoteRepository;
+use Sami\Version\GitVersionCollection;
+use Symfony\Component\Finder\Finder;
+
+$iterator = Finder::create()
+    ->files()
+    ->name('*.php')
+    ->in($dir = './src')
+;
+
+$versions = GitVersionCollection::create($dir)
+    ->add('master', 'master branch')
+;
+
+$s =  new Sami($iterator, array(
+    'theme'                => 'markdown',
+    'template_dirs'        => array( __DIR__ . '/markdown'),
+    'versions'             => $versions,
+    'title'                => 'Symfony2 API',
+    'build_dir'            => __DIR__.'/build/%version%',
+    'cache_dir'            => __DIR__.'/cache/%version%',
+));
+
+$s["twig"]->addExtension(new Markdown_Sami_Twig_Extension());
+
+return $s;
+
+?>

BIN
sami.phar


+ 75 - 0
src/API/Client.php

@@ -0,0 +1,75 @@
+<?php namespace Gogs\API;
+
+/** 
+ * Gogs API client. This class initially provide 
+ * the programmer with a starting point and a kind
+ * of an interface to start from, to keep  track of
+ * the context.
+ * 
+ * @author Joachim M. Giaever (joachim[]giaever.org)
+ * @version 0.1
+ */
+
+class Client {
+    use \Gogs\Lib\Curl\Client;
+
+    /** 
+     * Constructor of the class to store the api data.
+     *
+     * @deprecated "No longer user"
+     * @param string $api_url 
+     * @param string $api_token 
+     * @return null
+     */
+    public function __construct(string $api_url, string $api_token) {
+        $this->url = $api_url;
+        $this->token = $api_token;
+    }
+
+    /** 
+     * Returns an object to fetch users from the
+     * Gogs installation. See \Request\Users class
+     * to understand usage (e.g ->load() to fetch all, 
+     * ->search(array params) to search for one or several 
+     * users etc).
+     * 
+     * @return \Request\Users
+     */
+    public function users() {
+        return new Request\Users($this->url, $this->token);
+    }
+
+    /** 
+     * Returns an object to fetch the repositories
+     * on the Gogs installation. See \Request\Repos to
+     * understand usage. Inherits the same class as \R\Users,
+     * but the usage may differ!
+     *
+     * Note! To fetch a particular repo under a user, you
+     * should go through the user (see method below).
+     * 
+     * @return \Request\Repos
+     */
+    public function repos() {
+        return new Request\Repos($this->url, $this->token);
+    }
+
+    /** 
+     * Returns either
+     * * the authorized user ($name = "" or "me")
+     * * the specified user ($name = anything else)
+     * 
+     * @return \Request\User
+     */
+    public function user(string $name = "me") {
+        return new Request\User($this->url, $this->token, $name);
+    }
+
+    /** 
+     * Nothing to "destruct" at this time. 
+     * 
+     * @return null
+     */
+    public function __destruct() {}
+}
+?>

+ 112 - 0
src/API/Request/Base.php

@@ -0,0 +1,112 @@
+<?php 
+
+namespace Gogs\API\Request {
+
+    abstract class Base implements RequestInterface {
+
+        protected $loaded = false;
+        protected $scope;
+
+        use \Gogs\Lib\Curl\Client {
+            get as private mget;
+            post as private mpost;
+            delete as private mdelete;
+        }
+
+        public function __construct(string $api_url, string $api_token) {
+            $this->url = $api_url;
+            $this->token = $api_token;
+        }
+
+        public function load(bool $force = false) {
+            $this->set_scope("get");
+
+            if ($this->loaded && !$force)
+                return $this;
+
+            $jenc =  $this->mget($this->scope);
+
+            $this->json_set_property($this->json_decode($jenc));
+            $this->loaded = true;
+
+            return $this;
+        }
+
+        protected function method_get(array $params = array()) {
+            return $this->mget($this->scope, $params);
+        }
+
+        protected function method_post(array $params = array()) {
+            return $this->mpost($this->scope, $params);
+        }
+
+        protected function method_delete() {
+            return $this->mdelete($this->scope);
+        }
+
+        public function get(string $s) {
+            if (!$this->set_scope("get"))
+                throw new Exception\NotImplementedException("::get:: Not implemented for class");
+        }
+
+        public function create(...$args) {
+
+            if ($this->loaded)
+                throw new Exception\InvalidMethodRequestException("::create:: Cant create on an git-initialized object. Create new object.");
+
+            if (!$this->set_scope("create"))
+                throw new Exception\NotImplementedException("::create:: Not implemented for class");
+
+            $ret = $this->method_post(...$args);
+
+            $this->json_set_property($this->json_decode($ret));
+
+            return true;
+        }
+
+        public function patch() {
+
+            if (!$this->loaded)
+                throw new Exception\InvalidMethodRequestException("::patch:: Cant patch an git-uninitialized object. Load it first.");
+
+            if (!$this->set_scope("patch"))
+                throw new Exception\NotImplementedException("::patch:: Not implemented for class");
+        }
+
+        public function delete() {
+            if (!$this->set_scope("delete"))
+                throw new Exception\NotImplementedException("::delete:: Not implemented for class");
+
+            return $this->method_delete();
+        }
+
+        protected function json_decode(string $jenc) {
+            $obj = json_decode($jenc);
+
+            $this->json_error();
+
+            return $obj;
+        }
+
+        protected function json_encode(iterable $jdec) {
+            $jenc = json_encode($jdec);
+
+            $this->json_error();
+
+            return $jenc;
+        }
+
+        protected function json_error() {
+            if (($err = json_last_error()) != JSON_ERROR_NONE)
+                throw new Exception\RequestErrorException(json_last_error_msg(), $err);
+        }
+
+        abstract protected function json_set_property($obj);
+        abstract protected function set_scope(string $method);
+
+        public function __destruct() {}
+    }
+
+}
+
+?>

+ 59 - 0
src/API/Request/Collection.php

@@ -0,0 +1,59 @@
+<?php
+
+namespace Gogs\API\Request {
+
+    abstract class Collection extends Base implements \Gogs\Lib\ArrayIterator {
+        private $objs = array();
+
+        public function add($obj, $key = null) {
+            if (!isset($key))
+                array_push($this->objs, $obj);
+            else
+                $this->objs[$key] = $obj;
+        }
+
+        public function remove($any) {
+            if (isset($this->objs[$any])) {
+                unset($this->objs[$any]);
+                return true;
+            } else if (in_array($any, $this->objs)) {
+                $key = array_search($any, $this->objs, true);
+                return $this->remove($key);
+            }
+            return false;
+        }
+
+        public function all() {
+            return $this->objs;
+        }
+
+        public function len() {
+            return count($this->objs);
+        }
+
+        public function by_key($idx) {
+            return isset($this->objs[$idx]) ? $this->objs[$idx] : false;
+        }
+
+        public function next() {
+            return next($this->objs);
+        }
+
+        public function prev() {
+            return prev($this->objs);
+        }
+
+        public function current() {
+            return current($this->objs);
+        }
+
+        public function reset() {
+            return reset($this->objs);
+        }
+
+        //abstract public function create(...$args);
+        abstract public function search(array $params = array());
+    }
+
+}
+?>

+ 5 - 0
src/API/Request/Exception/InvalidMethodRequestException.php

@@ -0,0 +1,5 @@
+<?php
+namespace Gogs\API\Request\Exception {
+    class InvalidMethodRequestException extends \Exception {};
+}
+?>

+ 5 - 0
src/API/Request/Exception/NotImplementedException.php

@@ -0,0 +1,5 @@
+<?php
+namespace Gogs\API\Request\Exception {
+    class NotImplementedException extends \BadMethodCallException{}
+}
+?>

+ 5 - 0
src/API/Request/Exception/RequestErrorException.php

@@ -0,0 +1,5 @@
+<?php
+namespace Gogs\API\Request\Exception {
+    class RequestErrorException extends \Exception {};
+}
+?>

+ 5 - 0
src/API/Request/Exception/SearchParamException.php

@@ -0,0 +1,5 @@
+<?php
+namespace Gogs\API\Request\Exception {
+    class SearchParamException extends \Exception {};
+}
+?>

+ 57 - 0
src/API/Request/Org.php

@@ -0,0 +1,57 @@
+<?php 
+
+namespace Gogs\API\Request {
+
+    class Org extends User {
+        public $udescription;
+        public $uwebsite;
+        public $ulocation;
+
+        private $owner;
+
+        public function __construct(string $api_url, string $api_token, string $oname = null, User $owner = null) {
+            $this->ousername = $oname;
+            $this->owner = $owner;
+            parent::__construct($api_url, $api_token);
+        }
+
+        protected function set_scope(string $method) {
+            switch ($method) {
+            case "create":
+                if ($this->owner == null)
+                    throw new Exception\InvalidMethodRequestException("Cant create organization without a related User");
+
+                $this->scope = "/admin/users/" . $this->owner->uusername . "/orgs";
+                return true;
+            case "get":
+                $this->scope = "/orgs/" . $this->uusername;
+                return true;
+            }
+        }
+
+        public function search(string $q) {
+            $searchable = sprintf("%s %s %s", $this->ufull_name, $this->uusername, $this->udescription);
+
+            return stripos($searchable, $q) !== false;
+        }
+
+        public function create(...$args) {
+            $params = array(
+                "username" => isset($args[0]) && is_string($args[0]) ? $args[0] : null,
+                "ufull_name" => isset($args[1]) && is_string($args[1]) ? $args[1] : null,
+                "description" => isset($args[2]) && is_string($args[2]) ? $args[2] : null,
+                "website" => isset($args[3]) && is_string($args[3]) ? $args[3] : null,
+                "location" => isset($args[4]) && is_string($args[4]) ? $args[4] : null
+            );
+
+            $params = array_filter($params, function($val) {
+                return $val != null;
+            });
+
+            parent::create($params);
+        }
+    }
+
+}
+
+?>

+ 70 - 0
src/API/Request/Orgs.php

@@ -0,0 +1,70 @@
+<?php 
+
+namespace Gogs\API\Request {
+
+    class Orgs extends Collection {
+        protected $owner;
+
+        public function __construct(string $api_url, string $api_token, User $owner) {
+            $this->owner = $owner;
+            parent::__construct($api_url, $api_token);
+        }
+
+        protected function set_scope(string $method) {
+            switch ($method) {
+            case "get":
+                $this->scope = ($this->owner == null || $this->owner->authenticated() ? "/user" : "/users/" . $this->owner->uusername) . "/orgs";
+                return true;
+            }
+        }
+
+        public function create(...$args) {
+
+            $org = new Org($this->url, $this->token, null, $this->owner);
+
+            if (count($args) > 0)
+                $org->create(...$args);
+
+            return $org;
+        }
+
+        public function get(string $s) {
+
+            if (($org = $this->by_key($s)))
+                return $org;
+
+            return new Org($this->url, $this->token, $s, $this->owner);
+        }
+
+        public function search(array $params = array()) {
+
+            if (!isset($params["name"]) && !isset($params["q"]))
+                throw new Exception\SearchParamException("Missing param <name>|<q>");
+
+            $q = isset($params["name"]) ? $params["name"] : $params["q"];
+            $l = isset($params["limit"]) ? $params["limit"] : 10;
+
+            $this->load();
+
+            $orgs = new CollectionResult();
+
+            foreach ($this->all() as $key => $org) {
+                if ($org->search($q))
+                    $orgs->set($org);
+                if ($orgs->len() == $l)
+                    break;
+            }
+
+            return $orgs;
+        }
+
+        protected function json_set_property($obj) {
+            foreach($obj as $val) {
+                $org = new Org($this->url, $this->token, null, $this->owner);
+                $org->json_set_property($val);
+                $this->add($org, $val->username);
+            }
+        }
+    }
+
+}

+ 110 - 0
src/API/Request/Repo.php

@@ -0,0 +1,110 @@
+<?php 
+
+namespace Gogs\API\Request {
+
+    class Repo extends Base {
+        
+        public $rid;
+        public $rowner;
+        public $rname;
+        public $rfull_name;
+        public $rdescription;
+        public $rprivate;
+        public $rfork;
+        public $rparent;
+        public $rempty;
+        public $rmirror;
+        public $rsize;
+        public $rhtml_url;
+        public $rssh_url;
+        public $rclone_url;
+        public $rwebsite;
+        public $rstars_count;
+        public $rforks_count;
+        public $rwatchers_count;
+        public $ropen_issues_count;
+        public $rdefault_branch;
+        public $rcreated_at;
+        public $rupdated_at;
+        public $rpermissions;
+        public $radmin;
+        public $rpush;
+        public $rpull;
+
+        public function __construct(string $api_url, string $api_token, User $owner = null, string $name = null) {
+            $this->rowner = $owner;
+            $this->rname = $name;
+            parent::__construct($api_url, $api_token);
+        }
+
+        protected function set_scope(string $method) {
+            switch ($method) {
+            case "create":
+                if ($this->rowner instanceof Org)
+                    $this->scope = "/org/" . $this->rowner->uusername . "/repos";
+                elseif ($this->rowner->authenticated())
+                    $this->scope = "/user/repos";
+                else
+                    $this->scope = "/admin/users/" . $this->rowner->uusername . "/repos";
+                break;
+            case "delete":
+                $this->scope = "/repos/" . $this->rowner->uusername . "/" . $this->rname;
+                break;
+            case "get":
+                $this->scope = "/repos/" . ($this->rowner ? $this->rowner->uusername . "/" . $this->rname : $this->rfull_name);
+                break;
+            default:
+                return false;
+            }
+
+            return true;
+        }
+
+        protected function json_set_property($obj) {
+            foreach($obj as $key => $val) {
+                $key = 'r' . $key;
+                if (property_exists($this, $key)) {
+                    switch ($key) {
+                    case 'rowner':
+                        if (!$this->rowner) {
+                            $user = new User($this->url, $this->token);
+                            $user->json_set_property($val);
+                            $this->{$key} = $user;
+                        }
+                        break;
+                    default:
+                        $this->{$key} = $val;
+                    }
+                }
+            }
+            $this->loaded = true;
+        }
+
+        public function search(string $q) {
+            $searchable = sprintf("%s %s", $this->rname, $this->rdescription);
+
+            return stripos($searchable, $q) !== false;
+        }
+
+        public function create(...$args) {
+
+            $params = array(
+                "name" => isset($args[0]) && is_string($args[0]) ? $args[0] : null,
+                "description" => isset($args[1]) && is_string($args[1]) ? $args[1] : null,
+                "private" => isset($args[2]) && is_bool($args[2]) ? $args[2] : false,
+                "auto_init" => isset($args[3]) && is_bool($args[3]) ? $args[3] : false,
+                "gitignores" => isset($args[4]) && is_string($args[4]) ? $args[4] : null,
+                "licence" => isset($args[5]) && is_string($args[5]) ? $args[5] : null,
+                "readme" => isset($args[6]) && is_string($args[6]) ? $args[6] : "Default"
+            );
+
+            $params = array_filter($params, function($val) {
+                return $val != null;
+            });
+
+            parent::create($params);
+        }
+    }
+
+    }
+?>

+ 106 - 0
src/API/Request/Repos.php

@@ -0,0 +1,106 @@
+<?php 
+
+namespace Gogs\API\Request {
+
+class Repos extends Collection {
+
+    protected $owner;
+
+    public function __construct(string $api_url, string $api_token, User $owner = null) {
+        $this->owner = $owner;
+        parent::__construct($api_url, $api_token);
+    }
+
+    protected function set_scope(string $method) {
+        switch ($method) {
+        case "get":
+            if ($this->owner instanceof Org)
+                $this->scope = "/orgs/" . $this->owner->uusername . "/repos";
+            else
+                $this->scope = ($this->owner == null || $this->owner->authenticated() ? "/user" : "/users/" . $this->owner->uusername ) . "/repos";
+            break;
+        case "search":
+            $this->scope = "/repos/search";
+            break;
+        default:
+            return false;
+        }
+
+        return true;
+    }
+
+    public function create(...$args) {
+
+        $repo = new Repo($this->url, $this->token, $this->owner);
+
+        if (count($args) > 0)
+            $repo->create(...$args);
+
+        return $repo;
+    }
+
+    public function search(array $params = array()) {
+        $scope = substr($this->scope, 1, strpos($this->scope, "/", 1)-1);
+
+        if (!isset($params["name"]) && !isset($params["q"]))
+            throw new Exception\SearchParamException("Missing param <name>|<q>");
+
+        if (isset($params["name"])) {
+            $params["q"] = $params["name"];
+            unset($params["name"]);
+        }
+
+        if (!isset($params["user"]))
+            $params["user"] = 0;
+
+        if (!isset($params["limit"]))
+            $params["limit"] = 10;
+
+        $repos = new \Gogs\Lib\Collection();
+        
+        switch ($scope) {
+        case "user":
+        case "users":
+        case "org":
+            $this->load();
+
+            foreach($this->all() as $key => $repo) {
+                if ($repo->search($params["q"]))
+                    $repos->set($repo);
+                if ($repos->len() == $params["limit"])
+                    break;
+            }
+
+        default:
+            $this->set_scope("search");
+            $jenc = $this->method_get($params);
+            $jdec = $this->json_decode($jenc);
+
+            foreach($this->json_set_property($jdec) as $key)
+                $repos->set($this->by_key($key), $key);
+
+        }
+        
+        return $repos;
+    }
+
+    protected function json_set_property($obj) {
+        if (isset($obj->data))
+            return $this->json_set_property($obj->data);
+
+        $rnames = array();
+
+        if (is_array($obj)) {
+            foreach ($obj as $key => $val) {
+                $repo = new Repo($this->url, $this->token);
+                $repo->json_set_property($val);
+                $this->add($repo, $repo->rfull_name);
+                array_push($rnames, $repo->rfull_name);
+            }
+        }
+
+        return $rnames;
+    }
+}
+
+}

+ 14 - 0
src/API/Request/RequestInterface.php

@@ -0,0 +1,14 @@
+<?php 
+
+namespace Gogs\API\Request {
+
+    interface RequestInterface {
+        public function load(bool $force = true);
+        public function get(string $s);
+        public function create(...$args);
+        public function patch();
+        public function delete();
+    }
+
+}
+?>

+ 82 - 0
src/API/Request/User.php

@@ -0,0 +1,82 @@
+<?php  
+
+namespace Gogs\API\Request {
+
+    class User extends Base {
+
+        private $authenticated;
+        public $uid;
+        public $ulogin;
+        public $ufull_name;
+        public $uemail;
+        public $uavatar_url;
+        public $uusername;
+
+        public function __construct(string $api_url, string $api_token, string $user = "") {
+            $this->uusername = (strlen($user) == 0 || $user == "me" ? "me" : $user);
+            $this->authenticated = $this->uusername == "me";
+            parent::__construct($api_url, $api_token);
+        }
+
+        protected function set_scope(string $method) {
+            switch($method) {
+            case "create":
+
+                if ($this->loaded)
+                    throw new Exception\InvalidMethodRequest("Cannot create user of existing user");
+
+                $this->scope = "/admin/users";
+                
+                return true;
+            case "delete":
+                $this->scope = "/admin/users/" . $this->uusername;
+                return true;
+            default:
+                $this->scope = ($this->authenticated ? "/user" : "/users/" . $this->uusername);
+            }
+        }
+
+        public function authenticated() {
+            return $this->authenticated;
+        }
+
+        public function repos() {
+            return new Repos($this->url, $this->token, $this);
+        }
+
+        public function repo(string $name) {
+            return (new Repo($this->url, $this->token, $this, $name))->load();
+        }
+
+        public function organizations() {
+            return new Orgs($this->url, $this->token, $this);
+        }
+
+        public function create(...$args) {
+            $params = array(
+                "username" => isset($args[0]) && is_string($args[0]) ? $args[0] : null,
+                "email" => isset($args[1]) && is_string($args[1]) ? $args[1] : null,
+                "source_id" => isset($args[2]) && is_numeric($args[2]) ? $args[2] : null,
+                "login_name" => isset($args[3]) && is_string($args[3]) ? $args[3] : null,
+                "password" => isset($args[4]) && is_string($args[4]) ? $args[4] : null,
+                "send_notify" => isset($args[5]) && is_bool($args[5]) ? $args[5] : null
+            );
+
+            $params = array_filter($params, function($val) {
+                return $val != null;
+            });
+            parent::create($params);
+        }
+
+        protected function json_set_property($obj) {
+            foreach ($obj as $key => $value) {
+                $key = 'u' . $key;
+                if (property_exists($this, $key))
+                    $this->{$key} = $value;
+            }
+            $this->loaded = true;
+        }
+    }
+
+}
+?>

+ 96 - 0
src/API/Request/Users.php

@@ -0,0 +1,96 @@
+<?php 
+
+namespace Gogs\API\Request {
+    /** 
+     * Returns one or more users in the Gogs installation, 
+     * depending on the called method. 
+     * 
+     * @author Joachim M. Giaever (joachim[]giaever.org)
+     * @package request
+     */
+    class Users extends Collection {
+
+        protected function set_scope(string $method) {
+            switch ($method) {
+            case "get":
+                $this->scope = "/users";
+                break;
+            case "search":
+                $this->scope = "/users/search";
+            default:
+                return false;
+            }
+
+            return true;
+        }
+
+        /**
+         * Returns a new user object. If arguments
+         * is specified the user will be "created".
+         *
+         * The arguments can be left out to "create" the
+         * user through the user object iteself.
+         *
+         * @param ...$args User->create arguments
+         * @return \User
+         */
+        public function create(...$args) {
+
+            $user = new User($this->url, $this->token, "-");
+
+            if (count($args) != 0)
+                $user->create(...$args);
+
+            return $user;
+        }
+
+        public function get(string $s = "") {
+
+            if ($this->by_key($s))
+                return $this->by_key($s);
+
+            $user = (new User($this->url, $this->token, $s))->load();
+
+            $this->add($user, $user->ulogin);
+
+            return $user;
+        }
+
+        public function search(array $params = array()) {
+
+            if (!isset($params["name"]) && !isset($params['q']))
+                throw new Exception\SearchParamException("Missing param <name>|<q>");
+
+            if (isset($params["name"])) {
+                $params["q"] = $params["name"];
+                unset($params["name"]);
+            }
+
+            $this->set_scope("search");
+            $jenc = $this->method_get($params);
+
+            $old = $this->all();
+
+            $this->json_set_property($this->json_decode($jenc));
+
+            $users = new \Gogs\Lib\Collection();
+
+            $users->set(array_diff_key($this->all(), $old));
+
+            return $users;
+        }
+
+        protected function json_set_property($obj) {
+            if (isset($obj->data)) {
+                foreach($obj->data as $key => $val) {
+                    $user = new User($this->url, $this->token, $val->login);
+                    $user->json_set_property($val);
+                    $this->add($user, $user->ulogin);
+                }
+            }
+        }
+    }
+
+}
+
+?>

+ 11 - 0
src/Lib/ArrayIterator.php

@@ -0,0 +1,11 @@
+<?php namespace Gogs\Lib;
+
+interface ArrayIterator {
+    public function current();
+    public function next();
+    public function reset();
+    public function len();
+    public function all();
+    public function by_key($idx);
+}
+

+ 43 - 0
src/Lib/Collection.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace Gogs\Lib {
+
+    class Collection implements ArrayIterator {
+        private $objs = array();
+
+        public function set($val, $key = null) {
+            if ($key == null && is_array($val))
+                $this->objs = $val;
+            else if ($key != null)
+                $this->objs[$key] = $val;
+            else 
+                array_push($this->objs, $val);
+        }
+
+        public function by_key($idx) {
+            return isset($this->objs[$idx]) ? $this->objs[$idx] : false;
+        }
+
+        public function all() {
+            return $this->objs;
+        }
+
+        public function len() {
+            return count($this->objs);
+        }
+
+        public function next() {
+            return next($this->objs);
+        }
+
+        public function current() {
+            return current($this->objs);
+        }
+
+        public function reset() {
+            return reset($this->objs);
+        }
+
+    }
+
+}

+ 200 - 0
src/Lib/Curl/Client.php

@@ -0,0 +1,200 @@
+<?php 
+
+namespace Gogs\Lib\Curl {
+
+    /** 
+     * A trait used for every class referencing the api-url and token.
+     *
+     * @author Joachim M. Giaever (joachim[]giaever.org)
+     * @package curl
+     * @version 0.1
+     */
+    trait Client {
+        protected $url;
+        protected $token;
+
+        protected $user_agent = "Gogs PHP Api Curl\\Client/0.1 (compatible; LINUX)";
+        protected $timeout = 30;
+        protected $max_redirects = 4;
+
+        /** 
+         * array_2_params takes an array and converts it into a
+         * query string (e.g param=val&param2=val2).
+         *
+         * @param array $params parameters to pass
+         * @return string
+         */
+        private function array_2_params(array $params) {
+            return join("&", array_map(function($k, $v) {
+                return sprintf("%s=%s", $k, rawurlencode(is_bool($v) ? ($v ? "true" : "false") : $v ));
+            }, array_keys($params), $params));
+        }
+
+        /** 
+         * array_2_json takes an array and converts it into a
+         * json-string (e.g {'name': 'This'}) which is typically
+         * used in a request body.
+         * 
+         * @param array $params paramters to pass
+         * @return string
+         */
+        private function array_2_json(array $params) {
+            return count($params) == 0 ? null : json_encode($params);
+        }
+
+        /** 
+         * Initializes a curl request of different kinds, depending
+         * on the specified method. This can be
+         *
+         * DELETE, PATCH, POST or GET. An unidentified value will
+         * become a GET-request.
+         * 
+         * @param string $method either DELETE, PATCH, POST, GET
+         * @param string &$req variable to store request body in
+         * @param string $scope scope within the API (e.g /user/repos)
+         * @param array $params parameters to pass
+         * @param bool $ret return transfer
+         * @return int the status code
+         */
+        protected function method(string $method, string &$req, string $scope, array $params, bool $ret) {
+            $c = curl_init();
+
+            if (!$c) {
+                return false;
+            }
+
+            $headers = array(
+                sprintf("Authorization: token %s", $this->token),
+            );
+
+            $url = sprintf("%s%s", $this->url, $scope);
+
+            echo sprintf("%s: %s, params: %s\n", $method, $url, $this->array_2_json($params));
+
+            if (in_array($method, array("DELETE", "PATCH", "POST"))) {
+                $json = $this->array_2_json($params);
+                curl_setopt($c, CURLOPT_CUSTOMREQUEST, $method);
+                curl_setopt($c, CURLOPT_POSTFIELDS, $json);
+                array_unshift($headers, "Content-Type: application/json");
+                array_push($headers, "Content-Length: " . strlen($json));
+            } else {
+                $url .= "?" . $this->array_2_params($params);
+            }
+
+            curl_setopt($c, CURLOPT_USERAGENT, $this->user_agent);
+            curl_setopt($c, CURLOPT_URL, $url);
+            curl_setopt($c, CURLOPT_HTTPHEADER, $headers);
+            curl_setopt($c, CURLOPT_RETURNTRANSFER, $ret);
+            curl_setopt($c, CURLOPT_TIMEOUT, $this->timeout);
+            curl_setopt($c, CURLOPT_MAXREDIRS, $this->max_redirects);
+            curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);
+
+            $req = curl_exec($c);
+
+            $status_code = curl_getinfo($c, CURLINFO_HTTP_CODE);
+
+            curl_close($c);
+
+            return $status_code;
+        }
+
+        /** 
+         * Checks if the user is authorized for the scope. Shouldn't
+         * be used frequently. One test for one scope should be enough,
+         * but if you know for sure thats you're programming with the
+         * use of an authorized user you should leave this and just
+         * handle the NotAuthorizedExeption whenever thrown.
+         *
+         * @param $scope the scope, a relative uri.
+         * @throws Not AuthorizedException if server responde with a 401
+         * @return bool
+         */
+        protected function authorized(string $scope = "") {
+            $ret = "";
+            if ($this->method("GET", $ret, $scope, array(), false) == 401) {
+                throw new NotAuthorizedException("Not authorized", 401);
+            }
+            return true;
+        }
+
+        /**
+         * Post method.
+         *
+         * @param string $scope the scope, a relative uri.
+         * @param array $params the parameters to post.
+         * @throws NotAuthorizedException on 401, 403
+         * @throws HTTPUnexpectedResponse when not 200,201,401,403
+         * @return string the request content.
+         */
+        private function post(string $scope = "", array $params = array()) {
+            $req = "";
+
+            $code = $this->method("POST", $req, $scope, $params, true);
+
+            switch ($code) {
+            case 200:
+            case 201:
+                return $req;
+            case 401:
+            case 403:
+                throw new Exception\NotAuthorizedException($req, $code);
+            default:
+                throw new Exception\HTTPUnexpectedResponse($req, $code);
+            }
+        }
+
+        /**
+         * Delete method.
+         *
+         * @param string $scope the scope, a relative uri.
+         * @throws NotAuthorizedException on 401, 403
+         * @throws HTTPUnexpectedResponse when not 200,204,401,403
+         * @return string the request content.
+         */
+        private function delete(string $scope = "") {
+            $req = "";
+
+            $code = $this->method("DELETE", $req, $scope, array(), true);
+
+            switch ($code) {
+            case 200:
+            case 204:
+                return $req;
+            case 401:
+            case 403:
+                throw new Exception\NotAuthorizedException($req, $code);
+            default:
+                throw new Exception\HTTPUnexpectedResponse($req, $code);
+            }
+        }
+
+        /**
+         * GET method.
+         *
+         * @param string $scope the scope, a relative uri.
+         * @param array $params the parameters to post.
+         * @throws NotAuthorizedException on 401, 403
+         * @throws HTTPUnexpectedResponse when not 200,401,403
+         * @return string the request content.
+         */
+        private function get($scope = "", $params = array()) {
+            $req = "";
+
+            $code = $this->method("GET", $req, $scope, $params, true);
+
+            switch ($code) {
+            case 200:
+                return $req;
+            case 401:
+            case 403:
+                throw new Exception\NotAuthorizedException($req, $code);
+            default:
+                throw new Exception\HTTPUnexpectedResponse($req, $code);
+
+            }
+        }
+
+    }
+
+}
+?>

+ 138 - 0
src/Lib/Curl/Exception/HTTPUnexpectedResponse.php

@@ -0,0 +1,138 @@
+<?php 
+
+namespace Gogs\Lib\Curl\Exception {
+
+    /** 
+     * Defines an unexpected response.
+     *
+     * @author Joachim M. Giaever (joachim[]giaever.org)
+     * @package curl
+     * @version -1.1
+     */
+    class HTTPUnexpectedResponse extends \Exception {
+        /**
+         * Includes valid codes, as a valid code can also be unexpeted.
+         *
+         * @var array $ecode HTTP status codes
+         * @static
+         */
+        static $ecode = array(
+            -1 => "Unknown error",
+            99 => 'Continue',
+            100 => 'Switching Protocols',
+            101 => 'Processing', // WebDAV; RFC 2518
+            199 => 'OK',
+            200 => 'Created',
+            201 => 'Accepted',
+            202 => 'Non-Authoritative Information', // since HTTP/1.1
+            203 => 'No Content',
+            204 => 'Reset Content',
+            205 => 'Partial Content',
+            206 => 'Multi-Status', // WebDAV; RFC 4918
+            207 => 'Already Reported', // WebDAV; RFC 5842
+            225 => 'IM Used', // RFC 3229
+            299 => 'Multiple Choices',
+            300 => 'Moved Permanently',
+            301 => 'Found',
+            302 => 'See Other', // since HTTP/1.1
+            303 => 'Not Modified',
+            304 => 'Use Proxy', // since HTTP/1.1
+            305 => 'Switch Proxy',
+            306 => 'Temporary Redirect', // since HTTP/1.1
+            307 => 'Permanent Redirect', // approved as experimental RFC
+            399 => 'Bad Request',
+            400 => 'Unauthorized',
+            401 => 'Payment Required',
+            402 => 'Forbidden',
+            403 => 'Not Found',
+            404 => 'Method Not Allowed',
+            405 => 'Not Acceptable',
+            406 => 'Proxy Authentication Required',
+            407 => 'Request Timeout',
+            408 => 'Conflict',
+            409 => 'Gone',
+            410 => 'Length Required',
+            411 => 'Precondition Failed',
+            412 => 'Request Entity Too Large',
+            413 => 'Request-URI Too Long',
+            414 => 'Unsupported Media Type',
+            415 => 'Requested Range Not Satisfiable',
+            416 => 'Expectation Failed',
+            417 => 'I\'m a teapot', // RFC 2324
+            418 => 'Authentication Timeout', // not in RFC 2616
+            419 => 'Enhance Your Calm', // Twitter
+            419 => 'Method Failure', // Spring Framework
+            421 => 'Unprocessable Entity', // WebDAV; RFC 4918
+            422 => 'Locked', // WebDAV; RFC 4918
+            423 => 'Failed Dependency', // WebDAV; RFC 4918
+            423 => 'Method Failure', // WebDAV)
+            424 => 'Unordered Collection', // Internet draft
+            425 => 'Upgrade Required', // RFC 2817
+            427 => 'Precondition Required', // RFC 6585
+            428 => 'Too Many Requests', // RFC 6585
+            430 => 'Request Header Fields Too Large', // RFC 6585
+            443 => 'No Response', // Nginx
+            448 => 'Retry With', // Microsoft
+            449 => 'Blocked by Windows Parental Controls', // Microsoft
+            450 => 'Redirect', // Microsoft
+            450 => 'Unavailable For Legal Reasons', // Internet draft
+            493 => 'Request Header Too Large', // Nginx
+            494 => 'Cert Error', // Nginx
+            495 => 'No Cert', // Nginx
+            496 => 'HTTP to HTTPS', // Nginx
+            498 => 'Client Closed Request', // Nginx
+            499 => 'Internal Server Error',
+            500 => 'Not Implemented',
+            501 => 'Bad Gateway',
+            502 => 'Service Unavailable',
+            503 => 'Gateway Timeout',
+            504 => 'HTTP Version Not Supported',
+            505 => 'Variant Also Negotiates', // RFC 2295
+            506 => 'Insufficient Storage', // WebDAV; RFC 4918
+            507 => 'Loop Detected', // WebDAV; RFC 5842
+            508 => 'Bandwidth Limit Exceeded', // Apache bw/limited extension
+            509 => 'Not Extended', // RFC 2774
+            510 => 'Network Authentication Required', // RFC 6585
+            597 => 'Network read timeout error', // Unknown
+            598 => 'Network connect timeout error', // Unknown
+        );
+
+        /**
+         * The response from server (body)
+         * @access private
+         */
+        private $response;
+
+        /**
+         * Sets the exceptions.
+         *
+         * @string $message - the response from the server.
+         * @string $code - the HTTP status code.
+         * @exception $prev - Previous exceptions
+         **/
+        public function __construct(string $message, int $code = -1, Exception $previous = null) {
+            $this->response = $message;
+            parent::__construct(HTTPUnexpectedResponse::$ecode[$code], $code, $previous);
+        }
+
+        /** 
+         * Visual representation of the exception.
+         *
+         * @return string
+         */
+        public function __toString() {
+            return __CLASS__ . ": [{$this->code} | {$this->message}]: {$this->response}\n";
+        }
+
+        /** 
+         * Get the actual response from the body or the request.
+         *
+         * @return string
+         */
+        public function getResponse() {
+            return $this->response;
+        }
+    }
+
+}
+?>

+ 28 - 0
src/Lib/Curl/Exception/NotAuthorizedException.php

@@ -0,0 +1,28 @@
+<?php 
+
+namespace Gogs\Lib\Curl\Exception {
+
+    /** 
+     * When the request fails because of an unauthorized token,
+     * this is thrown instead.
+     *
+     * @author Joachim M. Giaever (joachim[]giaever.org)
+     * @package curl
+     * @version 0.1
+     */
+    class NotAuthorizedException extends HTTPUnexpectedResponse {
+
+        /**
+         * Sets the exceptions.
+         *
+         * @string $message - the response from the server.
+         * @string $code - the HTTP status code, @default 401
+         * @exception $prev - Previous exceptions
+         **/
+        public function __construct($message, $code = 401, Exception $previous = null) {
+            parent::__construct($message, $code, $previous);
+        }
+    }
+
+}
+?>