1
0

/about/ + extendedGitGraph

This commit is contained in:
2018-01-21 15:29:01 +01:00
parent d77954ddae
commit 28ae8beb2d
26 changed files with 1346 additions and 48 deletions

39
www/extern/egh/ConnectionGitea.php vendored Normal file
View File

@@ -0,0 +1,39 @@
<?php
require_once 'SingleCommitInfo.php';
class ConnectionGitea
{
/* @var string */
private $url;
/* @var string */
private $owner;
/**
* @param $owner ExtendedGitGraph
*/
public function __construct($owner) {
$this->owner = $owner;
}
public function setURL($giteaurl) {
$this->url = $giteaurl;
}
/* @return SingleCommitInfo[] */
public function getDataUser($cfg)
{
$result = []; //TODO
return $result;
}
/* @return SingleCommitInfo[] */
public function getDataRepository($cfg)
{
$result = []; //TODO
return $result;
}
}

166
www/extern/egh/ConnectionGithub.php vendored Normal file
View File

@@ -0,0 +1,166 @@
<?php
require_once 'SingleCommitInfo.php';
require_once 'Utils.php';
class ConnectionGithub
{
const API_OAUTH_AUTH = 'https://github.com/login/oauth/authorize?client_id=%s';
const URL_OAUTH_TOKEN = 'https://github.com/login/oauth/access_token?client_id={id}&client_secret={secret}&code={code}';
const API_RATELIMIT = 'https://api.github.com/rate_limit';
const API_REPOSITORIESLIST = 'https://api.github.com/users/{user}/repos?page={page}&per_page=100';
const API_COMMITSLIST = 'https://api.github.com/repos/{repo}/commits?per_page=100&page={page}&author={author}';
/* @var string */
private $token;
/* @var string */
private $owner;
/**
* @param $owner ExtendedGitGraph
*/
public function __construct($owner) {
$this->owner = $owner;
}
public function setAPIToken($token) {
$this->token = $token;
}
public function queryAPIToken($client_id, $client_secret) {
$url = Utils::sharpFormat(self::URL_OAUTH_TOKEN, ['id'=>$client_id, 'secret'=>$client_secret, 'code'=>'egh']);
$result = file_get_contents($url);
$result = str_replace('access_token=', '', $result);
$result = str_replace('&scope=&token_type=bearer', '', $result);
$this->setAPIToken($result);
}
/**
* @param $cfg EGHRemoteConfig
* @return SingleCommitInfo[]
*/
public function getDataUser($cfg)
{
$repos = $this->listRepositoriesByUser($cfg->Param);
$result = [];
foreach ($repos as $repo)
{
$commits = $this->listCommitsFromRepo($repo, $cfg->Author);
foreach ($commits as $c) $result []= $c;
}
return $result;
}
/**
* @param $cfg EGHRemoteConfig
* @return SingleCommitInfo[]
*/
public function getDataRepository($cfg)
{
return $this->listCommitsFromRepo($cfg->Param, $cfg->Author);
}
/**
* @param $user string
* @return string[]
*/
private function listRepositoriesByUser($user)
{
$result = [];
$page = 1;
$url = Utils::sharpFormat(self::API_REPOSITORIESLIST, ['user'=>$user, 'page'=>$page, 'token'=>$this->token]);
$json = $this->getJSON($url);
while (! empty($json)) {
foreach ($json as $result_repo) {
$result []= $result_repo->{'full_name'};
$this->owner->out("Found Repo: " . $result_repo->{'full_name'});
}
$page++;
$url = Utils::sharpFormat(self::API_REPOSITORIESLIST, ['user'=>$user, 'page'=>$page, 'token'=>$this->token]);
$json = $this->getJSON($url);
}
return $result;
}
/**
* @param $repo string
* @param $user string
* @return SingleCommitInfo[]
*/
private function listCommitsFromRepo($repo, $user)
{
$page = 1;
$url = Utils::sharpFormat(self::API_COMMITSLIST, ['repo'=>$repo, 'author'=>$user, 'page'=>$page, 'token'=>$this->token]);
$result = $this->getJSON($url);
$commit_list = [];
while (! empty($result)) {
foreach ($result as $rc) $commit_list[] = new SingleCommitInfo(DateTime::createFromFormat(DateTime::ISO8601, $rc->{'commit'}->{'author'}->{'date'}), 'github', $user, $repo);
$this->owner->out("Found " . count($result) . " Commits in " . $repo);
$page++;
$url = Utils::sharpFormat(self::API_COMMITSLIST, [ 'repo'=>$repo, 'author'=>$user, 'page'=>$page, 'token'=>$this->token ]);
$result = $this->getJSON($url);
}
return $commit_list;
}
public function getJSON($url) {
if (array_key_exists('HTTP_USER_AGENT', $_SERVER)) {
$options =
[
'http' =>
[
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'header' => 'Authorization: token ' . $this->token,
],
'https' =>
[
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'header' => 'Authorization: token ' . $this->token,
],
];
} else {
$options =
[
'http' =>
[
'user_agent' => 'ExtendedGitGraph_for_mikescher.com',
'header' => 'Authorization: token ' . $this->token,
],
'https' =>
[
'user_agent' => 'ExtendedGitGraph_for_mikescher.com',
'header' => 'Authorization: token ' . $this->token,
],
];
}
$context = stream_context_create($options);
$response = @file_get_contents($url, false, $context);
if ($response === false)
{
$this->owner->out("Error recieving json: '" . $url . "'");
return [];
}
return json_decode($response);
}
}

