1
0
This commit is contained in:
2022-09-21 14:01:45 +02:00
parent 0a30f39eb4
commit cfee4d93d0
349 changed files with 4 additions and 4 deletions

View File

@ -0,0 +1,24 @@
<?php
/*
Condorcet PHP - Election manager and results calculator.
Designed for the Condorcet method. Integrating a large number of algorithms extending Condorcet. Expandable for all types of voting systems.
By Julien Boudry and contributors - MIT LICENSE (Please read LICENSE.txt)
https://github.com/julien-boudry/Condorcet
*/
declare(strict_types=1);
namespace CondorcetPHP\Condorcet\Dev\CondorcetDocumentationGenerator\CondorcetDocAttributes;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD)]
class Description
{
public readonly string $text;
public function __construct(string $text)
{
$this->text = $text;
}
}

View File

@ -0,0 +1,26 @@
<?php
/*
Condorcet PHP - Election manager and results calculator.
Designed for the Condorcet method. Integrating a large number of algorithms extending Condorcet. Expandable for all types of voting systems.
By Julien Boudry and contributors - MIT LICENSE (Please read LICENSE.txt)
https://github.com/julien-boudry/Condorcet
*/
declare(strict_types=1);
namespace CondorcetPHP\Condorcet\Dev\CondorcetDocumentationGenerator\CondorcetDocAttributes;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Example
{
public readonly string $name;
public readonly string $link;
public function __construct(string $name, string $link)
{
$this->name = $name;
$this->link = $link;
}
}

View File

@ -0,0 +1,24 @@
<?php
/*
Condorcet PHP - Election manager and results calculator.
Designed for the Condorcet method. Integrating a large number of algorithms extending Condorcet. Expandable for all types of voting systems.
By Julien Boudry and contributors - MIT LICENSE (Please read LICENSE.txt)
https://github.com/julien-boudry/Condorcet
*/
declare(strict_types=1);
namespace CondorcetPHP\Condorcet\Dev\CondorcetDocumentationGenerator\CondorcetDocAttributes;
use Attribute;
#[Attribute(Attribute::TARGET_PARAMETER)]
class FunctionParameter
{
public readonly string $text;
public function __construct(string $text)
{
$this->text = (mb_substr($text, -1) === '.') ? $text : $text.'.';
}
}

View File

@ -0,0 +1,24 @@
<?php
/*
Condorcet PHP - Election manager and results calculator.
Designed for the Condorcet method. Integrating a large number of algorithms extending Condorcet. Expandable for all types of voting systems.
By Julien Boudry and contributors - MIT LICENSE (Please read LICENSE.txt)
https://github.com/julien-boudry/Condorcet
*/
declare(strict_types=1);
namespace CondorcetPHP\Condorcet\Dev\CondorcetDocumentationGenerator\CondorcetDocAttributes;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD)]
class FunctionReturn
{
public readonly string $text;
public function __construct(string $text)
{
$this->text = $text;
}
}

View File

@ -0,0 +1,18 @@
<?php
/*
Condorcet PHP - Election manager and results calculator.
Designed for the Condorcet method. Integrating a large number of algorithms extending Condorcet. Expandable for all types of voting systems.
By Julien Boudry and contributors - MIT LICENSE (Please read LICENSE.txt)
https://github.com/julien-boudry/Condorcet
*/
declare(strict_types=1);
namespace CondorcetPHP\Condorcet\Dev\CondorcetDocumentationGenerator\CondorcetDocAttributes;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS_CONSTANT | Attribute::TARGET_CLASS)]
class InternalModulesAPI
{
}

View File

@ -0,0 +1,21 @@
<?php
/*
Condorcet PHP - Election manager and results calculator.
Designed for the Condorcet method. Integrating a large number of algorithms extending Condorcet. Expandable for all types of voting systems.
By Julien Boudry and contributors - MIT LICENSE (Please read LICENSE.txt)
https://github.com/julien-boudry/Condorcet
*/
declare(strict_types=1);
namespace CondorcetPHP\Condorcet\Dev\CondorcetDocumentationGenerator\CondorcetDocAttributes;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS_CONSTANT | Attribute::TARGET_CLASS)]
class PublicAPI extends InternalModulesAPI
{
public function __construct(string ...$class)
{
}
}

View File

@ -0,0 +1,24 @@
<?php
/*
Condorcet PHP - Election manager and results calculator.
Designed for the Condorcet method. Integrating a large number of algorithms extending Condorcet. Expandable for all types of voting systems.
By Julien Boudry and contributors - MIT LICENSE (Please read LICENSE.txt)
https://github.com/julien-boudry/Condorcet
*/
declare(strict_types=1);
namespace CondorcetPHP\Condorcet\Dev\CondorcetDocumentationGenerator\CondorcetDocAttributes;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD)]
class Related
{
public readonly array $relatedList;
public function __construct(string ...$relatedList)
{
$this->relatedList = $relatedList;
}
}

