Finalize documentation

This commit is contained in:
Daniil Gentili 2020-10-15 19:07:37 +02:00
parent 3d956b9002
commit f4cb2d83a1
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
17 changed files with 171 additions and 752 deletions

View File

@ -57,7 +57,8 @@
"danog/7to5": "^1",
"vimeo/psalm": "dev-master",
"phpstan/phpstan": "^0.12.14",
"friendsofphp/php-cs-fixer": "^2"
"friendsofphp/php-cs-fixer": "dev-master",
"danog/phpdoc": "^0.1"
},
"suggest": {
"ext-libtgvoip": "Install the php-libtgvoip extension to make phone calls (https://github.com/danog/php-libtgvoip)"
@ -97,9 +98,14 @@
],
"build": [
"@docs",
"@phpdocs",
"@cs-fix",
"@psalm"
],
"phpdocs": [
"@phpdocsInternal",
"@phpdocsMain"
],
"check": [
"@cs",
"@test"
@ -109,6 +115,8 @@
"cs": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix -v --diff --dry-run",
"cs-fix": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix -v --diff",
"psalm": "psalm",
"phpdocsMain": "php tools/phpdoc.php",
"phpdocsInternal": "phpdoc docs/docs/PHPInternal",
"docs": "php tools/build_docs.php",
"test": "@php -dzend.assertions=1 -dassert.exception=1 ./vendor/bin/phpunit --coverage-text --config tests/phpunit.xml"
}

View File