27
www/extern/egh/EGHRemoteConfig.php vendored Normal file
View File

@@ -0,0 +1,27 @@
<?php
class EGHRemoteConfig
{
/* @var string|null */
public $Type;
/* @var string|null */
public $URL;
/* @var string|null */
public $Author;
/* @var string|null */
public $Param;
/**
* @param $typ string|null
* @param $url string|null
* @param $usr string|null
* @param $param string|null
*/
public function __construct($typ, $url, $usr, $param) {
$this->Type = $typ;
$this->URL = $url;
$this->Author = $usr;
$this->Param = $param;
}
}

224
www/extern/egh/EGHRenderer.php vendored Normal file
View File

@@ -0,0 +1,224 @@
<?php
require_once 'ExtendedGitGraph.php';
require_once 'SingleCommitInfo.php';
require_once 'Utils.php';
class EGHRenderer
{
const DIST_X = 13;
const DIST_Y = 13;
const DAY_WIDTH = 11;
const DAY_HEIGHT = 11;
const COMMITCOUNT_COLOR_UPPERLIMIT = 16;
const COLOR_SCHEMES =
[
'custom' => ['#F5F5F5', '#DBDEE0', '#C2C7CB', '#AAB0B7', '#9099A2', '#77828E', '#5E6B79', '#455464', '#2C3E50'],
'standard' => ["#ebedf0", "#c6e48b", "#7bc96f", "#239a3b", "#196127"],
'modern' => ["#afaca8", "#d6e685", "#8cc665", "#44a340", "#1e6823"],
'gray' => ["#eeeeee", "#bdbdbd", "#9e9e9e", "#616161", "#212121"],
'red' => ["#eeeeee", "#ff7171", "#ff0000", "#b70000", "#830000"],
'blue' => ["#eeeeee", "#6bcdff", "#00a1f3", "#0079b7", "#003958"],
'purple' => ["#eeeeee", "#d2ace6", "#aa66cc", "#660099", "#4f2266"],
'orange' => ["#eeeeee", "#ffcc80", "#ffa726", "#fb8c00", "#e65100"],
'halloween' => ["#eeeeee", "#ffee4a", "#ffc501", "#fe9600", "#03001c"],
];
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const DAYS = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
/* @var ExtendedGitGraph */
private $owner;
/* @var SingleCommitInfo[] */
public $data;
/* @var string */
public $colorScheme = 'standard';
/* @var int[] */
public $yearList;
/* @var array */
public $commitMap; // date('Y-m-d') -> count
public function __construct($egh) {
$this->owner = $egh;
}
/**
* @param $data SingleCommitInfo[]
*/
public function init($data)
{
$this->data = $data;
$this->yearList = $this->getYears($data);
$this->owner->out("Found " . count($this->yearList) . " year to generate.");
$this->commitMap = $this->generateCommitMap($data, $this->yearList);
$this->owner->out("Commitmap with ".count($this->commitMap)." entries generated.");
}
/**
* @param $data SingleCommitInfo[]
* @return int[]
*/
public function getYears($data) {
$years = array();
foreach ($data as $commit) {
if(! in_array($commit->Timestamp->format('Y'), $years))
$years[] = intval($commit->Timestamp->format('Y'));
}
asort($years);
return $years;
}
/**
* @param $data SingleCommitInfo[]
* @param $yearList int[]
* @return array
*/
private function generateCommitMap($data, $yearList)
{
$result = [];
foreach ($yearList as $year)
{
$ymap = [];
$date = new DateTime($year . '-01-01');
while($date->format('Y') == $year)
{
$ymap[$date->format('Y-m-d')] = 0;
$date = $date->modify("+1 day");
}
foreach ($data as $commit)
{
if(array_key_exists($commit->Timestamp->format('Y-m-d'), $ymap)) $ymap[$commit->Timestamp->format('Y-m-d')]++;
}
$result = array_merge($result, $ymap);
}
return $result;
}
/**
* @param $year int
* @return int
*/
private function getMaxCommitCount($year)
{
$max = 0;
foreach ($this->commitMap as $date => $count) if (Utils::startsWith($date, strval($year))) $max = max($max, $count);
return $max;
}
/**
* @param $year int
* @return string
*/
public function render($year)
{
$now = new DateTime();
$date = new DateTime($year . '-01-01');
$monthlist = array_fill(0, 12, [0, 0]);
$colors = self::COLOR_SCHEMES[$this->colorScheme];
$ymapmax = $this->getMaxCommitCount($year);
$exponent = log(0.98/(count($colors)-1), 1/$ymapmax); // (1/max)^n = 0.98 // => 1 commit erreicht immer genau die erste stufe
$html = '';
$html .= '<div class="extGitGraphContainer">' . "\n";
$html .= '<svg class="git_list" viewBox="0 0 715 115">' . "\n";
$html .= '<g transform="translate(20, 20) ">' . "\n";
$html .= '<g transform="translate(0, 0)">' . "\n";
$week = 0;
$wday = 0;
while($date->format('Y') == $year)
{
if ($date > $now) // THE FUTURE, SPONGEBOB
{
while ($date->format('d') != $date->format('t'))
{
if ($date->format('N') == 1 && $date->format('z') > 0) $week++;
$date = $date->modify("+1 day");
}
$monthlist[$date->format('m') - 1][1] = $week + ($wday / 7);
$date = $date->modify("+1 year"); // Kill
continue;
}
$c_count = $this->commitMap[$date->format('Y-m-d')];
$color_idx = min((count($colors)-1), ceil(pow($c_count/$ymapmax, $exponent) * (count($colors)-1)));
$color = $colors[$color_idx];
$wday = ($date->format('N') - 1);
if ($date->format('N') == 1 && $date->format('z') > 0)
{
$html .= '</g>' . "\n";
$week++;
$html .= '<g transform="translate(' . $week * self::DIST_X . ', 0)">' . "\n";
}
if ($date->format('d') == 1)
{
$monthlist[$date->format('m') - 1][0] = $week + ($wday / 7);
}
else if ($date->format('d') == $date->format('t'))
{
$monthlist[$date->format('m') - 1][1] = $week + ($wday / 7);
}
$html .= '<rect'.
' style=' .'"fill:'.$color.';' . '"' .
' y="' . ($wday * self::DIST_Y) . '"' .
' height="' . self::DAY_HEIGHT . '"' .
' width="' . self::DAY_WIDTH . '"' .
' class="' . 'egg_rect' . '"' .
' data-count="' . $c_count . '"' .
' data-date="' . ' ' . $date->format('Y-m-d') . '"' .
'></rect>' . "\n";
$date = $date->modify("+1 day");
}
$html .= '</g>' . "\n";
for($i = 0; $i < 12; $i++)
{
if ($monthlist[$i][1]-$monthlist[$i][0] > 0)
{
$posx = (($monthlist[$i][0]+$monthlist[$i][1])/2) * self::DIST_X;
$html .= '<text y="-3" x="' . $posx . '" style="text-anchor: middle" class="caption_month">' . self::MONTHS[$i] . '</text>' . "\n";
}
}
for($i = 0; $i < 7; $i++) {
$html .= '<text y="' . ($i*self::DIST_Y + self::DAY_HEIGHT/2) . '" x="-6" style="text-anchor: middle" class="caption_day" dominant-baseline="central">' . self::DAYS[$i] . '</text>' . "\n";
}
$html .= '<text x="-10" y="-5" class="caption">' . $year . '</text>' . "\n";
$html .= '</g>' . "\n";
$html .= '</svg>' . "\n";
$html .= '<div class="svg-tip n">' . "\n";
$html .= '<strong>&nbsp;</strong><span>&nbsp;</span>' . "\n";
$html .= '</div>' . "\n";
$html .= '<div class="egg_footer">' . "\n";
$html .= '<a href="https://www.mikescher.com/programs/view/ExtendedGitGraph">extendedGitGraph</a>' . "\n";
$html .= '</div>' . "\n";
$html .= '</div>' . "\n";
return $html;
}
}