View File

@ -0,0 +1,24 @@
<?php
/*
Condorcet PHP - Election manager and results calculator.
Designed for the Condorcet method. Integrating a large number of algorithms extending Condorcet. Expandable for all types of voting systems.
By Julien Boudry and contributors - MIT LICENSE (Please read LICENSE.txt)
https://github.com/julien-boudry/Condorcet
*/
declare(strict_types=1);
namespace CondorcetPHP\Condorcet\Dev\CondorcetDocumentationGenerator\CondorcetDocAttributes;
use Attribute;
#[Attribute(Attribute::TARGET_METHOD)]
class Throws
{
public array $exceptionList;
public function __construct(string ...$exceptionList)
{
$this->exceptionList = $exceptionList;
}
}

View File

@ -0,0 +1,580 @@
<?php
declare(strict_types=1);
namespace CondorcetPHP\Condorcet\Dev\CondorcetDocumentationGenerator;
use CondorcetPHP\Condorcet\Dev\CondorcetDocumentationGenerator\CondorcetDocAttributes\{Description, Example, FunctionParameter, FunctionReturn, PublicAPI, Related, Throws};
use HaydenPierce\ClassFinder\ClassFinder;
class Generate
{
// Static - Translators
public static function makeFilename(\ReflectionMethod $method): string
{
return self::getModifiersName($method).
' '.
str_replace('\\', '_', self::simpleClass($method->class)).'--'.$method->name.
'.md';
}
public static function simpleClass(string $fullClassName): string
{
return str_replace('CondorcetPHP\\Condorcet\\', '', $fullClassName);
}
public static function speakBool($c): string
{
if ($c === true || $c === 'true') {
return 'true';
}
if ($c === false || $c === 'false') {
return 'false';
}
if ($c === null || $c === 'null') {
return 'null';
}
if (\is_array($c)) {
return '['.implode(',', $c).']';
}
if (\is_object($c)) {
return 'new '.$c::class;
}
return (string) $c;
}
public static function getTypeAsString(?\ReflectionType $rf_rt, bool $codeBlock = false): ?string
{
if ($rf_rt !== null) {
if ($codeBlock) {
return '```'.((string) $rf_rt).'```';
} else {
return (string) $rf_rt;
}
}
return $rf_rt;
}
public static function getModifiersName(\ReflectionMethod $method): string
{
return implode(' ', \Reflection::getModifierNames($method->getModifiers()));
}
// Static - Builder
public static function cleverRelated(string $name): string
{
$infos = explode('::', $name);
$infos[0] = str_replace('static ', '', $infos[0]);
$url = '../'.$infos[0].' Class/public '.str_replace('::', '--', $name) . '.md';
$url = str_replace(' ', '%20', $url);
return '['.$name.']('.$url.')';
}
public static function computeRepresentationAsForIndex(\ReflectionMethod $method): string
{
return self::getModifiersName($method).
' '.
self::simpleClass($method->class).
(($method->isStatic()) ? '::' : '->').
$method->name.
' ('.(($method->getNumberOfParameters() > 0) ? '...' : '').')';
}
public static function computeRepresentationAsPHP(\ReflectionMethod $method): string
{
$option = false;
$str = '(';
$i = 0;
if ($method->getNumberOfParameters() > 0) {
foreach ($method->getParameters() as $value) {
$str .= ' ';
$str .= ($value->isOptional() && !$option) ? '[' : '';
$str .= ($i > 0) ? ', ' : '';
$str .= self::getTypeAsString($value->getType());
$str .= ' ';
$str .= $value->isPassedByReference() ? '&' : '';
$str .= '$'.$value->getName();
$str .= $value->isDefaultValueAvailable() ? ' = '.self::speakBool($value->getDefaultValue()) : '';
($value->isOptional() && !$option) ? $option = true : null;
$i++;
}
}
if ($option) {
$str .= ']';
}
$str .= ' )';
return "```php\n".
self::getModifiersName($method).' '.self::simpleClass($method->class).(($method->isStatic()) ? '::' : '->').$method->name.' '.$str. ((self::getTypeAsString($method->getReturnType()) !== null) ? ': '.self::getTypeAsString($method->getReturnType()) : '').
"\n```";
}
// Script
public function __construct(string $path)
{
$start_time = microtime(true);
$pathDirectory = $path.\DIRECTORY_SEPARATOR;
//
$FullClassList = ClassFinder::getClassesInNamespace('CondorcetPHP\Condorcet\\', ClassFinder::RECURSIVE_MODE);
$FullClassList = array_filter($FullClassList, static function (string $value) {
return (mb_strpos($value, 'Condorcet\Test') === false) && (mb_strpos($value, 'Condorcet\Dev') === false);
});
$inDoc = 0;
$non_inDoc = 0;
$total_methods = 0;
$total_nonInternal_methods = 0;
// Warnings
foreach ($FullClassList as $FullClass) {
$methods = (new \ReflectionClass($FullClass))->getMethods(\ReflectionMethod::IS_PUBLIC);
foreach ($methods as $oneMethod) {
if ($oneMethod->isInternal()) {
} elseif (!empty($oneMethod->getAttributes(PublicAPI::class))) {
$inDoc++;
if ($oneMethod->getNumberOfParameters() > 0) {
foreach ($oneMethod->getParameters() as $oneParameter) {
if (empty($oneParameter->getAttributes(FunctionParameter::class))) {
var_dump('Method Has Public API attribute but parameter $'.$oneParameter->getName().' is undocumented '.$oneMethod->getDeclaringClass()->getName().'->'.$oneMethod->getName());
}
}
}
if (empty($oneMethod->getAttributes(Description::class)) && $oneMethod->getDeclaringClass()->getNamespaceName() !== '') {
var_dump('Description Attribute is empty: '.$oneMethod->getDeclaringClass()->getName().'->'.$oneMethod->getName());
}
} else {
$non_inDoc++;
if (empty($oneMethod->getAttributes(PublicAPI::class)) && $oneMethod->getDeclaringClass()->getNamespaceName() !== '') {
// var_dump('Method not has API attribute: '.$oneMethod->getDeclaringClass()->getName().'->'.$oneMethod->getName());
}
}
}
}
$full_methods_list = [];
// generate .md
foreach ($FullClassList as $FullClass) {
$reflectionClass = new \ReflectionClass($FullClass);
$methods = ($reflectionClass)->getMethods();
$shortClass = str_replace('CondorcetPHP\Condorcet\\', '', $FullClass);
$full_methods_list[$shortClass] = [
'FullClass' => $FullClass,
'shortClass' => $shortClass,
'ReflectionClass' => $reflectionClass,
'methods' => [],
];
foreach ($methods as $oneMethod) {
$method_array = $full_methods_list[$shortClass]['methods'][$oneMethod->name] = [
'FullClass' => $FullClass,
'shortClass' => $shortClass,
'name' => $oneMethod->name,
'static' => $oneMethod->isStatic(),
'visibility_public' => $oneMethod->isPublic(),
'visibility_protected' => $oneMethod->isProtected(),
'visibility_private' => $oneMethod->isPrivate(),
'ReflectionMethod' => $oneMethod,
'ReflectionClass' => $oneMethod->getDeclaringClass(),
];
$total_methods++;
if (!$oneMethod->isInternal()) {
$total_nonInternal_methods++;
}
// Write Markdown
if (!empty($apiAttribute = $oneMethod->getAttributes(PublicAPI::class)) && (empty($apiAttribute[0]->getArguments()) || \in_array(self::simpleClass($oneMethod->class), $apiAttribute[0]->getArguments(), true))) {
$path = $pathDirectory . str_replace('\\', '_', self::simpleClass($oneMethod->class)) . ' Class/';
if (!is_dir($path)) {
mkdir($path);
}
file_put_contents($path.self::makeFilename($oneMethod), $this->createMarkdownContent($oneMethod, $method_array));
}
}
}
print 'Public methods in doc: '.$inDoc.' / '.($inDoc + $non_inDoc).' | Total non-internal methods count: '.$total_nonInternal_methods.' | Number of Class: '.\count($FullClassList).' | Number of Methods including internals: '.$total_methods."\n";
// Add Index
$file_content = "> **[Presentation](../README.md) | [Manual](https://github.com/julien-boudry/Condorcet/wiki) | Methods References | [Tests](../Tests)**\n\n".
"# Public API Index*_\n".
"_*: I try to update and complete the documentation. See also [the manual](https://github.com/julien-boudry/Condorcet/wiki), [the tests](../Tests) also produce many examples. And create issues for questions or fixing documentation!_\n\n";
$file_content .= $this->makeIndex($full_methods_list);
$file_content .= "\n\n\n";
uksort($full_methods_list, 'strnatcmp');
$file_content .= "# Full Class & Methods References\n".
"_Including above methods from public API_\n\n";
$file_content .= $this->makeProfundis($full_methods_list);
// Write file
file_put_contents($pathDirectory.'README.md', $file_content);
echo 'YAH ! <br>' . (microtime(true) - $start_time) .'s';
}
// Build Methods
protected function createMarkdownContent(\ReflectionMethod $method, array $entry): string
{
// Header
$md = '## '.self::getModifiersName($method).' '. self::simpleClass($method->class).'::'.$method->name."\n\n".
"### Description \n\n".
self::computeRepresentationAsPHP($method)."\n\n".
$method->getAttributes(Description::class)[0]->getArguments()[0]."\n ";
// Input
if ($method->getNumberOfParameters() > 0) {
foreach ($method->getParameters() as $key => $value) {
if (!empty($attributes = $value->getAttributes(FunctionParameter::class))) {
$pt = $attributes[0]->newInstance()->text;
} elseif (isset($entry['input'][$value->getName()]['text'])) {
$pt = $entry['input'][$value->getName()]['text'];
} else {
$pt = '';
}
$md .= "\n\n".
'##### **'.$value->getName().':** *'.self::getTypeAsString($value->getType(), true)."* \n".
$pt." \n";
}
}
// Return Value
if (!empty($method->getAttributes(FunctionReturn::class))) {
$md .= "\n\n".
"### Return value: \n\n".
'*('.self::getTypeAsString($method->getReturnType(), true).')* '.$method->getAttributes(FunctionReturn::class)[0]->getArguments()[0]."\n\n";
}
// Throw
if (!empty($method->getAttributes(Throws::class))) {
$md .= "\n\n".
"### Throws: \n\n";
foreach ($method->getAttributes(Throws::class)[0]->getArguments() as $arg) {
$md .= '* ```'.$arg."```\n";
}
}
// Related methods
if (!empty($method->getAttributes(Related::class))) {
$md .= "\n".
"---------------------------------------\n\n".
"### Related method(s) \n\n";
foreach ($method->getAttributes(Related::class) as $RelatedAttribute) {
foreach ($RelatedAttribute->newInstance()->relatedList as $value) {
if ($value === self::simpleClass($method->class).'::'.$method->name) {
continue;
}
$md .= '* '.self::cleverRelated($value)." \n";
}
}
}
if (!empty($method->getAttributes(Example::class))) {
$md .= "\n".
"---------------------------------------\n\n".
"### Examples and explanation\n\n";
foreach ($method->getAttributes(Example::class) as $ExampleAttribute) {
$ExampleAttribute = $ExampleAttribute->newInstance();
$md .= '* **['.$ExampleAttribute->name.']('.$ExampleAttribute->link.")** \n";
}
}
return $md;
}
protected function makeIndex(array $index): string
{
$file_content = '';
$testPublicAttribute = static function (\ReflectionMethod $reflectionMethod): bool {
return !(empty($apiAttribute = $reflectionMethod->getAttributes(PublicAPI::class)) || (!empty($apiAttribute[0]->getArguments()) && !\in_array(self::simpleClass($reflectionMethod->class), $apiAttribute[0]->getArguments(), true)));
};
foreach ($index as $class => &$classMeta) {
usort($classMeta['methods'], static function (array $a, array $b): int {
if ($a['ReflectionMethod']->isStatic() === $b['ReflectionMethod']->isStatic()) {
return strnatcmp($a['ReflectionMethod']->name, $b['ReflectionMethod']->name);
} elseif ($a['ReflectionMethod']->isStatic() && !$b['ReflectionMethod']->isStatic()) {
return -1;
} else {
return 1;
}
});
$classWillBePublic = false;
if ($classMeta['ReflectionClass']->getAttributes(PublicAPI::class)) {
$classWillBePublic = true;
} else {
foreach ($classMeta['methods'] as $oneMethod) {
if ($testPublicAttribute($oneMethod['ReflectionMethod'])) {
$classWillBePublic = true;
break;
}
}
foreach ($classMeta['ReflectionClass']->getReflectionConstants() as $oneConstant) {
if (!empty($oneConstant->getAttributes(PublicAPI::class))) {
$classWillBePublic = true;
break;
}
}
foreach ($classMeta['ReflectionClass']->getProperties() as $onePropertie) {
if (!empty($onePropertie->getAttributes(PublicAPI::class))) {
$classWillBePublic = true;
break;
}
}
}
if ($classWillBePublic) {
$isEnum = enum_exists(($enumCases = $classMeta['ReflectionClass'])->name);
$file_content .= "\n";
$file_content .= '### CondorcetPHP\Condorcet\\'.$class.' '.((!$isEnum) ? 'Class' : 'Enum')." \n\n";
if ($isEnum) {
$file_content .= $this->makeEnumeCases(new \ReflectionEnum($enumCases->name), false);
$file_content .= "\n";
} else {
$file_content .= $this->makeConstants($classMeta['ReflectionClass'], \ReflectionClassConstant::IS_PUBLIC, true);
}
$file_content .= $this->makeProperties($classMeta['ReflectionClass'], null, true);
}
foreach ($classMeta['methods'] as $oneMethod) {
if (!$testPublicAttribute($oneMethod['ReflectionMethod']) || !$oneMethod['ReflectionMethod']->isUserDefined()) {
continue;
} else {
$url = str_replace('\\', '_', self::simpleClass($oneMethod['ReflectionMethod']->class)).' Class/'.self::getModifiersName($oneMethod['ReflectionMethod']).' '. str_replace('\\', '_', self::simpleClass($oneMethod['ReflectionMethod']->class).'--'. $oneMethod['ReflectionMethod']->name) . '.md';
$url = str_replace(' ', '%20', $url);
$file_content .= '* ['.self::computeRepresentationAsForIndex($oneMethod['ReflectionMethod']).']('.$url.')';
if (isset($oneMethod['ReflectionMethod']) && $oneMethod['ReflectionMethod']->hasReturnType()) {
$file_content .= ': '.self::getTypeAsString($oneMethod['ReflectionMethod']->getReturnType(), true);
}
$file_content .= " \n";
}
}
}
return $file_content;
}
protected function makeEnumeCases(\ReflectionEnum $enumReflection, bool $shortName = false): string
{
$cases = $enumReflection->getCases();
$r = '';
foreach ($cases as $oneCase) {
$name = ($shortName) ? $enumReflection->getShortName() : self::simpleClass($enumReflection->getName());
$r .= '* ```case '.$name.'::'.$oneCase->getName()."``` \n";
}
return $r;
}
protected function makeConstants(\ReflectionClass $class, ?int $type = null, bool $mustHaveApiAttribute = false, bool $addMdCodeTag = true): string
{
$file_content = '';
$hasConstants = false;
foreach ($class->getReflectionConstants($type) as $constant) {
if (!$mustHaveApiAttribute || !empty($constant->getAttributes(PublicAPI::class))) {
$file_content .= '* ';
$file_content .= $addMdCodeTag ? '```' : '';
$file_content .= $constant->isFinal() ? 'final ' : '';
$file_content .= $constant->isPublic() ? 'public' : '';
$file_content .= $constant->isProtected() ? 'protected' : '';
$file_content .= $constant->isPrivate() ? 'private' : '';
$file_content .= ' const '.$constant->getName().': ('.\gettype($constant->getValue()).')';
$file_content .= $addMdCodeTag ? '``` ' : '';
$file_content .= "\n";
$hasConstants = true;
}
}
if ($hasConstants) {
$file_content .= "\n";
}
return $file_content;
}
protected function makeProperties(\ReflectionClass $class, ?int $type = null, bool $mustHaveApiAttribute = false, bool $addMdCodeTag = true): string
{
$file_content = '';
$hasConstants = false;
foreach ($class->getProperties($type) as $propertie) {
if (!$mustHaveApiAttribute || !empty($propertie->getAttributes(PublicAPI::class))) {
$file_content .= '* ';
$file_content .= $addMdCodeTag ? '```' : '';
$file_content .= $propertie->isReadOnly() ? 'readonly ' : '';
$file_content .= $propertie->isPublic() ? 'public ' : '';
$file_content .= $propertie->isProtected() ? 'protected ' : '';
$file_content .= $propertie->isPrivate() ? 'private ' : '';
$file_content .= $propertie->isStatic() ? 'static ' : '';
$file_content .= ((string) $propertie->getType()).' $'.$propertie->getName();
$file_content .= $addMdCodeTag ? '``` ' : '';
$file_content .= "\n";
$hasConstants = true;
}
}
if ($hasConstants) {
$file_content .= "\n";
}
return $file_content;
}
protected function makeProfundis(array $index): string
{
$file_content = '';
foreach ($index as $class => &$classMeta) {
usort($classMeta['methods'], static function (array $a, array $b): int {
if ($a['static'] === $b['static']) {
if ($a['visibility_public'] && !$b['visibility_public']) {
return -1;
} elseif (!$a['visibility_public'] && $b['visibility_public']) {
return 1;
} else {
if ($a['visibility_protected'] && !$b['visibility_protected']) {
return -1;
} elseif (!$a['visibility_protected'] && $b['visibility_protected']) {
return 1;
} else {
return strnatcmp($a['name'], $b['name']);
}
}
} elseif ($a['static'] && !$b['static']) {
return -1;
} else {
return 1;
}
});
$file_content .= "\n";
$file_content .= '#### ';
$file_content .= ($classMeta['ReflectionClass']->isAbstract()) ? 'Abstract ' : '';
$file_content .= 'CondorcetPHP\Condorcet\\'.$class.' ';
$file_content .= ($p = $classMeta['ReflectionClass']->getParentClass()) ? 'extends '.$p->name.' ' : '';
$interfaces = implode(', ', $classMeta['ReflectionClass']->getInterfaceNames());
$file_content .= (!empty($interfaces)) ? 'implements '.$interfaces : '';
$file_content .= " \n";
$file_content .= "```php\n";
$isEnum = enum_exists(($enumCases = $classMeta['ReflectionClass'])->name);
if ($isEnum) {
$file_content .= $this->makeEnumeCases(new \ReflectionEnum($enumCases->name), true);
$file_content .= "\n";
} else {
$file_content .= $this->makeConstants(class: $classMeta['ReflectionClass'], addMdCodeTag: false);
}
$file_content .= $this->makeProperties(class: $classMeta['ReflectionClass'], addMdCodeTag: false);
foreach ($classMeta['methods'] as $oneMethod) {
if ($oneMethod['ReflectionMethod']->isUserDefined()) {
$parameters = $oneMethod['ReflectionMethod']->getParameters();
$parameters_string = '';
$i = 0;
foreach ($parameters as $oneP) {
$parameters_string .= (++$i > 1) ? ', ' : '';
if ($oneP->getType() !== null) {
$parameters_string .= self::getTypeAsString($oneP->getType()) . ' ';
}
$parameters_string .= '$'.$oneP->name;
if ($oneP->isDefaultValueAvailable()) {
$parameters_string .= ' = '.self::speakBool($oneP->getDefaultValue());
}
}
$representation = ($oneMethod['visibility_public']) ? 'public ' : '';
$representation .= ($oneMethod['visibility_protected']) ? 'protected ' : '';
$representation .= ($oneMethod['visibility_private']) ? 'private ' : '';
$representation .= ($oneMethod['static']) ? 'static ' : '';
$representation .= $oneMethod['name'] . ' ('.$parameters_string.')';
if ($oneMethod['ReflectionMethod']->hasReturnType()) {
$representation .= ': '.self::getTypeAsString($oneMethod['ReflectionMethod']->getReturnType());
}
$file_content .= '* '.$representation." \n";
}
}
$file_content .= "```\n";
}
return $file_content;
}
}

