debug
This commit is contained in:
3
include/condorcet/src/Console/Assets/Sources.md
Normal file
3
include/condorcet/src/Console/Assets/Sources.md
Normal file
@ -0,0 +1,3 @@
|
||||
* Logo 73c: https://patorjk.com/software/taag/#p=display&f=Slant&t=Condorcet%20PHP _(default)_
|
||||
|
||||
* Logos 90c && 125c: https://patorjk.com/software/taag/#p=display&h=1&f=Lean&t=Condorcet%20PHP _(fitted)_
|
7
include/condorcet/src/Console/Assets/logo.125c.ascii
Normal file
7
include/condorcet/src/Console/Assets/logo.125c.ascii
Normal file
@ -0,0 +1,7 @@
|
||||
<condor1> _/_/_/ _/ _/ </><condor2> _/_/_/ _/ _/ _/_/_/</>
|
||||
<condor1> _/ _/_/ _/_/_/ _/_/_/ _/_/ _/ _/_/ _/_/_/ _/_/ _/_/_/_/ </><condor2> _/ _/ _/ _/ _/ _/</>
|
||||
<condor1> _/ _/ _/ _/ _/ _/ _/ _/ _/ _/_/ _/ _/_/_/_/ _/ </><condor2> _/_/_/ _/_/_/_/ _/_/_/</>
|
||||
<condor1>_/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ </><condor2> _/ _/ _/ _/</>
|
||||
<condor1> _/_/_/ _/_/ _/ _/ _/_/_/ _/_/ _/ _/_/_/ _/_/_/ _/_/ </><condor2> _/ _/ _/ _/</>
|
||||
|
||||
<condor1>========================================================================================== </><condor2> ===============================</>
|
5
include/condorcet/src/Console/Assets/logo.73c.ascii
Normal file
5
include/condorcet/src/Console/Assets/logo.73c.ascii
Normal file
@ -0,0 +1,5 @@
|
||||
<condor1> ______ __ __ </><condor2> ____ __ ______ </>
|
||||
<condor1> / ____/___ ____ ____/ /___ _____________ / /_</><condor2> / __ \/ / / / __ \ </>
|
||||
<condor1> / / / __ \/ __ \/ __ / __ \/ ___/ ___/ _ \/ __/</><condor2> / /_/ / /_/ / /_/ /</>
|
||||
<condor1>/ /___/ /_/ / / / / /_/ / /_/ / / / /__/ __/ /_ </><condor2> / ____/ __ / ____/ </>
|
||||
<condor1>\____/\____/_/ /_/\__,_/\____/_/ \___/\___/\__/ </><condor2>/_/ /_/ /_/_/ </>
|
7
include/condorcet/src/Console/Assets/logo.90c.ascii
Normal file
7
include/condorcet/src/Console/Assets/logo.90c.ascii
Normal file
@ -0,0 +1,7 @@
|
||||
<condor1> _/_/_/ _/ _/ </>
|
||||
<condor1> _/ _/_/ _/_/_/ _/_/_/ _/_/ _/ _/_/ _/_/_/ _/_/ _/_/_/_/</>
|
||||
<condor1> _/ _/ _/ _/ _/ _/ _/ _/ _/ _/_/ _/ _/_/_/_/ _/ </>
|
||||
<condor1>_/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ </>
|
||||
<condor1> _/_/_/ _/_/ _/ _/ _/_/_/ _/_/ _/ _/_/_/ _/_/_/ _/_/ </>
|
||||
|
||||
<condor1>==========================================================================================</>
|
803
include/condorcet/src/Console/Commands/ElectionCommand.php
Normal file
803
include/condorcet/src/Console/Commands/ElectionCommand.php
Normal file
@ -0,0 +1,803 @@
|
||||
<?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\Console\Commands;
|
||||
|
||||
use CondorcetPHP\Condorcet\Algo\Tools\StvQuotas;
|
||||
use CondorcetPHP\Condorcet\Constraints\NoTie;
|
||||
use CondorcetPHP\Condorcet\DataManager\DataHandlerDrivers\PdoDriver\PdoHandlerDriver;
|
||||
use CondorcetPHP\Condorcet\{Condorcet, Election};
|
||||
use CondorcetPHP\Condorcet\Algo\StatsVerbosity;
|
||||
use CondorcetPHP\Condorcet\Console\Helper\{CommandInputHelper, FormaterHelper};
|
||||
use CondorcetPHP\Condorcet\Console\Style\CondorcetStyle;
|
||||
use Symfony\Component\Console\Input\{InputArgument, InputInterface, InputOption};
|
||||
use CondorcetPHP\Condorcet\Throwable\{FileDoesNotExistException, VoteConstraintException};
|
||||
use CondorcetPHP\Condorcet\Throwable\Internal\CondorcetInternalException;
|
||||
use CondorcetPHP\Condorcet\Timer\{Chrono, Manager};
|
||||
use Symfony\Component\Console\Helper\{Table, TableSeparator, TableStyle};
|
||||
use CondorcetPHP\Condorcet\Tools\Converters\{CondorcetElectionFormat, DavidHillFormat, DebianFormat};
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Completion\{CompletionInput, CompletionSuggestions};
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Terminal;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'election',
|
||||
description: 'Process an election',
|
||||
hidden: false,
|
||||
aliases: ['condorcet']
|
||||
)]
|
||||
class ElectionCommand extends Command
|
||||
{
|
||||
protected ?Election $election;
|
||||
protected ?string $candidates;
|
||||
protected ?string $votes;
|
||||
protected bool $displayMethodsStats = false;
|
||||
|
||||
protected ?string $CondorcetElectionFormatPath;
|
||||
protected ?string $DebianFormatPath;
|
||||
protected ?string $DavidHillFormatPath;
|
||||
|
||||
public static int $VotesPerMB = 100;
|
||||
protected string $iniMemoryLimit;
|
||||
protected int $maxVotesInMemory;
|
||||
|
||||
// Internal Process
|
||||
protected bool $candidatesListIsWrite = false;
|
||||
protected bool $votesCountIsWrite = false;
|
||||
protected bool $pairwiseIsWrite = false;
|
||||
public ?string $SQLitePath = null;
|
||||
|
||||
// TableFormat & Terminal
|
||||
protected Terminal $terminal;
|
||||
protected CondorcetStyle $io;
|
||||
|
||||
// Debug
|
||||
public static ?string $forceIniMemoryLimitTo = null;
|
||||
protected Manager $timer;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setHelp('This command takes candidates and votes as input. The output is the result of that election.')
|
||||
|
||||
->addOption(
|
||||
name: 'candidates',
|
||||
shortcut: 'c',
|
||||
mode: InputOption::VALUE_REQUIRED,
|
||||
description: 'Candidates list file path or direct input',
|
||||
)
|
||||
->addOption(
|
||||
name: 'votes',
|
||||
shortcut: 'w',
|
||||
mode: InputOption::VALUE_REQUIRED,
|
||||
description: 'Votes list file path or direct input',
|
||||
)
|
||||
->addOption(
|
||||
name: 'import-condorcet-election-format',
|
||||
mode: InputOption::VALUE_REQUIRED,
|
||||
description: 'File path. Setup an election and his data from a Condorcet Election file as defined as standard on https://github.com/CondorcetPHP/CondorcetElectionFormat . Other parameters from the command line argument have the priority if set. Other votes can be added with the --vote argument, other candidates can\'t be added.',
|
||||
)
|
||||
->addOption(
|
||||
name: 'import-debian-format',
|
||||
mode: InputOption::VALUE_REQUIRED,
|
||||
description: 'File path. Setup an election and his data from a Debian tally file. Other votes can be added with the --vote argument, other candidates can\'t be added.',
|
||||
)
|
||||
->addOption(
|
||||
name: 'import-david-hill-format',
|
||||
mode: InputOption::VALUE_REQUIRED,
|
||||
description: 'File path. Setup an election and his data from a Debian tally file. Other votes can be added with the --vote argument, other candidates can\'t be added.',
|
||||
)
|
||||
|
||||
|
||||
->addOption(
|
||||
name: 'stats',
|
||||
shortcut: 's',
|
||||
mode: InputOption::VALUE_NONE,
|
||||
description: 'Get detailed stats (equivalent to --show-pairwise and --method-stats)',
|
||||
)
|
||||
->addOption(
|
||||
name: 'method-stats',
|
||||
mode: InputOption::VALUE_NONE,
|
||||
description: 'Get detailed stats per method',
|
||||
)
|
||||
->addOption(
|
||||
name: 'show-pairwise',
|
||||
shortcut: 'p',
|
||||
mode: InputOption::VALUE_NONE,
|
||||
description: 'Get pairwise computation',
|
||||
)
|
||||
->addOption(
|
||||
name: 'list-votes',
|
||||
shortcut: 'l',
|
||||
mode: InputOption::VALUE_NONE,
|
||||
description: 'List registered votes',
|
||||
)
|
||||
->addOption(
|
||||
name: 'natural-condorcet',
|
||||
shortcut: 'r',
|
||||
mode: InputOption::VALUE_NONE,
|
||||
description: 'Print natural Condorcet winner / loser',
|
||||
)
|
||||
->addOption(
|
||||
name: 'deactivate-implicit-ranking',
|
||||
shortcut: 'i',
|
||||
mode: InputOption::VALUE_NONE,
|
||||
description: 'Deactivate implicit ranking',
|
||||
)
|
||||
->addOption(
|
||||
name: 'allows-votes-weight',
|
||||
shortcut: 'g',
|
||||
mode: InputOption::VALUE_NONE,
|
||||
description: 'Allows vote weight',
|
||||
)
|
||||
->addOption(
|
||||
name: 'no-tie',
|
||||
shortcut: 't',
|
||||
mode: InputOption::VALUE_NONE,
|
||||
description: 'Add no-tie constraint for vote',
|
||||
)
|
||||
|
||||
->addOption(
|
||||
name: 'seats',
|
||||
mode: InputOption::VALUE_REQUIRED,
|
||||
description: 'Specify the number of seats for proportional methods',
|
||||
)
|
||||
->addOption(
|
||||
name: 'quota',
|
||||
mode: InputOption::VALUE_REQUIRED,
|
||||
description: 'Quota to be used for STV compatible methods',
|
||||
)
|
||||
|
||||
->addOption(
|
||||
name: 'deactivate-file-cache',
|
||||
mode: InputOption::VALUE_NONE,
|
||||
description: "Don't use a disk cache for very large elections. Forces to work exclusively in RAM.",
|
||||
)
|
||||
->addOption(
|
||||
name: 'votes-per-mb',
|
||||
mode: InputOption::VALUE_REQUIRED,
|
||||
description: 'Adjust memory in case of failure. Default is 100. Try to lower it.',
|
||||
)
|
||||
|
||||
->addArgument(
|
||||
name: 'methods',
|
||||
mode: InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
|
||||
description: 'Methods to output',
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestOptionValuesFor('quota')) {
|
||||
$r = [];
|
||||
|
||||
foreach (StvQuotas::cases() as $case) {
|
||||
$r[] = $case->name;
|
||||
}
|
||||
|
||||
$suggestions->suggestValues($r);
|
||||
} elseif ($input->mustSuggestArgumentValuesFor('methods')) {
|
||||
$suggestions->suggestValues(Condorcet::getAuthMethods());
|
||||
}
|
||||
}
|
||||
|
||||
protected function initialize(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
// Initialize Style & Terminal
|
||||
$this->io = new CondorcetStyle($input, $output);
|
||||
$this->terminal = new Terminal;
|
||||
|
||||
// Setup Timer Manager
|
||||
$this->timer = new Manager;
|
||||
|
||||
// Setup Memory
|
||||
if ($input->getOption('votes-per-mb') && ($NewVotesPerMB = (int) $input->getOption('votes-per-mb')) >= 1) {
|
||||
self::$VotesPerMB = $NewVotesPerMB;
|
||||
}
|
||||
|
||||
// Setup Election Object
|
||||
$this->election = new Election;
|
||||
$this->displayMethodsStats = $input->getOption('method-stats') || $input->getOption('stats');
|
||||
$this->election->setStatsVerbosity($this->displayMethodsStats ? StatsVerbosity::HIGH : StatsVerbosity::NONE);
|
||||
|
||||
// Parameters
|
||||
$this->setUpParameters($input);
|
||||
$this->iniMemoryLimit = (self::$forceIniMemoryLimitTo === null) ? preg_replace('`[^-0-9KMG]`', '', \ini_get('memory_limit')) : self::$forceIniMemoryLimitTo;
|
||||
|
||||
// Non-interactive candidates
|
||||
$this->candidates = $input->getOption('candidates') ?? null;
|
||||
|
||||
// Non-interactive votes
|
||||
$this->votes = $input->getOption('votes') ?? null;
|
||||
|
||||
$this->CondorcetElectionFormatPath = $input->getOption('import-condorcet-election-format') ?? null;
|
||||
$this->DebianFormatPath = $input->getOption('import-debian-format') ?? null;
|
||||
$this->DavidHillFormatPath = $input->getOption('import-david-hill-format') ?? null;
|
||||
|
||||
// Logo
|
||||
$this->io->newLine();
|
||||
$this->io->logo($this->terminal->getWidth());
|
||||
$this->io->newLine();
|
||||
|
||||
// Header
|
||||
$this->io->version();
|
||||
$this->io->inlineSeparator();
|
||||
$this->io->author(Condorcet::AUTHOR);
|
||||
$this->io->inlineSeparator();
|
||||
$this->io->homepage(Condorcet::HOMEPAGE);
|
||||
$this->io->newLine(2);
|
||||
}
|
||||
|
||||
protected function interact(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
if (empty($this->CondorcetElectionFormatPath) && empty($this->DebianFormatPath) && empty($this->DavidHillFormatPath)) {
|
||||
// Interactive Candidates
|
||||
if (empty($this->candidates)) {
|
||||
$this->io->title('Enter the candidates');
|
||||
$this->io->instruction('Candidates', 'Enter each candidate names');
|
||||
|
||||
$registeringCandidates = [];
|
||||
|
||||
while (true) {
|
||||
$answer = $this->io->ask('Please register candidate N°<fg=magenta>'.(\count($registeringCandidates) + 1).'</> <continue>(or press enter to continue)</>');
|
||||
|
||||
if ($answer === null) {
|
||||
break;
|
||||
} else {
|
||||
array_push($registeringCandidates, ...explode(';', $answer));
|
||||
}
|
||||
}
|
||||
|
||||
$this->candidates = implode(';', $registeringCandidates);
|
||||
}
|
||||
|
||||
// Interactive Votes
|
||||
if (empty($this->votes)) {
|
||||
$this->io->title('Enter the votes');
|
||||
$this->io->instruction('Format', 'Candidate B > CandidateName D > CandidateName C = CandidateName A');
|
||||
|
||||
$registeringVotes = [];
|
||||
|
||||
while (true) {
|
||||
$answer = $this->io->ask('Please register vote N°<fg=magenta>'.(\count($registeringVotes) + 1).'</> <continue>(or press enter to continue)</>');
|
||||
|
||||
if ($answer === null) {
|
||||
break;
|
||||
} else {
|
||||
array_push($registeringVotes, ...explode(';', $answer));
|
||||
}
|
||||
}
|
||||
|
||||
$this->votes = implode(';', $registeringVotes);
|
||||
}
|
||||
|
||||
// Interactive Methods
|
||||
if (empty($input->getArgument('methods'))) {
|
||||
$this->io->title('Enter the methods');
|
||||
$this->io->instruction('Voting methods', 'Choose by entering their numbers separated by commas. Press enter for the default method.');
|
||||
|
||||
$c = 0;
|
||||
$registeringMethods = [];
|
||||
|
||||
$authMehods = Condorcet::getAuthMethods();
|
||||
$authMehods = array_merge(['ALL'], $authMehods);
|
||||
|
||||
$registeringMethods = $this->io->choiceMultiple('Select methods', $authMehods, Condorcet::getDefaultMethod()::METHOD_NAME[0], true);
|
||||
|
||||
$input->setArgument('methods', $registeringMethods);
|
||||
}
|
||||
|
||||
if (empty($input->getOption('seats'))) {
|
||||
$hasProportionalMethods = false;
|
||||
$methods = FormaterHelper::prepareMethods($input->getArgument('methods'));
|
||||
|
||||
foreach ($methods as $oneMethod) {
|
||||
if ($oneMethod['class']::IS_PROPORTIONAL) {
|
||||
$hasProportionalMethods = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasProportionalMethods) {
|
||||
$this->io->instruction('Number of Seats', 'Some of the method(s) chosen are proportional and require a number of seats.');
|
||||
|
||||
$answer = $this->io->ask('Number of seats to fill', (string) 100, static function ($answer): string {
|
||||
if (!is_numeric($answer)) {
|
||||
throw new CondorcetInternalException('Seats must be numeric');
|
||||
}
|
||||
|
||||
if (($answer = (int) $answer) < 1) {
|
||||
throw new CondorcetInternalException('Seats must be a natural number (positive)');
|
||||
}
|
||||
|
||||
return (string) $answer;
|
||||
});
|
||||
|
||||
$input->setOption('seats', $answer);
|
||||
$this->election->setNumberOfSeats((int) $answer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function importInputsData(InputInterface $input): void
|
||||
{
|
||||
// Define Callback
|
||||
$callBack = $this->useDataHandler($input);
|
||||
|
||||
// Setup Elections, candidates and votes from Condorcet Election Format
|
||||
if ($input->getOption('import-condorcet-election-format')) {
|
||||
$this->parseFromCondorcetElectionFormat($callBack);
|
||||
$this->setUpParameters($input); # Do it again, because command line parameters have priority
|
||||
}
|
||||
|
||||
if ($input->getOption('import-debian-format')) {
|
||||
$this->election->setNumberOfSeats(1); # Debian must be 1
|
||||
$this->parseFromDebianFormat();
|
||||
$this->setUpParameters($input); # Do it again, because command line parameters have priority
|
||||
}
|
||||
|
||||
if ($input->getOption('import-david-hill-format')) {
|
||||
$this->parseFromDavidHillFormat();
|
||||
$this->setUpParameters($input); # Do it again, because command line parameters have priority
|
||||
}
|
||||
|
||||
// Parse Votes & Candidates from classicals inputs
|
||||
!empty($this->candidates) && $this->parseFromCandidatesArguments();
|
||||
!empty($this->votes) && $this->parseFromVotesArguments($callBack);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
// Start Timer Chrono
|
||||
$chrono = new Chrono($this->timer, 'CondorcetConsoleCommand');
|
||||
|
||||
// Import Inputs
|
||||
$this->importInputsData($input);
|
||||
|
||||
// Inputs Display Section
|
||||
$this->displayInputsSection();
|
||||
|
||||
// Debug Display Section
|
||||
if ($output->isDebug()) {
|
||||
$this->displayDebugSection();
|
||||
}
|
||||
|
||||
// Configuration Display Section
|
||||
$this->displayConfigurationSection();
|
||||
|
||||
// Verbose Display Section
|
||||
if ($output->isVerbose() || $input->getOption('list-votes')) {
|
||||
$this->displayDetailedElectionInputsSection($input, $output);
|
||||
}
|
||||
|
||||
// Pairwise Display Section
|
||||
if ($input->getOption('show-pairwise') || $input->getOption('stats')) {
|
||||
$this->displayPairwiseSection($output);
|
||||
$this->io->newLine();
|
||||
}
|
||||
|
||||
// NaturalCondorcet Dislay Section
|
||||
$this->displayNaturalCondorcet($input, $output);
|
||||
|
||||
// MethodsResults Display Section
|
||||
$this->io->newLine();
|
||||
$this->io->title('Results per methods');
|
||||
$this->displayMethodsResultSection($input, $output);
|
||||
|
||||
|
||||
// RM Sqlite Database if exist
|
||||
/**
|
||||
* @infection-ignore-all
|
||||
*/
|
||||
if (($SQLitePath = $this->SQLitePath) !== null) {
|
||||
$this->election = null;
|
||||
unlink($SQLitePath);
|
||||
}
|
||||
|
||||
// Timer
|
||||
unset($chrono);
|
||||
$this->displayTimerSection();
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
protected function displayInputsSection(): void
|
||||
{
|
||||
$messageCandidates = "<condor1>{$this->election->countCandidates()} candidate".($this->election->countCandidates() > 1 ? 's' : '').' registered</>';
|
||||
$messageVotes = '<condor2>'.number_format($this->election->countVotes(), thousands_separator: ' ').' vote'.($this->election->countVotes() > 1 ? 's' : '').' registered</>';
|
||||
|
||||
$this->io->writeln('<comment>'.str_repeat('-', mb_strlen($messageCandidates.$messageVotes) + 4 - 24).'</comment>');
|
||||
$this->io->write($messageCandidates);
|
||||
$this->io->inlineSeparator();
|
||||
$this->io->writeln($messageVotes);
|
||||
$this->io->newLine();
|
||||
}
|
||||
|
||||
protected function displayDebugSection(): void
|
||||
{
|
||||
$this->io->newLine();
|
||||
$this->io->title('Debug - External Handler Informations');
|
||||
|
||||
$this->io->definitionList(
|
||||
['Ini max_memory' => $this->iniMemoryLimit],
|
||||
['Votes per Mb' => self::$VotesPerMB],
|
||||
['Db is used' => (empty($this->SQLitePath)) ? 'no' : 'yes, using path: '.$this->SQLitePath],
|
||||
['Max Votes in Memory' => $this->maxVotesInMemory],
|
||||
);
|
||||
}
|
||||
|
||||
protected function displayConfigurationSection(): void
|
||||
{
|
||||
$this->io->title('Configuration');
|
||||
|
||||
$this->io->definitionList(
|
||||
['Is vote weight allowed?' => $this->election->isVoteWeightAllowed() ? 'TRUE' : 'FALSE'],
|
||||
new TableSeparator,
|
||||
['Votes are evaluated according to the implicit ranking rule?' => $this->election->getImplicitRankingRule() ? 'TRUE' : 'FALSE'],
|
||||
new TableSeparator,
|
||||
['Is vote tie in rank allowed?' => \in_array(needle: NoTie::class, haystack: $this->election->getConstraints(), strict: true) ? 'FALSE' : 'TRUE']
|
||||
);
|
||||
}
|
||||
|
||||
protected function displayDetailedElectionInputsSection(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
$this->io->title('Detailed Election Inputs');
|
||||
|
||||
if ($output->isVerbose()) {
|
||||
$this->displayVerbose($output);
|
||||
}
|
||||
|
||||
// List-Votes Display Section
|
||||
if ($input->getOption('list-votes')) {
|
||||
$this->displayVotesCount($output);
|
||||
$this->io->newLine();
|
||||
$this->displayVotesList($output);
|
||||
$this->io->newLine();
|
||||
}
|
||||
}
|
||||
|
||||
protected function displayVerbose(OutputInterface $output): void
|
||||
{
|
||||
$this->displayCandidatesList($output);
|
||||
$this->io->newLine();
|
||||
$this->displayVotesCount($output);
|
||||
$this->io->newLine();
|
||||
}
|
||||
|
||||
protected function displayCandidatesList(OutputInterface $output): void
|
||||
{
|
||||
if (!$this->candidatesListIsWrite) {
|
||||
// Candidate List
|
||||
($candidateTable = new Table($output))
|
||||
->setHeaderTitle('Registered candidates')
|
||||
->setHeaders(['Num', 'Candidate name'])
|
||||
|
||||
->setStyle($this->io->MainTableStyle)
|
||||
->setColumnStyle(0, $this->io->FirstColumnStyle)
|
||||
->setColumnWidth(0, 14)
|
||||
;
|
||||
|
||||
$candidate_num = 1;
|
||||
foreach ($this->election->getCandidatesListAsString() as $oneCandidate) {
|
||||
$candidateTable->addRow([$candidate_num++, $oneCandidate]);
|
||||
}
|
||||
|
||||
$candidateTable->render();
|
||||
|
||||
$this->candidatesListIsWrite = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected function displayVotesCount(OutputInterface $output): void
|
||||
{
|
||||
if (!$this->votesCountIsWrite) {
|
||||
// Votes Count
|
||||
($votesStatsTable = new Table($output))
|
||||
->setHeaderTitle('Stats - votes registration')
|
||||
->setHeaders(['Stats', 'Value'])
|
||||
->setColumnStyle(0, (new Tablestyle)->setPadType(\STR_PAD_LEFT))
|
||||
->setStyle($this->io->MainTableStyle)
|
||||
;
|
||||
|
||||
$formatter = static fn (int $input): string => number_format($input, thousands_separator: ' ');
|
||||
|
||||
$votesStatsTable->addRow(['Count registered votes', $formatter($this->election->countVotes())]);
|
||||
$votesStatsTable->addRow(['Count valid registered votes with constraints', $formatter($this->election->countValidVoteWithConstraints())]);
|
||||
$votesStatsTable->addRow(['Count invalid registered votes with constraints', $formatter($this->election->countInvalidVoteWithConstraints())]);
|
||||
$votesStatsTable->addRow(['Sum vote weight', $formatter($this->election->sumVotesWeight())]);
|
||||
$votesStatsTable->addRow(['Sum valid votes weight with constraints', $formatter($this->election->sumValidVotesWeightWithConstraints())]);
|
||||
|
||||
$votesStatsTable->render();
|
||||
|
||||
$this->votesCountIsWrite = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected function displayVotesList(OutputInterface $output): void
|
||||
{
|
||||
($votesTable = new Table($output))
|
||||
->setHeaderTitle('Registered Votes List')
|
||||
->setHeaders(['Vote Num', 'Vote', 'Weight', 'Vote Tags'])
|
||||
|
||||
->setColumnWidth(0, 8)
|
||||
->setColumnWidth(1, 30)
|
||||
->setColumnMaxWidth(2, 6)
|
||||
->setStyle($this->io->MainTableStyle)
|
||||
;
|
||||
|
||||
foreach ($this->election->getVotesValidUnderConstraintGenerator() as $voteKey => $oneVote) {
|
||||
$votesTable->addRow([($voteKey + 1), $oneVote->getSimpleRanking($this->election, false), $oneVote->getWeight($this->election), implode(',', $oneVote->getTags())]);
|
||||
}
|
||||
|
||||
$votesTable->render();
|
||||
}
|
||||
|
||||
protected function displayPairwiseSection(OutputInterface $output): void
|
||||
{
|
||||
if (!$this->pairwiseIsWrite) {
|
||||
(new Table($output))
|
||||
->setHeaderTitle('Pairwise')
|
||||
->setHeaders(['For each candidate, show their win, null, or lose'])
|
||||
->setRows([[preg_replace('#!!float (\d+)#', '\1.0', Yaml::dump($this->election->getExplicitPairwise(), 100))]])
|
||||
|
||||
->setStyle($this->io->MainTableStyle)
|
||||
->render()
|
||||
;
|
||||
|
||||
$this->pairwiseIsWrite= true;
|
||||
}
|
||||
}
|
||||
|
||||
protected function displayNaturalCondorcet(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
if ($input->getOption('natural-condorcet')) {
|
||||
$this->io->methodResultSection('Condorcet natural winner & loser');
|
||||
$this->io->newLine();
|
||||
|
||||
(new Table($output))
|
||||
->setHeaderTitle('Natural Condorcet')
|
||||
->setHeaders(['Type', 'Candidate'])
|
||||
->setRows([
|
||||
[CondorcetStyle::CONDORCET_WINNER_SYMBOL_FORMATED.' Condorcet Winner', (string) ($this->election->getCondorcetWinner() ?? '-')],
|
||||
[CondorcetStyle::CONDORCET_LOSER_SYMBOL_FORMATED.' Condorcet Loser', (string) ($this->election->getCondorcetLoser() ?? '-')],
|
||||
])
|
||||
|
||||
->setStyle($this->io->MainTableStyle)
|
||||
->render()
|
||||
;
|
||||
|
||||
$this->io->newLine();
|
||||
}
|
||||
}
|
||||
|
||||
protected function displayMethodsResultSection(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
$methods = FormaterHelper::prepareMethods($input->getArgument('methods'));
|
||||
|
||||
foreach ($methods as $oneMethod) {
|
||||
$this->io->methodResultSection($oneMethod['name']);
|
||||
|
||||
if (isset($oneMethod['class']::$optionQuota) && $input->getOption('quota') !== null) {
|
||||
$this->election->setMethodOption($oneMethod['class'], 'Quota', StvQuotas::make($input->getOption('quota'))); // @phpstan-ignore-line
|
||||
}
|
||||
|
||||
// Result
|
||||
$result = $this->election->getResult($oneMethod['name']);
|
||||
|
||||
if (!empty($options = $result->getMethodOptions())) {
|
||||
$rows = [];
|
||||
foreach ($options as $key => $value) {
|
||||
if ($value instanceof \BackedEnum) {
|
||||
$value = $value->value;
|
||||
} elseif (\is_array($value)) {
|
||||
$value = implode(' / ', $value);
|
||||
}
|
||||
|
||||
$rows[] = [$key.':', $value];
|
||||
}
|
||||
|
||||
$this->io->newLine();
|
||||
|
||||
(new Table($output))
|
||||
->setHeaderTitle('Configuration: '.$oneMethod['name'])
|
||||
->setHeaders(['Variable', 'Value'])
|
||||
->setRows($rows)
|
||||
|
||||
->setColumnStyle(0, $this->io->FirstColumnStyle)
|
||||
->setColumnMaxWidth(0, 30)
|
||||
|
||||
->setColumnMaxWidth(0, 40)
|
||||
|
||||
->setStyle($this->io->MainTableStyle)
|
||||
->render()
|
||||
;
|
||||
}
|
||||
|
||||
$this->io->newLine();
|
||||
|
||||
$this->io->write('<condor3>'.CondorcetStyle::CONDORCET_WINNER_SYMBOL_FORMATED.' Condorcet Winner</>');
|
||||
$this->io->inlineSeparator();
|
||||
$this->io->writeln('<condor3>'.CondorcetStyle::CONDORCET_LOSER_SYMBOL_FORMATED.' Condorcet Loser</>');
|
||||
|
||||
(new Table($output))
|
||||
->setHeaderTitle('Results: '.$oneMethod['name'])
|
||||
->setHeaders(['Rank', 'Candidates'])
|
||||
->setRows(FormaterHelper::formatResultTable($result))
|
||||
|
||||
->setColumnWidth(0, $fcw = 20)
|
||||
->setColumnWidth(1, 50)
|
||||
->setColumnMaxWidth(1, ($this->terminal->getWidth() - $fcw - 10 - 3))
|
||||
|
||||
->setColumnStyle(0, $this->io->FirstColumnStyle)
|
||||
->setStyle($this->io->MainTableStyle)
|
||||
->render()
|
||||
;
|
||||
|
||||
// Stats
|
||||
if ($this->displayMethodsStats) {
|
||||
$table = (new Table($output))
|
||||
->setHeaderTitle('Stats: '.$oneMethod['name'])
|
||||
|
||||
->setColumnWidth(0, 73)
|
||||
->setColumnMaxWidth(0, $this->terminal->getWidth() - 10)
|
||||
// ->setColumnStyle(0, $this->io->FirstColumnStyle)
|
||||
->setStyle($this->io->MainTableStyle)
|
||||
;
|
||||
|
||||
$line = 0;
|
||||
foreach ($result->getStats() as $oneStatKey => $oneStatEntry) {
|
||||
++$line !== 1 && $table->addRow(new TableSeparator);
|
||||
$table->addRow([preg_replace('#!!float (\d+)#', '\1.0', Yaml::dump([$oneStatKey => $oneStatEntry], 100))]);
|
||||
}
|
||||
|
||||
$table->render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function displayTimerSection(): void
|
||||
{
|
||||
$executionTime = round($this->timer->getGlobalTimer(), 4);
|
||||
|
||||
$this->io->newLine();
|
||||
$this->io->writeln('<comment>'.str_repeat('-', 23).'</comment>', OutputInterface::VERBOSITY_VERBOSE);
|
||||
$this->io->writeln("<comment>Execution Time: {$executionTime}s</comment>", OutputInterface::VERBOSITY_VERBOSE);
|
||||
$this->io->newLine();
|
||||
}
|
||||
|
||||
# Processing
|
||||
|
||||
protected function setUpParameters(InputInterface $input): void
|
||||
{
|
||||
/// Implicit Ranking
|
||||
if ($input->getOption('deactivate-implicit-ranking')) {
|
||||
$this->election->setImplicitRanking(false);
|
||||
}
|
||||
|
||||
// Allow Votes Weight
|
||||
if ($input->getOption('allows-votes-weight')) {
|
||||
$this->election->allowsVoteWeight(true);
|
||||
}
|
||||
|
||||
if ($input->getOption('seats') && ($seats = (int) $input->getOption('seats')) >= 1) {
|
||||
$this->election->setNumberOfSeats($seats);
|
||||
}
|
||||
|
||||
try {
|
||||
if ($input->getOption('no-tie')) {
|
||||
$this->election->addConstraint(NoTie::class);
|
||||
}
|
||||
} catch (VoteConstraintException $e) {
|
||||
str_contains($e->getMessage(), 'class is already registered') || throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseFromCandidatesArguments(): void
|
||||
{
|
||||
if ($file = CommandInputHelper::getFilePath($this->candidates)) {
|
||||
$this->election->parseCandidates($file, true);
|
||||
} else {
|
||||
$this->election->parseCandidates($this->candidates);
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseFromVotesArguments(\Closure $callBack): void
|
||||
{
|
||||
// Parses Votes
|
||||
if ($file = CommandInputHelper::getFilePath($this->votes)) {
|
||||
$this->election->parseVotesWithoutFail(input: $file, isFile: true, callBack: $callBack);
|
||||
} else {
|
||||
$this->election->parseVotesWithoutFail(input: $this->votes, isFile: false, callBack: $callBack);
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseFromCondorcetElectionFormat(\Closure $callBack): void
|
||||
{
|
||||
$file = CommandInputHelper::getFilePath($this->CondorcetElectionFormatPath);
|
||||
|
||||
if ($file !== null) {
|
||||
(new CondorcetElectionFormat($file))->setDataToAnElection($this->election, $callBack);
|
||||
} else {
|
||||
throw new FileDoesNotExistException('File does not exist, path: '.$this->CondorcetElectionFormatPath);
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseFromDebianFormat(): void
|
||||
{
|
||||
$file = CommandInputHelper::getFilePath($this->DebianFormatPath);
|
||||
|
||||
if ($file !== null) {
|
||||
(new DebianFormat($file))->setDataToAnElection($this->election);
|
||||
} else {
|
||||
throw new FileDoesNotExistException('File does not exist, path: '.$this->CondorcetElectionFormatPath);
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseFromDavidHillFormat(): void
|
||||
{
|
||||
$file = CommandInputHelper::getFilePath($this->DavidHillFormatPath);
|
||||
|
||||
if ($file !== null) {
|
||||
(new DavidHillFormat($file))->setDataToAnElection($this->election);
|
||||
} else {
|
||||
throw new FileDoesNotExistException('File does not exist, path: '.$this->CondorcetElectionFormatPath);
|
||||
}
|
||||
}
|
||||
|
||||
protected function useDataHandler(InputInterface $input): ?\Closure
|
||||
{
|
||||
if ($input->getOption('deactivate-file-cache') || !class_exists('\PDO') || !\in_array(needle: 'sqlite', haystack: \PDO::getAvailableDrivers(), strict: true)) {
|
||||
return null;
|
||||
} else {
|
||||
if ($this->iniMemoryLimit === '-1') {
|
||||
$memoryLimit = 8 * (1000 * 1048576); # Limit to 8GB, use a true memory limit to go further
|
||||
} else {
|
||||
$memoryLimit = (int) preg_replace('`[^0-9]`', '', $this->iniMemoryLimit);
|
||||
$memoryLimit *= match (mb_strtoupper(mb_substr($this->iniMemoryLimit, -1, 1))) {
|
||||
'K' => 1024,
|
||||
'M' => 1048576,
|
||||
'G' => (1000 * 1048576),
|
||||
default => 1
|
||||
};
|
||||
}
|
||||
|
||||
$memoryLimit = (int) ($memoryLimit / 1048576);
|
||||
$this->maxVotesInMemory = self::$VotesPerMB * $memoryLimit;
|
||||
|
||||
$callBack = function (int $inserted_votes_count): bool {
|
||||
if ($inserted_votes_count > $this->maxVotesInMemory) {
|
||||
|
||||
/**
|
||||
* @infection-ignore-all
|
||||
*/
|
||||
if (file_exists($this->SQLitePath = getcwd().\DIRECTORY_SEPARATOR.'condorcet-bdd.sqlite')) {
|
||||
unlink($this->SQLitePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @infection-ignore-all
|
||||
*/
|
||||
$this->election->setExternalDataHandler(new PdoHandlerDriver(new \PDO('sqlite:'.$this->SQLitePath, '', '', [\PDO::ATTR_PERSISTENT => false]), true));
|
||||
|
||||
return false; // No, stop next iteration
|
||||
} else {
|
||||
return true; // Yes, continue
|
||||
}
|
||||
};
|
||||
|
||||
return $callBack;
|
||||
}
|
||||
}
|
||||
}
|
97
include/condorcet/src/Console/CondorcetApplication.php
Normal file
97
include/condorcet/src/Console/CondorcetApplication.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?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\Console;
|
||||
|
||||
use Symfony\Component\Console\Application as SymfonyConsoleApplication;
|
||||
use CondorcetPHP\Condorcet\Condorcet;
|
||||
use CondorcetPHP\Condorcet\Console\Commands\ElectionCommand;
|
||||
use CondorcetPHP\Condorcet\Throwable\Internal\NoGitShellException;
|
||||
|
||||
abstract class CondorcetApplication
|
||||
{
|
||||
public static SymfonyConsoleApplication $SymfonyConsoleApplication;
|
||||
|
||||
/**
|
||||
* @infection-ignore-all
|
||||
*/
|
||||
public static function run(): void
|
||||
{
|
||||
// Run
|
||||
self::create() && self::$SymfonyConsoleApplication->run();
|
||||
}
|
||||
|
||||
public static function create(): bool
|
||||
{
|
||||
// New App
|
||||
self::$SymfonyConsoleApplication = new SymfonyConsoleApplication('Condorcet', Condorcet::getVersion());
|
||||
|
||||
// Election command
|
||||
$command = new ElectionCommand;
|
||||
self::$SymfonyConsoleApplication->add($command);
|
||||
self::$SymfonyConsoleApplication->setDefaultCommand($command->getName(), false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getVersionWithGitParsing(): string
|
||||
{
|
||||
$git = static function (string $path): string {
|
||||
if (!is_dir($path . \DIRECTORY_SEPARATOR . '.git')) {
|
||||
throw new NoGitShellException('Path is not valid');
|
||||
}
|
||||
|
||||
$process = proc_open(
|
||||
'git describe --tags --match="v[0-9]*\.[0-9]*\.[0-9]*"',
|
||||
[
|
||||
1 => ['pipe', 'w'],
|
||||
2 => ['pipe', 'w'],
|
||||
],
|
||||
$pipes,
|
||||
$path
|
||||
);
|
||||
|
||||
if (!\is_resource($process)) {
|
||||
throw new NoGitShellException;
|
||||
}
|
||||
|
||||
$result = trim(stream_get_contents($pipes[1]));
|
||||
|
||||
fclose($pipes[1]);
|
||||
fclose($pipes[2]);
|
||||
|
||||
$returnCode = proc_close($process);
|
||||
|
||||
if ($returnCode !== 0) {
|
||||
throw new NoGitShellException;
|
||||
}
|
||||
|
||||
return $result;
|
||||
};
|
||||
|
||||
$applicationOfficialVersion = Condorcet::getVersion();
|
||||
|
||||
try {
|
||||
$version = $git(__DIR__.'/../../');
|
||||
$commit = explode('-', $version)[2];
|
||||
|
||||
$match = [];
|
||||
preg_match('/^v([0-9]+\.[0-9]+\.[0-9]+)/', $version, $match);
|
||||
$gitLastestTag = $match[1];
|
||||
|
||||
$version = (version_compare($gitLastestTag, $applicationOfficialVersion, '>=')) ? $version : $applicationOfficialVersion.'-(dev)-'.$commit;
|
||||
} catch (NoGitShellException) { // Git no available, use the Condorcet Version
|
||||
$version = $applicationOfficialVersion;
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
}
|
31
include/condorcet/src/Console/Helper/CommandInputHelper.php
Normal file
31
include/condorcet/src/Console/Helper/CommandInputHelper.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?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\Console\Helper;
|
||||
|
||||
use CondorcetPHP\Condorcet\{Condorcet, Election, Result};
|
||||
|
||||
abstract class CommandInputHelper
|
||||
{
|
||||
public static function getFilePath(string $path): ?string
|
||||
{
|
||||
if (self::pathIsAbsolute($path) && is_file($path)) {
|
||||
return $path;
|
||||
} else {
|
||||
return (is_file($file = getcwd().\DIRECTORY_SEPARATOR.$path)) ? $file : null;
|
||||
}
|
||||
}
|
||||
|
||||
public static function pathIsAbsolute(string $path): bool
|
||||
{
|
||||
return empty($path) ? false : (strspn($path, '/\\', 0, 1) || (mb_strlen($path) > 3 && ctype_alpha($path[0]) && $path[1] === ':' && strspn($path, '/\\', 2, 1)));
|
||||
}
|
||||
}
|
67
include/condorcet/src/Console/Helper/FormaterHelper.php
Normal file
67
include/condorcet/src/Console/Helper/FormaterHelper.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?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\Console\Helper;
|
||||
|
||||
use CondorcetPHP\Condorcet\Console\Style\CondorcetStyle;
|
||||
use CondorcetPHP\Condorcet\{Condorcet, Result};
|
||||
|
||||
abstract class FormaterHelper
|
||||
{
|
||||
public static function formatResultTable(Result $result): array
|
||||
{
|
||||
$resultArray = $result->getResultAsArray(true);
|
||||
|
||||
foreach ($resultArray as $rank => &$line) {
|
||||
if (\is_array($line)) {
|
||||
$line = implode(',', $line);
|
||||
}
|
||||
|
||||
if ($rank === 1 && \count($result[1]) === 1 && $result[1][0] === $result->getCondorcetWinner()) {
|
||||
$line .= ' '.CondorcetStyle::CONDORCET_WINNER_SYMBOL_FORMATED;
|
||||
} elseif ($rank === max(array_keys($resultArray)) && \count($result[max(array_keys($resultArray))]) === 1 && $result[max(array_keys($resultArray))][0] === $result->getCondorcetLoser()) {
|
||||
$line .= ' '.CondorcetStyle::CONDORCET_LOSER_SYMBOL_FORMATED;
|
||||
}
|
||||
|
||||
$line = [$rank, $line];
|
||||
}
|
||||
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
public static function prepareMethods(array $methodArgument): array
|
||||
{
|
||||
if (empty($methodArgument)) {
|
||||
return [['name' => Condorcet::getDefaultMethod()::METHOD_NAME[0], 'class' => Condorcet::getDefaultMethod()]];
|
||||
} else {
|
||||
$methods = [];
|
||||
|
||||
foreach ($methodArgument as $oneMethod) {
|
||||
if (mb_strtolower($oneMethod) === 'all') {
|
||||
$methods = Condorcet::getAuthMethods(false);
|
||||
$methods = array_map(static fn ($m) => ['name' => $m, 'class' => Condorcet::getMethodClass($m)], $methods);
|
||||
break;
|
||||
}
|
||||
|
||||
if (Condorcet::isAuthMethod($oneMethod)) {
|
||||
$method_class = Condorcet::getMethodClass($oneMethod);
|
||||
$method_name = $method_class::METHOD_NAME[0];
|
||||
|
||||
if (!\in_array(needle: $method_name, haystack: $methods, strict: true)) {
|
||||
$methods[] = ['name' => $method_name, 'class' => $method_class];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $methods;
|
||||
}
|
||||
}
|
||||
}
|
163
include/condorcet/src/Console/Style/CondorcetStyle.php
Normal file
163
include/condorcet/src/Console/Style/CondorcetStyle.php
Normal file
@ -0,0 +1,163 @@
|
||||
<?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\Console\Style;
|
||||
|
||||
use CondorcetPHP\Condorcet\Console\CondorcetApplication;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Symfony\Component\Console\Helper\TableStyle;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class CondorcetStyle extends SymfonyStyle
|
||||
{
|
||||
public const CONDORCET_MAIN_COLOR = '#f57255';
|
||||
public const CONDORCET_SECONDARY_COLOR = '#8993c0';
|
||||
public const CONDORCET_THIRD_COLOR = '#e3e1e0';
|
||||
|
||||
public const CONDORCET_WINNER_SYMBOL = '★';
|
||||
public const CONDORCET_LOSER_SYMBOL = '⚐';
|
||||
public const CONDORCET_WINNER_SYMBOL_FORMATED = '<fg=#ffff00>'.self::CONDORCET_WINNER_SYMBOL.'</>';
|
||||
public const CONDORCET_LOSER_SYMBOL_FORMATED = '<fg=#33beff>'.self::CONDORCET_LOSER_SYMBOL.'</>';
|
||||
|
||||
public readonly TableStyle $MainTableStyle;
|
||||
public readonly TableStyle $FirstColumnStyle;
|
||||
|
||||
public function __construct(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
parent::__construct($input, $output);
|
||||
|
||||
$output->getFormatter()->setStyle('condor1', new OutputFormatterStyle(foreground: self::CONDORCET_MAIN_COLOR));
|
||||
$output->getFormatter()->setStyle('condor2', new OutputFormatterStyle(foreground: self::CONDORCET_SECONDARY_COLOR));
|
||||
$output->getFormatter()->setStyle('condor3', new OutputFormatterStyle(foreground: self::CONDORCET_THIRD_COLOR));
|
||||
|
||||
$output->getFormatter()->setStyle('condor1b', new OutputFormatterStyle(foreground: self::CONDORCET_MAIN_COLOR, options: ['bold']));
|
||||
$output->getFormatter()->setStyle('condor2b', new OutputFormatterStyle(foreground: self::CONDORCET_SECONDARY_COLOR, options: ['bold']));
|
||||
$output->getFormatter()->setStyle('condor3b', new OutputFormatterStyle(foreground: self::CONDORCET_THIRD_COLOR, options: ['bold']));
|
||||
|
||||
$output->getFormatter()->setStyle('continue', new OutputFormatterStyle(foreground: '#008080'));
|
||||
$output->getFormatter()->setStyle('comment', new OutputFormatterStyle(foreground: '#fed5b7'));
|
||||
|
||||
$this->MainTableStyle = (new TableStyle)
|
||||
->setBorderFormat('<condor1>%s</>')
|
||||
->setHeaderTitleFormat('<fg='.self::CONDORCET_THIRD_COLOR.';bg='.self::CONDORCET_SECONDARY_COLOR.';options=bold> %s </>')
|
||||
->setCellHeaderFormat('<condor2>%s</>')
|
||||
->setCellRowFormat('<condor3>%s</>')
|
||||
;
|
||||
|
||||
$this->FirstColumnStyle = (new TableStyle)
|
||||
->setPadType(\STR_PAD_BOTH)
|
||||
// ->setCellRowFormat('<condor1>%s</>') # Buggy
|
||||
;
|
||||
}
|
||||
|
||||
public function logo(int $terminalSize): void
|
||||
{
|
||||
$path = __DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'Assets'.\DIRECTORY_SEPARATOR;
|
||||
|
||||
if ($terminalSize >= 125) {
|
||||
$path .= 'logo.125c.ascii';
|
||||
} elseif ($terminalSize >= 90) {
|
||||
$path .= 'logo.90c.ascii';
|
||||
} else {
|
||||
$path .= 'logo.73c.ascii';
|
||||
}
|
||||
|
||||
$this->writeln(file_get_contents($path));
|
||||
}
|
||||
|
||||
public function author(string $author): void
|
||||
{
|
||||
$this->write("<condor1b>Author:</> <condor2>{$author}</>");
|
||||
}
|
||||
|
||||
public function choiceMultiple(string $question, array $choices, mixed $default = null, bool $multi): mixed
|
||||
{
|
||||
if ($default !== null) {
|
||||
$values = array_flip($choices);
|
||||
$default = $values[$default] ?? $default;
|
||||
}
|
||||
|
||||
$questionChoice = new ChoiceQuestion($question, $choices, $default);
|
||||
$questionChoice->setMultiselect($multi);
|
||||
|
||||
return $this->askQuestion($questionChoice);
|
||||
}
|
||||
|
||||
public function homepage(string $homepage): void
|
||||
{
|
||||
$this->write("<condor1b>Homepage:</> <condor2><href={$homepage}>{$homepage}</></>");
|
||||
}
|
||||
|
||||
public function inlineSeparator(): void
|
||||
{
|
||||
$this->write('<condor3> || </>');
|
||||
}
|
||||
|
||||
public function instruction(string $prefix, string $message): void
|
||||
{
|
||||
$this->writeln("<condor1b>{$prefix}:</> <condor2>{$message}</>");
|
||||
}
|
||||
|
||||
public function section(string $message): void
|
||||
{
|
||||
$this->block(
|
||||
messages: $message,
|
||||
type: null,
|
||||
style: 'fg='.self::CONDORCET_MAIN_COLOR.';bg='.self::CONDORCET_SECONDARY_COLOR.';options=bold',
|
||||
padding: false
|
||||
);
|
||||
}
|
||||
|
||||
public function methodResultSection(string $message): void
|
||||
{
|
||||
$messageLength = mb_strlen($message) + 4;
|
||||
$prefixLength = 15;
|
||||
$totalLength = $messageLength + $prefixLength;
|
||||
$totalBorderLength = $totalLength + 4;
|
||||
|
||||
$horizontalBorder = '<condor2>'.str_repeat('=', (int) $totalBorderLength).'</>';
|
||||
$vbs = '<condor2>|</>';
|
||||
|
||||
$spaceMessage = '<bg='.self::CONDORCET_MAIN_COLOR.'>'.str_repeat(' ', $messageLength).'</>';
|
||||
$spacePrefix = '<bg='.self::CONDORCET_SECONDARY_COLOR.'>'.str_repeat(' ', $prefixLength).'</>';
|
||||
$bande = "{$vbs} ".$spacePrefix.$spaceMessage." {$vbs}";
|
||||
|
||||
$this->writeln($horizontalBorder);
|
||||
$this->writeln($bande);
|
||||
$this->writeln("{$vbs} <bg=".self::CONDORCET_SECONDARY_COLOR.';options=bold> Vote Method </><bg='.self::CONDORCET_MAIN_COLOR.";options=bold> {$message} </> {$vbs}");
|
||||
$this->writeln($bande);
|
||||
$this->writeln($horizontalBorder);
|
||||
}
|
||||
|
||||
public function note(string|array $message): void
|
||||
{
|
||||
$this->block(messages: $message, style: 'fg=gray');
|
||||
}
|
||||
|
||||
public function success(string|array $message): void
|
||||
{
|
||||
$this->block(
|
||||
messages: $message,
|
||||
type: 'OK',
|
||||
style: 'fg='.self::CONDORCET_MAIN_COLOR.';bg='.self::CONDORCET_SECONDARY_COLOR.';options=bold',
|
||||
padding: true
|
||||
);
|
||||
}
|
||||
|
||||
public function version(): void
|
||||
{
|
||||
$version = CondorcetApplication::getVersionWithGitParsing();
|
||||
$this->write("<condor1b>Version:</> <condor2>{$version}</>");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user