184
www/extern/egh/ExtendedGitGraph.php vendored Normal file
View File

@@ -0,0 +1,184 @@
<?php
require_once 'EGHRemoteConfig.php';
require_once 'ConnectionGithub.php';
require_once 'ConnectionGitea.php';
require_once 'SingleCommitInfo.php';
require_once 'EGHRenderer.php';
class ExtendedGitGraph
{
const OUT_SESSION = 0;
const OUT_STDOUT = 1;
const OUT_LOGFILE = 2;
const PROGRESS_SESSION_COOKIE = 'ajax_progress_egh_refresh';
const COMMITCOUNT_COLOR_UPPERLIMIT = 16;
/* @var string */
private $filenamecache;
/* @var EGHRemoteConfig[] */
private $remoteconfigs;
/* @var ConnectionGithub */
public $ConnectionGithub;
/* @var ConnectionGitea */
public $ConnectionGitea;
/* @var int */
private $outputMode = self::OUT_SESSION;
/* @var string */
private $logFilePath;
/* @var array */
private $renderedHTML;
/* @var SingleCommitInfo[] */
private $queriedData;
/* @var string */
private $colorScheme = 'blue';
public function __construct($filename_cache, $outmode, $logfile) {
$this->filenamecache = $filename_cache;
$this->remoteconfigs = [];
$this->ConnectionGithub = new ConnectionGithub($this);
$this->ConnectionGitea = new ConnectionGitea($this);
$this->outputMode = $outmode;
$this->logFilePath = $logfile;
}
public function addRemote($type, $url, $user, $param) {
$this->remoteconfigs []= new EGHRemoteConfig($type, $url, $user, $param);
}
public function setColorScheme($s) {
$this->colorScheme = $s;
}
public function out($txt)
{
if ($txt !== '') $txt = '[' . date('H:i:s') . '] ' . $txt;
if ($this->outputMode === self::OUT_SESSION)
{
if (session_status() !== PHP_SESSION_ACTIVE) session_start();
$_SESSION[self::PROGRESS_SESSION_COOKIE] .= $txt . "\r\n";
session_commit();
}
else if ($this->outputMode === self::OUT_STDOUT)
{
print $txt;
print "\r\n";
}
$logfile = Utils::sharpFormat($this->logFilePath, ['num'=>'']);
file_put_contents($logfile, $txt.PHP_EOL , FILE_APPEND | LOCK_EX);
}
public function init()
{
if ($this->outputMode === self::OUT_SESSION)
{
if (session_status() !== PHP_SESSION_ACTIVE) session_start();
$_SESSION[self::PROGRESS_SESSION_COOKIE] = '';
session_commit();
}
$f3 = Utils::sharpFormat($this->logFilePath, ['num'=>'_3']);
$f2 = Utils::sharpFormat($this->logFilePath, ['num'=>'_2']);
$f1 = Utils::sharpFormat($this->logFilePath, ['num'=>'_1']);
$f0 = Utils::sharpFormat($this->logFilePath, ['num'=>'' ]);
if (file_exists($f3)) @unlink($f3);
if (file_exists($f2)) @rename($f2, $f3);
if (file_exists($f1)) @rename($f1, $f2);
if (file_exists($f0)) @rename($f0, $f1);
if (file_exists($f0)) @unlink($f0);
$this->out('EXTENDED_GIT_GRAPH started');
$this->out('');
}
public function updateFromRemotes()
{
$data = [];
foreach ($this->remoteconfigs as $cfg)
{
if ($cfg->Type === 'github-user')
$data = array_merge($data, $this->ConnectionGithub->getDataUser($cfg));
else if ($cfg->Type === 'github-repository')
$data = array_merge($data, $this->ConnectionGithub->getDataRepository($cfg));
else if ($cfg->Type === 'gitea-user')
$data = array_merge($data, $this->ConnectionGitea->getDataUser($cfg));
else if ($cfg->Type === 'gitea-repository')
$data = array_merge($data, $this->ConnectionGitea->getDataRepository($cfg));
else
$this->out("Unknown type: " . $cfg->Type);
}
$this->out("Found " . count($data) . " commits.");
file_put_contents($this->filenamecache, serialize($data));
$this->queriedData = $data;
}
public function updateFromCache()
{
if (file_exists($this->filenamecache))
$this->queriedData = unserialize(file_get_contents($this->filenamecache));
else
$this->queriedData = [];
}
public function generate()
{
$renderer = new EGHRenderer($this);
$renderer->colorScheme = $this->colorScheme;
$renderer->init($this->queriedData);
$this->renderedHTML = [];
foreach ($renderer->yearList as $y) $this->renderedHTML[$y] = $renderer->render($y);
}
/**
* @param $url string
* @return array|mixed
*/
public function getJSON($url) {
if (array_key_exists('HTTP_USER_AGENT', $_SERVER)) {
$options =
[
'http' => ['user_agent'=> $_SERVER['HTTP_USER_AGENT']],
'https' => ['user_agent'=> $_SERVER['HTTP_USER_AGENT']],
];
} else {
$options =
[
'http' => ['user_agent'=> 'ExtendedGitGraph_for_mikescher.com'],
'https' => ['user_agent'=> 'ExtendedGitGraph_for_mikescher.com'],
];
}
$context = stream_context_create($options);
$response = @file_get_contents($url, false, $context);
if ($response === false)
{
$this->out("Error recieving json: '" . $url . "'");
return [];
}
return json_decode($response);
}
public function get()
{
return $this->renderedHTML;
}
}

31
www/extern/egh/SingleCommitInfo.php vendored Normal file
View File

@@ -0,0 +1,31 @@
<?php
class SingleCommitInfo
{
/* @var DateTime */
public $Timestamp;
/* @var string */
public $SourcePlatform;
/* @var string */
public $SourceUser;
/* @var string */
public $SourceRepository;
/**
* @param $ts DateTime
* @param $src string
* @param $usr string
* @param $repo string
*/
public function __construct($ts, $src, $usr, $repo) {
$this->Timestamp = $ts;
$this->SourcePlatform = $src;
$this->SourceUser = $usr;
$this->SourceRepository = $repo;
}
}

19
www/extern/egh/Utils.php vendored Normal file
View File

@@ -0,0 +1,19 @@
<?php
class Utils
{
public static function sharpFormat($str, $args)
{
foreach ($args as $key => $val)
{
$str = str_replace('{'.$key.'}', $val, $str);
}
return $str;
}
public static function startsWith($haystack, $needle)
{
$length = strlen($needle);
return (substr($haystack, 0, $length) === $needle);
}
}