View File

@ -0,0 +1,14 @@
FROM condorcet
RUN pecl install pcov \
&& docker-php-ext-enable pcov
RUN php composer.phar install --optimize-autoloader --no-progress \
&& rm -rf /root/.composer/cache
ENTRYPOINT [ "vendor/bin/infection" ]
# Usage:
# 1. docker build -f Dockerfile.infection -t infection .
# 2. docker run --hostname="infection" --mount type=bind,src=$(pwd),dst=/usr/src/condorcetapp --rm -it infection:latest
# The infection test takes a long time. You can use --filter to only test a portion of the code.

View File

@ -0,0 +1,6 @@
Get some codes stats:
```
vendor/bin/phploc src Benchmarks Dev Tests Examples # With Test and Examples
vendor/bin/phploc src Benchmarks Dev # Without Tests and Examples
```

View File

@ -0,0 +1,8 @@
To generate Infection report:
_Customize with the right number of threads for your system._
```
php vendor/bin/infection --threads=24
```
_Logs at the racine, infection.log_

View File

@ -0,0 +1,83 @@
2021-01-03 - > 7590291d
2479 mutations were generated:
1872 mutants were killed
14 mutants were not covered by tests
427 covered mutants were not detected
110 errors were encountered
0 syntax errors were encountered
56 time outs were encountered
0 mutants required more time than configured
Metrics:
Mutation Score Indicator (MSI): 82%
Mutation Code Coverage: 99%
Covered Code MSI: 82%
2021-09-28 - 9882d9c
2538 mutations were generated:
1879 mutants were killed
14 mutants were not covered by tests
479 covered mutants were not detected
110 errors were encountered
0 syntax errors were encountered
56 time outs were encountered
0 mutants required more time than configured
Metrics:
Mutation Score Indicator (MSI): 80%
Mutation Code Coverage: 99%
Covered Code MSI: 81%
2021-09-20 - 24d4ba4
2586 mutations were generated:
1892 mutants were killed
42 mutants were not covered by tests
479 covered mutants were not detected
117 errors were encountered
0 syntax errors were encountered
56 time outs were encountered
0 mutants required more time than configured
Metrics:
Mutation Score Indicator (MSI): 79%
Mutation Code Coverage: 98%
Covered Code MSI: 81%
2021-08-05 - 40bfb78
2685 mutations were generated:
1962 mutants were killed
53 mutants were not covered by tests
492 covered mutants were not detected
122 errors were encountered
0 syntax errors were encountered
56 time outs were encountered
0 mutants required more time than configured
Metrics:
Mutation Score Indicator (MSI): 79%
Mutation Code Coverage: 98%
Covered Code MSI: 81%
2022-07-01 - 39b9c31
3207 mutations were generated:
1229 mutants were killed
0 mutants were configured to be ignored
42 mutants were not covered by tests
191 covered mutants were not detected
1 errors were encountered
0 syntax errors were encountered
11 time outs were encountered
1733 mutants required more time than configured
Metrics:
Mutation Score Indicator (MSI): 84%
Mutation Code Coverage: 97%
Covered Code MSI: 86%