@ -31,6 +31,60 @@ use danog\MadelineProto\Settings\Logger as SettingsLogger;
*/
class API extends InternalDoc
{
/**
* Release version.
*
* @var string
*/
const RELEASE = MTProto::RELEASE;
/**
* We're not logged in.
*
* @var int
*/
const NOT_LOGGED_IN = MTProto::NOT_LOGGED_IN;
/**
* We're waiting for the login code.
*
* @var int
*/
const WAITING_CODE = MTProto::WAITING_CODE;
/**
* We're waiting for parameters to sign up.
*
* @var int
*/
const WAITING_SIGNUP = MTProto::WAITING_SIGNUP;
/**
* We're waiting for the 2FA password.
*
* @var int
*/
const WAITING_PASSWORD = MTProto::WAITING_PASSWORD;
/**
* We're logged in.
*
* @var int
*/
const LOGGED_IN = MTProto::LOGGED_IN;
/**
* Secret chat was not found.
*
* @var int
*/
const SECRET_EMPTY = MTProto::SECRET_EMPTY;
/**
* Secret chat was requested.
*
* @var int
*/
const SECRET_REQUESTED = MTProto::SECRET_REQUESTED;
/**
* Secret chat was found.
*
* @var int
*/
const SECRET_READY = MTProto::SECRET_READY;
use \danog\Serializable;
use \danog\MadelineProto\ApiWrappers\Start;
use \danog\MadelineProto\ApiWrappers\Templates;

View File

@ -314,6 +314,8 @@ class AnnotationsBuilder
$promise = '\\'.Promise::class;
$phpdoc = $method->getDocComment() ?? '';
$phpdoc = \str_replace("@return \\Generator", "@return $promise", $phpdoc);
$phpdoc = \str_replace("@return \\Promise", "@return $promise", $phpdoc);
$phpdoc = \str_replace("@return Promise", "@return $promise", $phpdoc);
if ($hasReturnValue && $async && \preg_match("/@return (.*)/", $phpdoc, $matches)) {
$ret = $matches[1];
$new = $ret;

View File

@ -15,7 +15,7 @@ interface DbArray extends DbType, \ArrayAccess, \Countable
/**
* Get Array copy.
*
* @psalm-return Promise<array<string|int, T>> $value
* @psalm-return Promise<array<string|int, T>>
*
* @return Promise
*/

View File

@ -4529,7 +4529,7 @@ class InternalDoc extends APIFactory
*
* @psalm-suppress InvalidScope
*
* @return Promise
* @return \Amp\Promise
*/
public function after($a, $b)
{
@ -4541,7 +4541,7 @@ class InternalDoc extends APIFactory
*
* @param array<\Generator|Promise> $promises Promises
*
* @return Promise
* @return \Amp\Promise
*/
public function all(array $promises)
{
@ -4552,7 +4552,7 @@ class InternalDoc extends APIFactory
*
* @param array<Promise|\Generator> $promises Promises
*
* @return Promise
* @return \Amp\Promise
*/
public function any(array $promises)
{
@ -4621,7 +4621,7 @@ class InternalDoc extends APIFactory
* @template TReturn
* @psalm-param \Generator<mixed, mixed, mixed, TReturn>|Promise<TReturn>|TReturn $promise
*
* @return Promise
* @return \Amp\Promise
* @psalm-return Promise<TReturn>
*/
public function call($promise)
@ -4637,7 +4637,7 @@ class InternalDoc extends APIFactory
*
* @psalm-suppress InvalidScope
*
* @return Promise|mixed
* @return \Amp\Promise|mixed
*/
public function callFork($promise, $actual = null, $file = '')
{
@ -4904,7 +4904,7 @@ class InternalDoc extends APIFactory
*
* @param string $string Message to echo
*
* @return Promise
* @return \Amp\Promise
*/
public function echo(string $string)
{
@ -4961,7 +4961,7 @@ class InternalDoc extends APIFactory
*
* @param array<Promise|\Generator> $promises Promises
*
* @return Promise
* @return \Amp\Promise
*/
public function first(array $promises)
{
@ -4977,7 +4977,7 @@ class InternalDoc extends APIFactory
* @param ?Promise $token Cancellation token
* @param ?callable $failureCb Failure callback, called only once if the first locking attempt fails.
*
* @return Promise<?callable>
* @return \Amp\Promise<?callable>
*/
public function flock(string $file, int $operation, float $polling = 0.1, ?\Amp\Promise $token = null, $failureCb = null)
{
@ -5284,8 +5284,8 @@ class InternalDoc extends APIFactory
* user_id?: int,
* chat_id?: int,
* channel_id?: int,
* InputUser?: {_: string, user_id?: int, access_hash?: mixed, min?: bool},
* InputChannel?: {_: string, channel_id: int, access_hash: mixed, min: bool},
* InputUser?: array{_: string, user_id?: int, access_hash?: mixed, min?: bool},
* InputChannel?: array{_: string, channel_id: int, access_hash: mixed, min: bool},
* type: string
* }>
*/
@ -5624,7 +5624,7 @@ class InternalDoc extends APIFactory
/**
* Start MadelineProto's update handling loop in background.
*
* @return Promise
* @return \Amp\Promise
*/
public function loopFork(array $extra = [])
{
@ -5835,7 +5835,7 @@ class InternalDoc extends APIFactory
*
* @param string $prompt Prompt
*
* @return Promise<string>
* @return \Amp\Promise<string>
*/
public function readLine(string $prompt = '')
{
@ -6076,7 +6076,7 @@ class InternalDoc extends APIFactory
*
* @param int|float $time Number of seconds to sleep for
*
* @return Promise
* @return \Amp\Promise
*/
public function sleep($time)
{
@ -6088,7 +6088,7 @@ class InternalDoc extends APIFactory
*
* @param array<Promise|\Generator> $promises Promises
*
* @return Promise
* @return \Amp\Promise
*/
public function some(array $promises)
{
@ -6153,7 +6153,7 @@ class InternalDoc extends APIFactory
* @param \Generator|Promise $promise
* @param integer $timeout
*
* @return Promise
* @return \Amp\Promise
*/
public function timeout($promise, int $timeout)
{
@ -6177,7 +6177,7 @@ class InternalDoc extends APIFactory
* @psalm-param Promise<TReturn>|TGenerator $promise Promise to which the timeout is applied.
* @psalm-param TReturnAlt $default
*
* @return Promise<TReturn>|Promise<TReturnAlt>
* @return \Amp\Promise<TReturn>|Promise<TReturnAlt>
*
* @throws \TypeError If $promise is not an instance of \Amp\Promise, \Generator or \React\Promise\PromiseInterface.
*/

View File

@ -33,9 +33,21 @@ use function Amp\ByteStream\getStdout;
*/
class Logger
{
/**
* @internal ANSI foreground color escapes
*/
const FOREGROUND = ['default' => 39, 'black' => 30, 'red' => 31, 'green' => 32, 'yellow' => 33, 'blue' => 34, 'magenta' => 35, 'cyan' => 36, 'light_gray' => 37, 'dark_gray' => 90, 'light_red' => 91, 'light_green' => 92, 'light_yellow' => 93, 'light_blue' => 94, 'light_magenta' => 95, 'light_cyan' => 96, 'white' => 97];
/**
* @internal ANSI background color escapes
*/
const BACKGROUND = ['default' => 49, 'black' => 40, 'red' => 41, 'magenta' => 45, 'yellow' => 43, 'green' => 42, 'blue' => 44, 'cyan' => 46, 'light_gray' => 47, 'dark_gray' => 100, 'light_red' => 101, 'light_green' => 102, 'light_yellow' => 103, 'light_blue' => 104, 'light_magenta' => 105, 'light_cyan' => 106, 'white' => 107];
/**
* @internal ANSI modifier escapes
*/
const SET = ['bold' => 1, 'dim' => 2, 'underlined' => 3, 'blink' => 4, 'reverse' => 5, 'hidden' => 6];
/**
* @internal ANSI reset modifier escapes
*/
const RESET = ['all' => 0, 'bold' => 21, 'dim' => 22, 'underlined' => 24, 'blink' => 25, 'reverse' => 26, 'hidden' => 28];
/**
* Logging mode.
@ -101,61 +113,115 @@ class Logger
private PsrLogger $psr;
/**
* Ultra verbose logging.
*
* @internal
*/
const ULTRA_VERBOSE = 5;
/**
* Verbose logging.
*
* @internal
*/
const VERBOSE = 4;
/**
* Notice logging.
*
* @internal
*/
const NOTICE = 3;
/**
* Warning logging.
*
* @internal
*/
const WARNING = 2;
/**
* Error logging.
*
* @internal
*/
const ERROR = 1;
/**
* Log only fatal errors.
*
* @internal
*/
const FATAL_ERROR = 0;
/**
* Disable logger (DEPRECATED).
*
* @internal
* @deprecated
*/
const NO_LOGGER = 0;
/**
* Default logger (syslog).
*
* @internal
*/
const DEFAULT_LOGGER = 1;
/**
* File logger.
*
* @internal
*/
const FILE_LOGGER = 2;
/**
* Echo logger.
*
* @internal
*/
const ECHO_LOGGER = 3;
/**
* Callable logger.
*
* @internal
*/
const CALLABLE_LOGGER = 4;
/**
* Ultra verbose level.
*/
const LEVEL_ULTRA_VERBOSE = self::ULTRA_VERBOSE;
/**
* Verbose level.
*/
const LEVEL_VERBOSE = self::VERBOSE;
/**
* Notice level.
*/
const LEVEL_NOTICE = self::NOTICE;
/**
* Warning level.
*/
const LEVEL_WARNING = self::WARNING;
/**
* Error level.
*/
const LEVEL_ERROR = self::ERROR;
/**
* Fatal error level.
*/
const LEVEL_FATAL = self::FATAL_ERROR;
/**
* Default logger (syslog).
*/
const LOGGER_DEFAULT = self::DEFAULT_LOGGER;
/**
* Echo logger.
*/
const LOGGER_ECHO = self::ECHO_LOGGER;
/**
* File logger.
*/
const LOGGER_FILE = self::FILE_LOGGER;
/**
* Callable logger.
*/
const LOGGER_CALLABLE = self::CALLABLE_LOGGER;
/**
* Construct global static logger from MadelineProto settings.
*

View File

@ -95,6 +95,8 @@ class MTProto extends AsyncConstruct implements TLCallback
*
* Increased every time the default settings array or something big changes
*
* @internal
*
* @var int
*/
const V = 147;

View File

@ -202,7 +202,8 @@ class MinDatabase implements TLCallback
*
* @param float|int $id Peer ID
*
* @return boolean<Promise>
* @return Promise
* @psalm-return Promise<bool>
*/
public function hasPeer($id): Promise
{

View File

@ -489,8 +489,8 @@ trait PeerHandler
* user_id?: int,
* chat_id?: int,
* channel_id?: int,
* InputUser?: {_: string, user_id?: int, access_hash?: mixed, min?: bool},
* InputChannel?: {_: string, channel_id: int, access_hash: mixed, min: bool},
* InputUser?: array{_: string, user_id?: int, access_hash?: mixed, min?: bool},
* InputChannel?: array{_: string, channel_id: int, access_hash: mixed, min: bool},
* type: string
* }>
*/

View File

@ -1,137 +0,0 @@
<?php
namespace danog\MadelineProto\PhpDoc;
use danog\MadelineProto\Logger;
use danog\MadelineProto\PhpDocBuilder;
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
use phpDocumentor\Reflection\DocBlock\Tags\Property;
use ReflectionClass;
use ReflectionClassConstant;
use ReflectionMethod;
class ClassDoc extends GenericDoc
{
/**
* Properties.
*
* @var array<string, array>
*/
private array $properties = [];
/**
* Methods.
*
* @var array<string, MethodDoc>
*/
private array $methods = [];
/**
* Constants.
*/
private array $constants = [];
public function __construct(PhpDocBuilder $builder, ReflectionClass $reflectionClass)
{
$this->builder = $builder;
$this->name = $reflectionClass->getName();
$doc = $reflectionClass->getDocComment();
if (!$doc) {
Logger::log($reflectionClass->getName()." has no PHPDOC");
$this->ignore = true;
return;
}
$doc = $this->builder->getFactory()->create($doc);
parent::__construct($doc, $reflectionClass);
$tags = $doc->getTags();
foreach ($tags as $tag) {
if ($tag instanceof Property) {
$this->properties[$tag->getVariableName()] = [
$tag->getType(),
$tag->getDescription()
];
}
if ($tag instanceof InvalidTag && $tag->getName() === 'property') {
[$type, $description] = \explode(" $", $tag->render(), 2);
$description .= ' ';
[$varName, $description] = \explode(" ", $description, 2);
$type = \str_replace('@property ', '', $type);
$description ??= '';
$this->properties[$varName] = [
$type,
$description
];
}
}
foreach ($reflectionClass->getConstants() as $key => $value) {
$refl = new ReflectionClassConstant($reflectionClass->getName(), $key);
if (!$refl->isPublic()) {
continue;
}
$description = '';
if ($refl->getDocComment()) {
$docConst = $this->builder->getFactory()->create($refl->getDocComment());
if ($this->builder->shouldIgnore($refl->getDeclaringClass()->getName())) {
continue;
}
$description .= $docConst->getSummary();
if ($docConst->getDescription()) {
$description .= "\n\n";
$description .= $docConst->getDescription();
}
if ($docConst->getTagsByName('internal')) {
continue;
}
}
$description = \trim($description);
$this->constants[$key] = [
$value,
$description
];
}
foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
if (str_starts_with($method->getName(), '__') && $method !== '__construct') {
continue;
}
$this->methods[$method->getName()] = new MethodDoc($this->builder, $method);
}
$this->methods = \array_filter($this->methods, fn (MethodDoc $doc): bool => !$doc->shouldIgnore());
}
public function format(): string
{
$init = parent::format();
if ($this->constants) {
$init .= "\n";
$init .= "## Constants\n";
foreach ($this->constants as $name => [, $description]) {
$description = \trim($description);
$description = \str_replace("\n", "\n ", $description);
$init .= "* {$this->className}::$name: $description\n";
$init .= "\n";
}
}
if ($this->methods) {
$init .= "\n";
$init .= "## Method list:\n";
foreach ($this->methods as $method) {
$init .= "* `".$method->getSignature()."`\n";
}
$init .= "\n";
$init .= "## Methods:\n";
foreach ($this->methods as $method) {
$init .= $method->format();
$init .= "\n";
}
}
if ($this->properties) {
$init .= "## Properties\n";
foreach ($this->properties as $name => [$type, $description]) {
$init .= "* `\$$name`: `$type` $description";
}
}
return $init;
}
}

View File

@ -1,25 +0,0 @@
<?php
namespace danog\MadelineProto\PhpDoc;
use danog\MadelineProto\Logger;
use danog\MadelineProto\PhpDocBuilder;
use ReflectionFunction;
class FunctionDoc extends MethodDoc
{
public function __construct(PhpDocBuilder $builder, ReflectionFunction $reflectionClass)
{
$this->builder = $builder;
$this->nameGenericDoc = $reflectionClass->getName();
$doc = $reflectionClass->getDocComment();
if (!$doc) {
Logger::log($reflectionClass->getName()." has no PHPDOC");
$this->ignore = true;
return;
}
$doc = $this->builder->getFactory()->create($doc);
parent::__construct($builder, $reflectionClass);
}
}

View File

@ -1,148 +0,0 @@
<?php
namespace danog\MadelineProto\PhpDoc;
use danog\MadelineProto\PhpDocBuilder;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tags\Author;
use phpDocumentor\Reflection\DocBlock\Tags\Deprecated;
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
use phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen;
use phpDocumentor\Reflection\DocBlock\Tags\Reference\Url;
use phpDocumentor\Reflection\DocBlock\Tags\See;
abstract class GenericDoc
{
/**
* Builder instance.
*/
protected PhpDocBuilder $builder;
/**
* Name.
*/
protected string $name;
/**
* Title.
*/
protected string $title;
/**
* Description.
*/
protected Description $description;
/**
* See also array.
*
* @var array<string, See>
*/
protected array $seeAlso = [];
/**
* Authors.
*
* @var Author[]
*/
protected array $authors;
/**
* Ignore this class.
*/
protected bool $ignore = false;
/**
* Class name.
*/
protected string $className;
/**
* Class namespace.
*/
protected string $namespace;
public function __construct(DocBlock $doc, $reflectionClass)
{
$this->className = $reflectionClass->getName();
$this->namespace = \str_replace('/', '\\', \dirname(\str_replace('\\', '/', $this->className)));
$this->title = $doc->getSummary();
$this->description = $doc->getDescription();
$tags = $doc->getTags();
$this->authors = $this->builder->getAuthors();
foreach ($tags as $tag) {
if ($tag instanceof Author) {
$this->authors []= $tag;
}
if ($tag instanceof Deprecated) {
$this->ignore = true;
break;
}
if ($tag instanceof Generic && $tag->getName() === 'internal') {
$this->ignore = true;
break;
}
if ($tag instanceof See) {
$this->seeAlso[$tag->getReference()->__toString()] = $tag;
}
}
}
public function seeAlso(): string
{
$seeAlso = '';
foreach ($this->seeAlso as $see) {
$ref = $see->getReference();
if ($ref instanceof Fqsen) {
$ref = (string) $ref;
$ref = $this->builder->resolveTypeAlias($this->className, $ref);
$refExpl = \explode("\\", $ref);
$name = \array_pop($refExpl);
$namespace = \explode('/', $this->namespace);
$count = \count($refExpl);
foreach ($namespace as $k => $name) {
if (isset($refExpl[$k]) && $refExpl[$k] === $name) {
$count--;
} else {
break;
}
}
$ref = \str_repeat('../', $count)
.\implode('/', \array_slice($refExpl, $count))
."/$name.md";
$desc = $see->getDescription() ?: $ref;
$seeAlso .= "* [$desc]($ref)\n";
}
if ($ref instanceof Url) {
$desc = $see->getDescription() ?: $ref;
$seeAlso .= "* [$desc]($ref)\n";
}
}
if ($seeAlso) {
$seeAlso = "\n#### See also: \n$seeAlso\n\n";
}
return $seeAlso;
}
public function format(): string
{
$authors = '';
foreach ($this->authors as $author) {
$authors .= "> Author: $author \n";
}
$seeAlso = $this->seeAlso();
return <<<EOF
---
title: $this->name: $this->title
description: $this->description
image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png
---
# `$this->name`: $this->title
[Back to API index](index.md)
$authors
$this->description
$seeAlso
EOF;
}
public function shouldIgnore(): bool
{
return $this->ignore;
}
}

View File

@ -1,119 +0,0 @@
<?php
namespace danog\MadelineProto\PhpDoc;
use danog\MadelineProto\Logger;
use danog\MadelineProto\PhpDocBuilder;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
use ReflectionFunctionAbstract;
use ReflectionMethod;
class MethodDoc extends GenericDoc
{
private Return_ $return;
private string $psalmReturn;
private array $params = [];
private array $psalmParams = [];
public function __construct(PhpDocBuilder $phpDocBuilder, ReflectionFunctionAbstract $method)
{
$this->builder = $phpDocBuilder;
$this->name = $method->getName();
$doc = $method->getDocComment();
if (!$doc) {
$this->ignore = true;
if ($method instanceof ReflectionMethod) {
Logger::log($method->getDeclaringClass()->getName().'::'.$method->getName().' has no PHPDOC!');
} else {
Logger::log($method->getName().' has no PHPDOC!');
}
return;
}
$doc = $this->builder->getFactory()->create($doc);
parent::__construct($doc, $method instanceof ReflectionMethod ? $method->getDeclaringClass() : $method);
foreach ($doc->getTags() as $tag) {
if ($tag instanceof Param && !isset($this->params[$tag->getVariableName()])) {
$this->params[$tag->getVariableName()] = [
$tag->getType(),
$tag->getDescription()
];
} elseif ($tag instanceof Return_ && !isset($this->return)) {
$this->return = $tag;
} elseif ($tag instanceof Generic && $tag->getName() === 'psalm-return') {
$this->psalmReturn = $tag;
} elseif ($tag instanceof Generic && $tag->getName() === 'psalm-param') {
[$type, $description] = \explode(" $", $tag->getDescription(), 2);
$description .= ' ';
[$varName, $description] = \explode(" ", $description, 2);
if (!$description && isset($this->params[$varName])) {
$description = (string) $this->params[$varName][1];
} else {
$description = new Description($description);
}
$this->psalmParams[$varName] = [
$type,
$description
];
}
}
}
public function getSignature(): string
{
$sig = $this->name;
$sig .= "(";
foreach ($this->params as $var => [$type, $description]) {
$sig .= $type.' ';
$sig .= "$".$var;
$sig .= ', ';
}
$sig = \trim($sig, ', ');
$sig .= ')';
if (isset($this->return)) {
$sig .= ': ';
$sig .= $this->builder->resolveTypeAlias($this->className, $this->return);
}
return $sig;
}
public function format(): string
{
$sig = '### `'.$this->getSignature()."`";
$sig .= "\n\n";
$sig .= $this->title;
$sig .= "\n";
$sig .= $this->description;
$sig .= "\n";
if ($this->psalmParams || $this->params) {
$sig .= "\nParameters:\n";
foreach ($this->params as $name => [$type, $description]) {
if ($type) {
$type = $this->builder->resolveTypeAlias($this->className, $type);
}
$sig .= "* `\$$name`: `$type` $description \n";
if (isset($this->psalmParams[$name])) {
[$psalmType] = $this->psalmParams[$name];
$psalmType = \trim(\str_replace("\n", "\n ", $psalmType));
$sig .= " Full type:\n";
$sig .= " ```\n";
$sig .= " $psalmType\n";
$sig .= " ```\n";
}
}
$sig .= "\n";
}
if (isset($this->return) && $this->return->getDescription() && $this->return->getDescription()->render()) {
$sig .= "\nReturn value: ".$this->return->getDescription()."\n";
}
if (isset($this->psalmReturn)) {
$sig .= "\nFully typed return value:\n```\n".$this->psalmReturn."\n```";
}
$sig .= $this->seeAlso();
$sig .= "\n";
return $sig;
}
}

View File

@ -1,287 +0,0 @@
<?php
/**
* PhpDocBuilder module.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;
use danog\ClassFinder\ClassFinder;
use danog\MadelineProto\PhpDoc\ClassDoc;
use danog\MadelineProto\PhpDoc\FunctionDoc;
use phpDocumentor\Reflection\DocBlock\Tags\Author;
use phpDocumentor\Reflection\DocBlockFactory;
use ReflectionClass;
use ReflectionFunction;
class PhpDocBuilder
{
/**
* Namespace.
*/
private string $namespace;
/**
* Scan mode.
*/
private int $mode;
/**
* Docblock factory.
*/
private DocBlockFactory $factory;
/**
* Authors.
*/
private array $authors = [];
/**
* Classes/interfaces/traits to ignore.
*
* @var ?callable
* @psalm-var null|callable(class-string)
*/
private $ignore;
/**
* Output directory.
*/
private string $output;
/**
* Use map.
*
* array<class-string, array<class-string, class-string>>
*/
private array $useMap = [];
/**
* Create docblock builder.
*
* @param string $namespace Namespace (defaults to package namespace)
* @param int $mode Finder mode, an OR-selection of ClassFinder::ALLOW_*
*
* @return self
*/
public static function fromNamespace(string $namespace = '', int $mode = ClassFinder::ALLOW_ALL): self
{
return new self($namespace, $mode);
}
/**
* Constructor.
*
* @param string $namespace
* @param int $mode
*/
private function __construct(string $namespace, int $mode)
{
$this->factory = DocBlockFactory::createInstance();
$this->namespace = $namespace;
$this->mode = $mode;
$appRoot = new \danog\ClassFinder\AppConfig;
$appRoot = $appRoot->getAppRoot();
$appRoot .= "/composer.json";
$json = \json_decode(\file_get_contents($appRoot), true);
$authors = $json['authors'] ?? [];
foreach ($authors as $author) {
$this->authors []= new Author($author['name'], $author['email']);
}
if (!$this->namespace) {
$namespaces = \array_keys($json['autoload']['psr-4']);
$this->namespace = $namespaces[0];
foreach ($namespaces as $namespace) {
if (\strlen($namespace) && \strlen($namespace) < \strlen($this->namespace)) {
$this->namespace = $namespace;
}
}
}
}
/**
* Set filter to ignore certain classes.
*
* @param callable $ignore
*
* @psalm-param callable(class-string) $ignore
*
* @return self
*/
public function setFilter(callable $ignore): self
{
$this->ignore = $ignore;
return $this;
}
/**
* Set output directory.
*
* @param string $output Output directory
*
* @return self
*/
public function setOutput(string $output): self
{
$this->output = $output;
return $this;
}
/**
* Run documentor.
*
* @return self
*/
public function run(): self
{
$classes = ClassFinder::getClassesInNamespace($this->namespace, $this->mode | ClassFinder::RECURSIVE_MODE);
foreach ($classes as $class) {
$this->addTypeAliases($class);
}
foreach ($classes as $class) {
if ($this->ignore && $this->shouldIgnore($class)) {
continue;
}
$class = \function_exists($class)
? new ReflectionFunction($class)
: new ReflectionClass($class);
$this->generate($class);
}
return $this;
}
/**
* Resolve type alias.
*
* @internal
*
* @param string $fromClass Class from where this function is called
* @param string $name Name to resolve
*
* @psalm-param class-string $fromClass Class from where this function is called
* @psalm-param class-string $name Name to resolve
*
* @return string
*/
public function resolveTypeAlias(string $fromClass, string $name): string
{
return $this->useMap[$fromClass][$name] ?? $name;
}
/**
* Add type alias.
*
* @param string $class
*
* @psalm-param class-string $class
*
* @return void
*/
private function addTypeAliases(string $class)
{
$reflectionClass = \function_exists($class)
? new ReflectionFunction($class)
: new ReflectionClass($class);
$payload = \file_get_contents($reflectionClass->getFileName());
\preg_match_all("/use *(function)? +(.*?)(?: +as +(.+))? *;/", $payload, $matches, PREG_SET_ORDER|PREG_UNMATCHED_AS_NULL);
foreach ($matches as [, $function, $import, $alias]) {
$import = "\\$import";
$alias ??= \basename(\str_replace('\\', '/', $import));
$this->useMap[$class][$alias] = $import;
$this->useMap[$class]['\\'.$alias] = $import;
}
}
/**
* Create directory recursively.
*
* @param string $file
* @return string
*/
private static function createDir(string $file): string
{
$dir = \dirname($file);
if (!\file_exists($dir)) {
self::createDir($dir);
\mkdir($dir);
}
return $file;
}
/**
* Generate documentation for class.
*
* @param ReflectionClass|ReflectionFunction $class Class
*
* @return void
*/
private function generate($class): void
{
$name = $class->getName();
$fName = $this->output;
$fName .= \str_replace('\\', DIRECTORY_SEPARATOR, $name);
$fName .= '.md';
$class = $class instanceof ReflectionFunction
? new FunctionDoc($this, $class)
: new ClassDoc($this, $class);
if ($class->shouldIgnore()) {
return;
}
$class = $class->format();
$handle = \fopen(self::createDir($fName), 'w+');
\fwrite($handle, $class);
\fclose($handle);
}
/**
* Get docblock factory.
*
* @internal
*
* @return DocBlockFactory
*/
public function getFactory(): DocBlockFactory
{
return $this->factory;
}
/**
* Whether should ignore trait/class/interface.
*
* @return bool
*/
public function shouldIgnore(string $class): bool
{
return !($this->ignore)($class);
}
/**
* Get authors.
*
* @return Author[]
*/
public function getAuthors(): array
{
return $this->authors;
}
/**
* Set authors.
*
* @param Author[] $authors Authors
*
* @return self
*/
public function setAuthors(array $authors): self
{
$this->authors = $authors;
return $this;
}
}

View File

@ -33,7 +33,8 @@ interface BufferedStreamInterface extends StreamInterface
*
* @param int $length Length of payload, as detected by this layer
*
* @return Promise<BufferInterface>
* @return Promise
* @psalm-return Promise<BufferInterface>
*/
public function getReadBuffer(&$length): Promise;
/**

View File

@ -920,7 +920,7 @@ class InternalDoc extends APIFactory
*
* @psalm-suppress InvalidScope
*
* @return Promise
* @return \Amp\Promise
*/
public function after($a, $b)
{
@ -932,7 +932,7 @@ class InternalDoc extends APIFactory
*
* @param array<\Generator|Promise> $promises Promises
*
* @return Promise
* @return \Amp\Promise
*/
public function all(array $promises)
{
@ -943,7 +943,7 @@ class InternalDoc extends APIFactory
*
* @param array<Promise|\Generator> $promises Promises
*
* @return Promise
* @return \Amp\Promise
*/
public function any(array $promises)
{
@ -1001,7 +1001,7 @@ class InternalDoc extends APIFactory
* @template TReturn
* @psalm-param \Generator<mixed, mixed, mixed, TReturn>|Promise<TReturn>|TReturn $promise
*
* @return Promise
* @return \Amp\Promise
* @psalm-return Promise<TReturn>
*/
public function call($promise)
@ -1017,7 +1017,7 @@ class InternalDoc extends APIFactory
*
* @psalm-suppress InvalidScope
*
* @return Promise|mixed
* @return \Amp\Promise|mixed
*/
public function callFork($promise, $actual = null, $file = '')
{
@ -1050,7 +1050,7 @@ class InternalDoc extends APIFactory
*
* @param string $string Message to echo
*
* @return Promise
* @return \Amp\Promise
*/
public function echo(string $string)
{
@ -1072,7 +1072,7 @@ class InternalDoc extends APIFactory
*
* @param array<Promise|\Generator> $promises Promises
*
* @return Promise
* @return \Amp\Promise
*/
public function first(array $promises)
{
@ -1088,7 +1088,7 @@ class InternalDoc extends APIFactory
* @param ?Promise $token Cancellation token
* @param ?callable $failureCb Failure callback, called only once if the first locking attempt fails.
*
* @return Promise<?callable>
* @return \Amp\Promise<?callable>
*/
public function flock(string $file, int $operation, float $polling = 0.1, ?\Amp\Promise $token = null, $failureCb = null)
{
@ -1378,7 +1378,7 @@ class InternalDoc extends APIFactory
*
* @param string $prompt Prompt
*
* @return Promise<string>
* @return \Amp\Promise<string>
*/
public function readLine(string $prompt = '')
{
@ -1442,7 +1442,7 @@ class InternalDoc extends APIFactory
*
* @param int|float $time Number of seconds to sleep for
*
* @return Promise
* @return \Amp\Promise
*/
public function sleep($time)
{
@ -1454,7 +1454,7 @@ class InternalDoc extends APIFactory
*
* @param array<Promise|\Generator> $promises Promises
*
* @return Promise
* @return \Amp\Promise
*/
public function some(array $promises)
{
@ -1466,7 +1466,7 @@ class InternalDoc extends APIFactory
* @param \Generator|Promise $promise
* @param integer $timeout
*
* @return Promise
* @return \Amp\Promise
*/
public function timeout($promise, int $timeout)
{
@ -1490,7 +1490,7 @@ class InternalDoc extends APIFactory
* @psalm-param Promise<TReturn>|TGenerator $promise Promise to which the timeout is applied.
* @psalm-param TReturnAlt $default
*
* @return Promise<TReturn>|Promise<TReturnAlt>
* @return \Amp\Promise<TReturn>|Promise<TReturnAlt>
*
* @throws \TypeError If $promise is not an instance of \Amp\Promise, \Generator or \React\Promise\PromiseInterface.
*/

View File

@ -23,7 +23,6 @@ use danog\MadelineProto\MTProtoTools\PasswordCalculator;
use danog\MadelineProto\MTProtoTools\ReferenceDatabase;
use danog\MadelineProto\MTProtoTools\UpdatesState;
use danog\MadelineProto\NothingInTheSocketException;
use danog\MadelineProto\PhpDocBuilder;
use danog\MadelineProto\RSA;
use danog\MadelineProto\Serialization;
use danog\MadelineProto\SessionPaths;
@ -39,6 +38,7 @@ use danog\MadelineProto\TON\APIFactory as TONAPIFactory;
use danog\MadelineProto\TON\InternalDoc as TONInternalDoc;
use danog\MadelineProto\TON\Lite;
use danog\MadelineProto\VoIP;
use danog\PhpDoc\PhpDocBuilder;
require 'vendor/autoload.php';
@ -100,6 +100,7 @@ $filter = function (string $class) use ($ignore): bool {
|| str_starts_with($class, 'danog\\MadelineProto\\MTProto\\')
|| str_starts_with($class, 'danog\\MadelineProto\\MTProtoSession\\')
|| str_starts_with($class, 'danog\\MadelineProto\\PhpDoc\\')
|| str_starts_with($class, 'danog\\MadelineProto\\Stream\\')
|| str_starts_with($class, 'danog\\MadelineProto\\Db\\NullCache')) {
return false;
}