Browse Source

Added sorting, limiting etc on collections and rewrote Request\Collection classes

Joachim M. Giæver 6 years ago
parent
commit
9f35eb9d2c

+ 73 - 91
index.php

@@ -13,110 +13,92 @@ use Gogs\Lib\Curl\Exception as ApiException;
 
 define('API_URL', 'https://git.giaever.org/api/v1');
 define('API_TOKEN', '142efbfd6fbdf147f03d289f8b22a438eaa1b5d1');
-
+//define("API_TOKEN", "e14b9eff0749b6f0c4cadf4bb72b83d44578ae28");
 
 try {
     $client =  new Gogs\API\Client(API_URL, API_TOKEN);
 
     $me = $client->user()->load();
 
-    $user_search = $client->users()->search(array(
-        "name" => "tester2"
-    ));
-
-    if ($user_search->len() > 0)
-        echo " * Found user(s) " . var_export($user_search, true);
-    else
-        echo " * User 'tester2' not found"; 
-
-    try {
-
-        echo " * Creating new repo under autorized user: " . $me->username;
-        // Create new repo under authorized user
-        $repo = $me->repos()->create(
-            /*name*/        "gogs-php-api-client-test", 
-            /*desc*/        "Repository created from test file.",
-            /*private*/     false,
-            /*auto init*/   true,
-            /*git ignore*/  "Vim",
-            /*license*/     "MIT License"
-            /* default read me */
-        );
+    $tester = "tester";
+    if (API_TOKEN == "142efbfd6fbdf147f03d289f8b22a438eaa1b5d1")
+        $tester = "joachimmg";
+
+    $repos = $me->repos()->load();
+
+    /*
+    echo "\nNormal repo\n";
+    foreach($repos->all() as $key => $repo)
+        echo sprintf("* %s: %s\n", $key, $repo->name);
+
+    echo "\nSorted created\n";
+    foreach($repos->sort_by(Gogs\API\Request\Repos::SORT_CREATED)->all() as $key => $repo)
+        echo sprintf("* %s: %s - %s\n", $repo->created_at, $key, $repo->name);
+
+    echo "\nSorted created, then reversed\n";
+    foreach($repos->sort_by(Gogs\API\Request\Repos::SORT_CREATED, true)->all() as $key => $repo)
+        echo sprintf("* %s: %s - %s\n", $repo->created_at, $key, $repo->name);
+
+    echo "\nSorted Normal, offset 1, limit 10\n";
+    foreach($repos->offset(1)->limit(10)->all() as $key => $repo)
+        echo sprintf("* %s: %s\n", $key, $repo->name);
+
+    echo "\nNormal repo\n";
+    foreach($repos->all() as $key => $repo)
+        echo sprintf("* %s: %s\n", $key, $repo->name);
+
+    echo "\nSearch for in loaded data for 'dns', limit 10\n";
+    foreach($repos->search(array("name" => "dns", "limit" => 3))->all() as $key => $repo)
+        echo sprintf("* %s: %s\n", $key, $repo->name);
+
+    echo "\nSearch for in new data for 'dns', limit 10\n";
+    foreach($me->repos()->search(array("name" => "dns", "limit" => 3))->all() as $key => $repo)
+        echo sprintf("* %s: %s\n", $key, $repo->name);
+
+    echo "\nUsers->search name 't', offset 1:\n";
+    foreach($client->users()->search(array("name" => "to"))->offset(1)->all() as $key => $user)
+       echo sprintf("* %s: %s\n", $key, $user->full_name); 
+
+    $user = $client->users()->get($tester);
+
+    echo "\nUser '" . $user->username . "' public repos\n";
+    foreach($user->repos()->load()->all() as $key => $repo)
+        echo sprintf("* %s: %s\n", $key, $repo->name);
+
+    echo "\nUser '" . $me->username . "' public repos \n";
+    foreach($client->users()->get($me->username)->repos()->load()->all() as $key => $repo)
+        echo sprintf("* %s: %s\n", $key, $repo->name);
+
+    echo "\nUsers '" . $me->username . "' organizations\n";
+    foreach($me->organizations()->load()->all() as $key => $org)
+        echo sprintf("* %s: %s\n", $key, $org->full_name);
+
+    echo "\nUser '" . $user->username . "' public organizations\n";
+    foreach($user->organizations()->load()->all() as $key => $org) {
+        echo sprintf("* %s: %s\n* Repositories:\n", $key, $org->full_name);
         
-       echo "New repo '" . $repo->full_name . "' created!\n";
+        foreach($org->repos()->load()->all() as $key => $repo)
+            echo sprintf("#### %s: %s\n", $key, $repo->name);
+    }
+     */
+    echo "Create data under specified user";
 
-    } catch(ApiException\HTTPUnexpectedResponse $e) {
+    $repo = $repos->create(
+        "test-test-test-" . $repos->load()->len(),
+        "This is repo #" . $repos->load()->len(),
+        false,
+        true
+    );
 
-        echo $e;
-        // Repo exists, get it!
-        $repo = $me->repo("gogs-php-api-client-test");
+    echo sprintf("* Created repo: '%s'", $repo->name);
 
-        // Delete it (it will now be created again on reload!)
+    echo "\nNow having repos\n";
+    foreach($repos->load()->sort_by()->all() as $key => $repo)
         $repo->delete();
 
-        echo "Repo '" . $repo->full_name . "' deleted\n";
-    }
 
-    // Loop through organizations
-    foreach($me->organizations()->load()->all() as $org)
-        var_dump("ORG", $org->username);
-
-    if (isset($org))
-        echo sprintf(
-            "org->username: %s\norg->org_username: %s\norg->user_username: %s\n",
-            $org->username, $org->org_username, $org->user_username
-        ); 
-
-    // Requires ADMIN rights to the authenticated user
-    #try {
-        // Create new user "tester2"
-    #    $user = $client->users()->new("tester2", "git@git.giaever.org");
-
-    #    echo "New user '" . $user->uusername . "' created!\n";
-
-        // And a new repository under this user
-    #    $repo = $user->repos()->new(
-    #        /*name*/        "gogs-php-api-client-auth-test", 
-    #        /*desc*/        "Repository created from test file.",
-    #        /*private*/     false,
-    #        /*auto init*/   true,
-    #        /*git ignore*/  "Vim",
-    #        /*license*/     "MIT License"
-    #        /* default read me */
-    #    );
-           
-    #    echo "New repo '" . $repo->rfull_name . "' created\n";
-
-    #    $org = $user->organizations()->new(
-    #        /*username*/ "tester-org",
-    #        /*full name*/ "Tester's organization",
-    #        /*desc*/ "Just a test organization",
-    #        /*website*/ "https://git.giaever.org/",
-    #        /*location*/ "Norway"
-    #    );
-
-    #}catch (\Lib\NotAuthorizedException $e) {
-    #    echo "User (me) '" . $me->uusername . "' not authorized for this action: " . $e->getMessage();
-    #} catch (\Lib\HTTPUnexpectedResponse $e) {
-    
-        // Most likely user exist; then delete it
-    #    $user = $client->users()->get("tester2");
-
-        // Delete it's repositories
-    #    foreach ($user->repos()->load()->all() as $repo)
-    #        $repo->delete();
-
-    #    foreach ($user->organizations()->load()->all() as $org)
-    #        $org->delete();
-
-    #    $user->delete();
-    #    echo "User '" . $user->uusername . "' deleted!\n";
-    #}
-
-    /*var_dump($me->organizations()->load()->all()["FlyViking"]->repos()->create(
-        "test-repo-for-org",
-        "this is desc."
-    ));*/
+    echo "\n\n\nLOG:\n" . join("\n", $client->get_log());
+    die();
 
 } catch (ApiException\NotAuthorizedException $e) {
     die("NOT AUTH: " . $e->getMessage());

+ 7 - 1
src/API/Client.php

@@ -15,7 +15,9 @@ namespace Gogs\API {
      */
 
     final class Client {
-        use \Gogs\Lib\Curl\Client;
+        use \Gogs\Lib\Curl\Client {
+
+        }
 
         /** 
          * @param string $api_url The base URL for the Gogs API (e.g https://git.domain.tld/api/v1) 
@@ -68,6 +70,10 @@ namespace Gogs\API {
         public function repos() {
             return new Request\Repos($this->url, $this->token);
         }
+
+        public function get_log() {
+            return (new Request\User($this->url, $this->token))->get_log();
+        }
     }
 }
 ?>

+ 3 - 2
src/API/Request/Base.php

@@ -41,11 +41,12 @@ namespace Gogs\API\Request {
          * If `$force = true` the object will be fetched
          * from the Gogs API again.
          *
-         * @deprecated since 2.0 to be removed in 3.0
+         * @throws Exception\NotImplementedException when method doesnt support load
          * @return object
          */
         final public function load(bool $force = false) {
-            $this->set_scope("get");
+            if (!$this->set_scope("load"))
+                throw new Exception\NotImplementedException("::load:: Not implemented for class '" . get_class($this) . "'");
 
             if ($this->loaded && !$force)
                 return $this;

+ 67 - 31
src/API/Request/Collection.php

@@ -7,10 +7,24 @@ namespace Gogs\API\Request {
      *
      * @see Users
      * @author Joachim M. Giaever (joachim[]giaever.org)
-     * @version 0.1
+     * @version 0.1.1
      */
     abstract class Collection extends Base implements \Gogs\Lib\ArrayIterator {
-        private $objs = array();
+
+        private $objs;
+
+        public function __construct(string $api_url, string $api_token, Collection $other = null) {
+            parent::__construct($api_url, $api_token);
+
+            if ($other != null)
+                $this->objs = $others->copy();
+            else
+                $this->objs = new \Gogs\Lib\Collection();
+        }
+
+        public function copy() {
+            return new Collection($this);
+        }
 
         /**
          * Add an object to the collection.
@@ -23,12 +37,8 @@ namespace Gogs\API\Request {
          * @return mixed|int The index key. If key is null the returned value will be an integer.
          */
         public function add($obj, $key = null) {
-            if (!isset($key))
-                array_push($this->objs, $obj);
-            else
-                $this->objs[$key] = $obj;
-
-            return $key == null ? $this->len - 1 : $key;
+            $this->objs->set($obj, $key);
+            return $key == null ? $this->objs->len() - 1 : $key;
         }
 
         /** 
@@ -40,75 +50,90 @@ namespace Gogs\API\Request {
          *
          * Deep functions only when the value is given and not the key.
          *
+         * @deprecated 0.1.1 Will be removed in future release
          * @param mixed $any Index key or element value
          * @param bool $deep Delete every item and not just the first 
          * @return bool
          */
-        public function remove($any, bool $deep = true) {
-            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);
-
-                // No need to add deep ($key deletion)
-                $val = $this->remove($key);
-
-                if ($val && $deep) // Delete every object
-                    $this->remove($any, $deep);
-
-                return $val;
-            }
-            return false;
+        protected function remove($any, bool $deep = true) {
+            return $objs->remove($any, $deep);
         }
 
         /**
          * @see \Gogs\Lib\ArrayIterator
          */
         public function all() {
-            return $this->objs;
+            return $this->objs->copy()->all();
         }
 
         /**
          * @see \Gogs\Lib\ArrayIterator
          */
         public function len() {
-            return count($this->objs);
+            return $this->objs->len();
         }
 
         /**
          * @see \Gogs\Lib\ArrayIterator
          */
         public function by_key($idx) {
-            return isset($this->objs[$idx]) ? $this->objs[$idx] : false;
+            return $this->objs->by_key($idx);
         }
 
         /**
          * @see \Gogs\Lib\ArrayIterator
          */
         public function next() {
-            return next($this->objs);
+            return $this->objs->next();
         }
 
         /**
          * @see \Gogs\Lib\ArrayIterator
          */
         public function prev() {
-            return prev($this->objs);
+            return $this->objs->prev();
         }
 
         /**
          * @see \Gogs\Lib\ArrayIterator
          */
         public function current() {
-            return current($this->objs);
+            return $this->objs->current();
         }
 
         /**
          * @see \Gogs\Lib\ArrayIterator
          */
         public function reset() {
-            return reset($this->objs);
+            return $this->objs->reset();
+        }
+
+        /**
+         * @see \Gogs\Lib\ArrayIterator
+         */
+        public function sort(callable $f) {
+            return $this->objs->copy()->sort($f);
+        }
+
+        /**
+         * @see \Gogs\Lib\ArrayIterator
+         */
+        public function limit(int $lim) {
+            return $this->objs->copy()->limit($lim);
+        }
+
+        /**
+         * @see \Gogs\Lib\ArrayIterator
+         */
+        public function offset(int $off) {
+            return $this->objs->copy()->offset($off);
+        }
+
+        /**
+         * @see \Gogs\Lib\ArrayIterator
+         */
+        public function reverse() {
+            return $this->objs->copy()->reverse();
         }
 
         /** 
@@ -118,6 +143,17 @@ namespace Gogs\API\Request {
          * @return \Gogs\Lib\Collection
          */
         abstract public function search(array $params = array());
+
+        /**
+         * Sort the object
+         *
+         * Should call sort on parent with the specified sort method,
+         * given by $flag
+         *
+         * @param int $flag Sorting flag
+         * @return \Gogs\Lib\Collection
+         */
+        abstract public function sort_by(int $flag = \Gogs\Lib\ArrayIterator::SORT_INDEX);
     }
 
 }

+ 27 - 4
src/API/Request/Orgs.php

@@ -16,14 +16,30 @@ namespace Gogs\API\Request {
             parent::__construct($api_url, $api_token);
         }
 
+        /**
+         * @see Base
+         */
         protected function set_scope(string $method) {
             switch ($method) {
             case "get":
+            case "load":
                 $this->scope = ($this->owner == null || $this->owner->authenticated() ? "/user" : "/users/" . $this->owner->username) . "/orgs";
                 return true;
+            default:
+                return false;
             }
         }
 
+        /**
+         * Create a new organization
+         *
+         * If arguments are given, the User will be created,
+         * otherise it will return an initialized object,
+         * leaving the programmer to create the user.
+         *
+         * @see Base
+         * @return User
+         */
         public function create(...$args) {
 
             $org = new Org($this->url, $this->token, $this->owner);
@@ -69,8 +85,8 @@ namespace Gogs\API\Request {
          * By now, this method can be intensive, as it will load
          * every organization and then do a match on each entry.
          *
-         * @param array $params
-         * @return CollectionResult
+         * @param array $params Search parameters
+         * @return Orgs
          * @throws Exception\SearchParamException on missing parameters
          */
         public function search(array $params = array()) {
@@ -83,11 +99,11 @@ namespace Gogs\API\Request {
 
             $this->load();
 
-            $orgs = new CollectionResult();
+            $orgs = new Orgs($this->url, $this->token, $this->owner);
 
             foreach ($this->all() as $key => $org) {
                 if ($org->search($q))
-                    $orgs->set($org);
+                    $orgs->add($org, $org->username);
                 if ($orgs->len() == $l)
                     break;
             }
@@ -111,6 +127,13 @@ namespace Gogs\API\Request {
 
             return $rnames;
         }
+
+        /**
+         * @see Collection
+         */
+        public function sort_by(int $flag = Collection::SORT_INDEX) {
+            return $this->sort("ksort");
+        }
     }
 
 }

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

@@ -85,6 +85,7 @@ namespace Gogs\API\Request {
                 $this->scope = "/repos/" . $this->owner->username . "/" . $this->name;
                 break;
             case "get":
+            case "load":
                 if (empty($this->owner->username) && empty($this->full_name))
                     throw new Exception\RequestErrorException("Missing userdata 'username' and/or 'full_name'");
 

+ 55 - 19
src/API/Request/Repos.php

@@ -6,9 +6,14 @@ namespace Gogs\API\Request {
      * Repos is a collection of repos.
      *
      * @author Joachim M. Giaever (joachim[]giaever.org)
-     * @version 0.1
+     * @version 0.1.1
      */
     final class Repos extends Collection {
+
+        const SORT_UPDATED = Collection::SORT_INDEX << 1;
+        const SORT_CREATED = Collection::SORT_INDEX << 2;
+        const SORT_OWNER = Collection::SORT_INDEX << 3;
+
         protected $owner;
 
         /**
@@ -32,6 +37,7 @@ namespace Gogs\API\Request {
         protected function set_scope(string $method) {
             switch ($method) {
             case "get":
+            case "load":
                 if ($this->owner instanceof Org)
                     $this->scope = "/orgs/" . $this->owner->username . "/repos";
                 else
@@ -54,8 +60,10 @@ namespace Gogs\API\Request {
 
             $repo = new Repo($this->url, $this->token, $this->owner);
 
-            if (count($args) > 0)
+            if (count($args) > 0) {
                 $repo->create(...$args);
+                $this->add($repo, $repo->full_name);
+            }
 
             return $repo;
         }
@@ -66,11 +74,10 @@ namespace Gogs\API\Request {
          * If the owner is specified the search will be 
          * limited to the actual user.
          *
-         * @todo Should rewrite now since set_scope is now the deal
          * @see Collection
+         * @return Repos
          */
         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>");
@@ -80,40 +87,69 @@ namespace Gogs\API\Request {
                 unset($params["name"]);
             }
 
-            if (!isset($params["user"]))
+            if (!isset($params["user"]) || isset($this->owner))
                 $params["user"] = isset($this->owner) ? $this->owner->id : 0;
 
             if (!isset($params["limit"]))
                 $params["limit"] = 10;
 
-            $repos = new \Gogs\Lib\Collection();
+            $repos = new Repos($this->url, $this->token, $this->owner);
             
-            switch ($scope) {
-            case "user":
-            case "users":
-            case "org":
-                $this->load();
-
+            if ($this->loaded) {
                 foreach($this->all() as $key => $repo) {
                     if ($repo->search($params["q"]))
-                        $repos->set($repo);
+                        $repos->add($repo, $key);
                     if ($repos->len() == $params["limit"])
                         break;
                 }
-
-            default:
+            } else {
                 $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);
-
+                    $repos->add($this->by_key($key), $key);
             }
             
             return $repos;
         }
 
+        /**
+         * Sort repos by `method`.
+         *
+         * Valid methods:
+         *
+         *  * SORT_UPDATED: Sort on `updated_at` value
+         *  * SORT_CREATED: Sort on `created_at` value
+         *  * SORT_OWNER: Sort on `owner` (organization repos etc may appear)
+         * 
+         * @param int $flag Defines sorting algorithm to use
+         * @param bool $asc Ascending order
+         * @return \Gogs\Lib\Collection
+         */
+        public function sort_by(int $flag = Collection::SORT_INDEX, bool $asc = false) {
+            switch ($flag) {
+            case self::SORT_CREATED:
+                return ($sort = $this->sort(function(Repo $a, Repo $b) {
+                    $adate = new \DateTime($a->created_at);
+                    $bdate = new \DateTime($b->created_at);
+                    return ($adate == $bdate ? 0 : ($adate > $bdate ? 1 : -1));
+                })) ? ($asc ? $sort->reverse() : $sort) : false;
+            case self::SORT_UPDATED:
+                return ($sort = $this->sort(function(Repo $a, Repo $b) {
+                    $adate = new \DateTime($a->updated_at);
+                    $bdate = new \DateTime($b->updated_at);
+                    return ($adate == $bdate ? 0 : ($adate > $bdate ? 1 : -1));
+                })) ? ($asc ? $sort->reverse() : $sort) : false;
+            case self::SORT_OWNER:
+                return ($sort = $this->sort(function(Repo $a, Repo $b) {
+                    return strcmp($a->owner->username, $b->owner->username);
+                })) ? ($asc ? $sort->reverse() : $sort) : false;
+            default:
+                return ($sort = $this->sort("ksort")) ? ($asc ? $sort->reverse() : $sort) : false;
+            }
+        }
+
         /** 
          * @see Base
          */
@@ -127,8 +163,8 @@ namespace Gogs\API\Request {
                 foreach ($obj as $key => $val) {
                     $repo = new Repo($this->url, $this->token);
                     $repo->json_set_property($val);
-                    $this->add($repo, $repo->rfull_name);
-                    $rnames[] = $repo->rfull_name;
+                    $this->add($repo, $repo->full_name);
+                    $rnames[] = $repo->full_name;
                 }
             }
 

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

@@ -65,6 +65,7 @@ namespace Gogs\API\Request {
                 $this->scope = "/admin/users/" . $this->username;
                 break;
             case "get":
+            case "load":
                 if (!$this->authenticated && empty($this->username))
                     throw new Exception\RequestErrorException("Missing userdata 'username'.");
 

+ 14 - 8
src/API/Request/Users.php

@@ -7,16 +7,15 @@ namespace Gogs\API\Request {
      * 
      * @author Joachim M. Giaever (joachim[]giaever.org)
      * @package request
+     * @version 0.1.1
      */
     final class Users extends Collection {
 
         protected function set_scope(string $method) {
             switch ($method) {
-            case "get":
-                $this->scope = "/users";
-                break;
             case "search":
                 $this->scope = "/users/search";
+                break;
             default:
                 return false;
             }
@@ -67,11 +66,14 @@ namespace Gogs\API\Request {
             }
 
             $this->set_scope("search");
-            $jenc = $this->method_get($params);
 
             $old = $this->all();
 
-            $this->json_set_property($this->json_decode($jenc));
+            $this->json_set_property(
+                $this->json_decode(
+                    $this->method_get($params)
+                )
+            );
 
             $users = new \Gogs\Lib\Collection();
 
@@ -80,16 +82,20 @@ namespace Gogs\API\Request {
             return $users;
         }
 
+        public function sort_by(int $flag = Collection::SORT_INDEX) {
+            return $this->sort("ksort");
+        }
+
         protected function json_set_property($obj) {
             $rnames[] = array();
 
             if (isset($obj->data)) {
                 foreach($obj->data as $key => $val) {
-                    $user = new User($this->url, $this->token, $val->login);
+                    $user = new User($this->url, $this->token, $val->username);
                     $user->json_set_property($val);
-                    $this->add($user, $user->login);
+                    $this->add($user, $user->username);
 
-                    $rnames[] = $user->login;
+                    $rnames[] = $user->username;
                 }
             }
 

+ 91 - 51
src/Lib/ArrayIterator.php

@@ -1,56 +1,96 @@
-<?php namespace Gogs\Lib;
-
-/**
- * Interface to store one or more elements in array
- * providing an iterator interface.
- */
-interface ArrayIterator {
-    /** 
-     * Get current element in collection.
-     * @return 
-     */
-    public function current();
+<?php 
 
-    /** 
-     * Get next element in collection.
-     * 
-     * @return mixed
+namespace Gogs\Lib {
+    /**
+     * Interface to store one or more elements in array
+     * providing an iterator interface.
+     * @version 0.1.1
      */
-    public function next();
+    interface ArrayIterator {
+        // Default sorting method; ksort (array index)
+        const SORT_INDEX = 1 << 1;
+        /** 
+         * Get current element in collection.
+         * @return 
+         */
+        public function current();
 
-    /** 
-     * Return previous element in collection.
-     * @return mixed
-     */
-    public function prev();
+        /** 
+         * Get next element in collection.
+         * 
+         * @return mixed
+         */
+        public function next();
 
-    /**
-     * Reset collection (set array to head).
-     *
-     * @return mixed Returns first elements value.
-     */
-    public function reset();
-    
-    /** 
-     * Return collection size.
-     *
-     * @return int
-     */
-    public function len();
-    
-    /** 
-     * Return the whole colection.
-     *
-     * @return array
-     */
-    public function all();
-    
-    /** 
-     * Get element by index key.
-     *
-     * @param mixed $idx Index key.
-     * @return mixed
-     */
-    public function by_key($idx);
-}
+        /** 
+         * Return previous element in collection.
+         * @return mixed
+         */
+        public function prev();
+
+        /**
+         * Reset collection (set array to head).
+         *
+         * @return mixed Returns first elements value.
+         */
+        public function reset();
+        
+        /** 
+         * Return collection size.
+         *
+         * @return int
+         */
+        public function len();
+        
+        /** 
+         * Return the whole colection.
+         *
+         * @return array
+         */
+        public function all();
+        
+        /** 
+         * Get element by index key.
+         *
+         * @param mixed $idx Index key.
+         * @return mixed
+         */
+        public function by_key($idx);
+
+        /**
+         * Copy collection
+         *
+         * @return Colletion
+         */
+        public function copy();
 
+        /**
+         * Limit until in collection
+         *
+         * @param int $lim Maximum entries returned
+         * @return Collection
+         */
+        public function limit(int $lim);
+
+        /**
+         * Get from offset collection
+         *
+         * @param int $offset Offset from in collection
+         * @return Collection
+         */
+        public function offset(int $off);
+
+        /**
+         * Reverse the collection
+         *
+         * @return Collection
+         */
+        public function reverse();
+        /**
+         * Sort collection
+         *
+         * @return Collection
+         */
+        public function sort(callable $f);
+    }
+}

+ 73 - 1
src/Lib/Collection.php

@@ -8,10 +8,14 @@ namespace Gogs\Lib {
      * which wont be a part of the "request package"
      *
      * @author Joachim M. Giaever (joachim[]giaever.org)
+     * @version 0.1.1
      */
     class Collection implements ArrayIterator {
-        private $objs = array();
+        private $objs;
 
+        public function __construct(array $arr = array()) {
+            $this->objs = $arr;
+        }
         /**
          * Set value(e) to the collection.
          *
@@ -34,6 +38,10 @@ namespace Gogs\Lib {
             return isset($this->objs[$idx]) ? $this->objs[$idx] : false;
         }
 
+        public function copy() {
+            return new Collection($this->all());
+        }
+
         /** 
          * @see ArrayIterator
          */
@@ -76,6 +84,70 @@ namespace Gogs\Lib {
             return reset($this->objs);
         }
 
+        /**
+         * @see \Gogs\Lib\ArrayIterator
+         */
+        public function sort(callable $f) {
+            if ($f == "" || $f == "ksort")
+                return ksort($this->objs) ? $this : false;
+
+            return uasort($this->objs, $f) ? $this : false;
+        }
+
+        /**
+         * @see \Gogs\Lib\ArrayIterator
+         */
+        public function limit(int $lim) {
+            $this->objs = array_slice($this->objs, 0, $lim);
+            return $this;
+        }
+
+        /**
+         * @see \Gogs\Lib\ArrayIterator
+         */
+        public function offset(int $off) {
+            $this->objs = array_slice($this->objs, $off);
+            return $this;
+        }
+
+        /**
+         * @see \Gogs\Lib\ArrayIterator
+         */
+        public function reverse() {
+            $this->objs = array_reverse($this->objs);
+            return $this;
+        }
+        /** 
+         * Remove an element in collection.
+         *
+         * The function will first look for the element as a
+         * index key, but if its not found it will look for the
+         * element as a value.
+         *
+         * Deep functions only when the value is given and not the key.
+         *
+         * @deprecated 0.1.1 Will be removed in future release
+         * @param mixed $any Index key or element value
+         * @param bool $deep Delete every item and not just the first 
+         * @return bool
+         */
+        public function remove($any, bool $deep = true) {
+            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);
+
+                // No need to add deep ($key deletion)
+                $val = $this->remove($key);
+
+                if ($val && $deep) // Delete every object
+                    $this->remove($any, $deep);
+
+                return $val;
+            }
+            return false;
+        }
     }
 
 }

+ 39 - 2
src/Lib/Curl/Client.php

@@ -7,9 +7,11 @@ namespace Gogs\Lib\Curl {
      *
      * @author Joachim M. Giaever (joachim[]giaever.org)
      * @package curl
-     * @version 0.1
+     * @version 0.1.1
      */
     trait Client {
+        private static $log = array();
+
         protected $url;
         protected $token;
 
@@ -57,6 +59,7 @@ namespace Gogs\Lib\Curl {
          * @return int the status code
          */
         protected function method(string $method, string &$req, string $scope, array $params, bool $ret) {
+
             $c = curl_init();
 
             if (!$c) {
@@ -69,7 +72,17 @@ namespace Gogs\Lib\Curl {
 
             $url = sprintf("%s%s", $this->url, $scope);
 
-            echo sprintf("%s: %s, params: %s\n", $method, $url, $this->array_2_json($params));
+            array_push(
+                self::$log,
+                sprintf(
+                    "%s:[%s] %s, %s, %s", 
+                    date("y-m-d H:i:s"), 
+                    $method, 
+                    $url, 
+                    !empty($p = $this->array_2_json($params)) ? $p : "none",
+                    get_class($this)
+                )
+            );
 
             if (in_array($method, array("DELETE", "PATCH", "POST"))) {
                 $json = $this->array_2_json($params);
@@ -95,6 +108,18 @@ namespace Gogs\Lib\Curl {
 
             curl_close($c);
 
+            array_push(
+                self::$log,
+                sprintf(
+                    "%s:[%s] %s, %d, %s", 
+                    date("y-m-d H:i:s"), 
+                    $method, 
+                    $url,
+                    $status_code,
+                    substr($req, 0, 100) . (strlen($req) > 100 ? "..." : ".")
+                )
+            );
+
             return $status_code;
         }
 
@@ -197,6 +222,18 @@ namespace Gogs\Lib\Curl {
             }
         }
 
+        /**
+         * Returns log entries for the client.
+         * 
+         * @return array
+         */
+        public static function get_log() {
+            if (empty(self::$log))
+                return self::$log;
+
+            return array_merge(self::$log, array("\n"));
+        }
+
     }
 
 }