View File

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
// Linux: php -dzend_extension=opcache -dopcache.enable_cli=1 -dopcache.jit_buffer_size=100M -dopcache.jit=tracing Dev/bugs/JitBug.php
// Windows: php -d zend_extension=opcache -d opcache.enable_cli=1 -d opcache.jit_buffer_size=100M -d opcache.jit=tracing Dev/bugs/JitBug.php
# Notes
// Not happening on 8.1
// Bug only with jit tracing mode (working with jit function) (test on PHP 8.0.10 NTS on x64)
// Working with or without opcache
# Bugs
// PHP Fatal error: Uncaught TypeError: CondorcetPHP\Condorcet\Result::__construct(): Argument #6 ($seats) must be of type ?int, CondorcetPHP\Condorcet\Election given, called in C:\dev_scripts\Condorcet\lib\Algo\Method.php on line 84 and defined in C:\dev_scripts\Condorcet\lib\Result.php:89
// Stack trace:
// #0 C:\dev_scripts\Condorcet\lib\Algo\Method.php(84): CondorcetPHP\Condorcet\Result->__construct()
// #1 C:\dev_scripts\Condorcet\lib\Algo\Methods\STV\SingleTransferableVote.php(91): CondorcetPHP\Condorcet\Algo\Method->createResult()
// #2 C:\dev_scripts\Condorcet\lib\Algo\Method.php(63): CondorcetPHP\Condorcet\Algo\Methods\STV\SingleTransferableVote->compute()
// #3 C:\dev_scripts\Condorcet\lib\ElectionProcess\ResultsProcess.php(80): CondorcetPHP\Condorcet\Algo\Method->getResult()
// #4 C:\dev_scripts\Condorcet\Dev\bugs\JitBug.php(24): CondorcetPHP\Condorcet\Election->getResult()
// #5 {main}
// thrown in C:\dev_scripts\Condorcet\lib\Result.php on line 89
# Test code
namespace CondorcetPHP\Condorcet;
require_once __DIR__.'/../../__CondorcetAutoload.php';
for ($i=1; $i <= 4000; $i++) {
# With Condorcet
$election = new Election;
$election->parseCandidates('A;B;C');
$election->addVote('A>B>C');
$election->getResult('Schulze');
$election->getResult('STV');
# Tentative to reproduce (impossible...)
// $a = new A (new Bar);
// $b = new B (new Bar);
}
# Tentative to reproduce (impossible...)
class Foo
{
public function __construct(public string $fromMethod, public string $byClass, public Bar $election, public array $result, public $stats, public ?int $seats = null, public array $methodOptions = [])
{
}
}
class Bar
{
}
abstract class Base
{
public const IS_PROPORTIONAL = false;
public Foo $foo;
public function __construct(public Bar $barInstance)
{
$this->foo = new Foo(
fromMethod: 'test',
byClass: $this::class,
election: $this->barInstance,
result: [],
stats: [],
seats: (static::IS_PROPORTIONAL) ? $this->getSeats() : null,
methodOptions: []
);
}
public function getSeats(): int
{
return 6;
}
}
class A extends Base
{
public const IS_PROPORTIONAL = false;
}
class B extends Base
{
public const IS_PROPORTIONAL = true;
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
require_once __DIR__.'/../__CondorcetAutoload.php';
///
$number_of_votes = 100_000_000;
$number_of_candidates = 8;
///
$candidateName = 'A';
$candidates = [];
for ($i=0; $i < $number_of_candidates; $i++) {
$candidates[] = $candidateName++;
}
$file = new \SplFileObject(__DIR__.'/large.votes', 'w+');
$cache = '';
for ($i=0; $i < $number_of_votes; $i++) {
shuffle($candidates);
$cache .= implode('>', $candidates)."\n";
if (mb_strlen($cache) > 5_000_000) {
$file->fwrite($cache);
$cache = '';
}
}
$file->fwrite($cache);

View File

@ -0,0 +1,5 @@
To generate documentation:
```
php Dev/update-documentation.php
```

View File

@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
namespace CondorcetPHP\Condorcet;
require_once __DIR__.'/../vendor/autoload.php';

View File

@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
use CondorcetPHP\Condorcet\Dev\CondorcetDocumentationGenerator\CondorcetDocAttributes\{PublicAPI};
use Symfony\Component\Yaml\Yaml;
require_once __DIR__.str_replace('/', \DIRECTORY_SEPARATOR, '/../vendor/../vendor/autoload.php');
$doc = Yaml::parseFile(__DIR__.'/../Documentation/doc.yaml');
// Header & Prefix
$header = $doc[0]['header'];
unset($doc[0]);
$undocumented_prefix = $doc[1]['undocumented_prefix'] . "\n";
unset($doc[1]);
//
$index = [];
foreach ($doc as &$entry) {
if (!\is_array($entry['class'])) {
$entry['class'] = [$entry['class']];
}
foreach ($entry['class'] as $class) {
$method = $entry;
$method['class'] = $class;
$index[$method['class']][$method['name']] = $method;
}
}
$patternReplaceLastNewLine = '/(.*)\n$/';
foreach ($index as $ClassName => $ClassData) {
$path_to_file = [
__DIR__.'/../lib/'.str_replace('\\', '/', $ClassName).'.php',
__DIR__.'/../lib/CondorcetVersion.php',
__DIR__.'/../lib/Linkable.php',
];
if ($ClassName === 'Election') {
$path_to_file[] = __DIR__.'/../lib/ElectionProcess/CandidatesProcess.php';
$path_to_file[] = __DIR__.'/../lib/ElectionProcess/VotesProcess.php';
$path_to_file[] = __DIR__.'/../lib/ElectionProcess/ResultsProcess.php';
}
// var_dump($path_to_file);
foreach ($path_to_file as $oneFile) {
$file_contents = file_get_contents($oneFile);
foreach ($ClassData as $MethodName => $MethodData) {
var_dump($ClassName.'->'.$MethodName);
$description = $MethodData['description'];
$description = str_replace('$', '\\\$', $description);
$description = preg_replace($patternReplaceLastNewLine, '$1', $description);
$description = str_replace(["\n ", "\n"], '\\n', $description);
$description = str_replace('"', '\"', $description);
$attributes = '$3#[Description("'.$description.'")]$2';
if (isset($MethodData['return'])) {
$returnV = $MethodData['return'];
$returnV = preg_replace($patternReplaceLastNewLine, '$1', $returnV);
$returnV = str_replace(["\n ", "\n"], '\\n', $returnV);
$returnV = str_replace('"', '\"', $returnV);
$attributes .= '$3#[FunctionReturn("'.$returnV.'")]$2';
}
if (isset($MethodData['examples'])) {
$examples = $MethodData['examples'];
$examples = preg_replace($patternReplaceLastNewLine, '$1', $examples);
$examples = str_replace(["\n ", "\n"], '\\n', $examples);
$examples = str_replace('"', '\"', $examples);
$arg = '';
$i = 1;
foreach ($examples as $oneExampleKey => $oneExample) {
if ($i++ > 1) {
$arg .= ', ';
}
$arg .= '"'.$oneExampleKey.'||'.$oneExample.'"';
}
$attributes .= '$3#[Examples('.$arg.')]$2';
}
if (isset($MethodData['related'])) {
$related = $MethodData['related'];
$related = str_replace('"', '\"', $related);
$arg = '';
$i = 1;
foreach ($related as $oneRelated) {
if ($i++ > 1) {
$arg .= ', ';
}
$arg .= '"'.$oneRelated.'"';
}
$attributes .= '$3#[Related('.$arg.')]$2';
}
$pattern = '/(#\[PublicAPI\])(\n)(.*)(public.*function '.$MethodName.' *\()/';
$replacement = '$1$2'.$attributes.'$3$4';
$file_contents = preg_replace(
$pattern,
$replacement,
$file_contents
);
}
file_put_contents($oneFile, $file_contents);
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
use CondorcetPHP\Condorcet\Dev\CondorcetDocumentationGenerator\Generate;
require_once __DIR__.str_replace('/', \DIRECTORY_SEPARATOR, '/../vendor/../vendor/autoload.php');
// Build command
$path = mb_substr(__DIR__, 0, mb_strlen(__DIR__) - 4);
$path .= \DIRECTORY_SEPARATOR.'Documentation';
// Clear folder
function rrmdir(string $dir, string $path): void
{
if (is_dir($dir)) {
$objects = scandir($dir);
foreach ($objects as $object) {
if ($object !== '.' && $object !== '..') {
if (filetype($dir.\DIRECTORY_SEPARATOR.$object) === 'dir') {
rrmdir($dir.\DIRECTORY_SEPARATOR.$object, $path);
} else {
unlink($dir.\DIRECTORY_SEPARATOR.$object);
}
}
}
reset($objects);
if ($dir !== $path) {
rmdir($dir);
}
}
}
rrmdir($path, $path);
// Execute command
new Generate($path);