]> Untitled Git - dev/commitdiff
Добавлена библиотека для обработки параметров запуска cli master
authordirect <direct@1423-sleepy.empl.vvsu.ru>
Thu, 17 Apr 2025 14:06:41 +0000 (00:06 +1000)
committerdirect <direct@1423-sleepy.empl.vvsu.ru>
Thu, 17 Apr 2025 14:06:41 +0000 (00:06 +1000)
lib/ArgsOptsProc.php [new file with mode: 0644]

diff --git a/lib/ArgsOptsProc.php b/lib/ArgsOptsProc.php
new file mode 100644 (file)
index 0000000..b2a2f66
--- /dev/null
@@ -0,0 +1,626 @@
+<?php
+//V2.9b        
+define('AOP_NOTUSED', 0);
+define('AOP_NOTREQUIRED', 1);
+define('AOP_REQUIRED', 2);
+
+class ArgsOptsProcessor
+{
+    protected array $ProcessedOptions;
+    protected int $CounterOptId = 0;
+    protected array $ShortOpts = [];
+    protected array $LongOpts = [];
+    protected array $IdOpts = [];
+    protected array $DescriptionOpts = [];
+    protected array $ValueModeOpts = [];
+    protected array $DefaultValueOpts = [];
+    protected array $ConflictGroups = [];
+    protected array $IsRequiredList = [];
+    protected array $IsOneList = [];
+    protected array $UseOnlyFirstValue = [];
+    protected array $CallbackOpts = [];
+    protected array $CallbackOptsOrder = [];
+    protected string $ProgrammDescription = '';
+    protected string $HelpTextTop = '';
+    protected string $HelpTextBottom = '';
+    protected bool $UseOnlyFirstArg = false;    
+    protected $CallbackArgs;
+    protected bool $InternalHelp = false;
+    protected int $ScreenWidth;
+    protected int $ErrorExitCode = 0;
+    protected string $ErrorAddText = '';
+
+    public function HelpTopic()
+    {  
+       $WidthScreen = $this->GetScreenWidth();
+       global $argv;
+       
+       $this->PrintWithWordWrap($this->ProgrammDescription);
+       $this->PrintWithWordWrap($this->HelpTextTop);
+       $OptionIds = array_keys($this->DescriptionOpts);
+       $diff = array_intersect($this->LongOpts, $OptionIds);
+       $NoShortOption = false;
+       $NoLongOption = false;
+       if (count(array_intersect($this->ShortOpts, $OptionIds)) == 0)
+       {
+           $NoShortOption = true;
+       }
+       if (count($diff) == 0)
+       {
+           $NoLongOption = true;
+           $MaxlenParamName = 0;
+           
+       }
+       else
+       {
+           $MaxlenParamName = max(array_map('strlen', array_keys($diff))) + 2;     
+       }
+
+       $pregsize = $WidthScreen - $MaxlenParamName - (($NoShortOption)?0:4) - 6;
+       if (!$NoShortOption || !$NoLongOption)
+       {
+           echo 'options:', PHP_EOL;
+           foreach ($this->DescriptionOpts as $OptionId => $Description)
+           {
+               preg_match_all('/(.{0,'.$pregsize.'})(\ |$)/', $Description, $parts);
+
+               if ($NoShortOption)
+               {
+                   $ShortOptionText = '';
+                   $OptionDelimiter = '';              
+               }
+               else
+               {
+                   $ShortOptionText = ($this->IdOpts[$OptionId]['ShortOpts'] === null)? "  " : '-'.$this->IdOpts[$OptionId]['ShortOpts'];
+                   $OptionDelimiter = ($this->IdOpts[$OptionId]['ShortOpts'] === null || $this->IdOpts[$OptionId]['LongOpts'] === null)? '  ' : ', ';
+               }
+               if ($NoLongOption)
+               {
+                   $LongOptionText = '';
+                   $OptionDelimiter = '';
+               }
+               else
+               {
+                   $LongOptionText = (($this->IdOpts[$OptionId]['LongOpts'] === null)? str_repeat(' ', $MaxlenParamName):'--' . str_pad($this->IdOpts[$OptionId]['LongOpts'],$MaxlenParamName - 2));
+               }
+               $OptionText = "  " . $ShortOptionText . $OptionDelimiter . $LongOptionText . "    ";
+               $FF = true;
+               foreach ($parts[1] as $part)
+               {
+                   if ($part == '')
+                   {
+                       continue;
+                   }
+                   if ($FF)
+                   {
+                       echo $OptionText;
+                       $FF = false;
+                   }
+                   else
+                   {
+                       echo str_repeat(' ', strlen($OptionText));
+                   }
+                   echo $part, PHP_EOL;
+               }
+           }
+       }
+       echo PHP_EOL;
+       $this->PrintWithWordWrap($this->HelpTextBottom);
+
+    }
+
+    protected function GetIdOption(string $option): ?int
+    {
+       if ($option == '')
+       {
+           return null;
+       }
+       
+       if (isset($this->ShortOpts[$option]))
+       {
+           return $this->ShortOpts[$option];
+       }
+       if (isset($this->LongOpts[$option]))
+       {
+           return $this->LongOpts[$option];
+       }
+       
+       return null;
+    }
+    
+    protected function GetOptionById(int $IdOption): ?array
+    {
+       $OptionName = null;
+       if (isset($this->IdOpts[$IdOption]['ShortOpts']))
+       {
+           $OptionName['Short'] = $this->IdOpts[$IdOption]['ShortOpts'];           
+       }
+       if (isset($this->IdOpts[$IdOption]['LongOpts']))
+       {
+           $OptionName['Long'] = $this->IdOpts[$IdOption]['LongOpts'];     
+       }
+       return $OptionName;
+    }
+    
+    public function DeclareOption(?string $ShortOptionName, ?string $LongOptionName = null): bool
+    {
+       //контроль входных данных
+       if (!is_null($ShortOptionName) && strlen($ShortOptionName) != 1)
+       {
+           return false;
+       }
+       if (!is_null($LongOptionName) && strlen($LongOptionName) < 2)
+       {
+           return false;
+       }
+       if (isset($this->ShortOpts[$ShortOptionName]) || isset($this->LongOpts[$LongOptionName]))
+       {
+           return false;
+       }
+       if (is_null($ShortOptionName) && is_null($LongOptionName))
+       {
+           return false;
+       }
+       if (!is_null($ShortOptionName))
+       {
+           $this->ShortOpts[$ShortOptionName] = $this->CounterOptId;
+       }
+       if (!is_null($LongOptionName))
+       {
+           $this->LongOpts[$LongOptionName] = $this->CounterOptId;
+       }
+       $this->IdOpts[$this->CounterOptId]['ShortOpts'] = $ShortOptionName;
+       $this->IdOpts[$this->CounterOptId]['LongOpts'] = $LongOptionName;
+       $this->CounterOptId++;
+       return true;
+    }
+    
+    public function SetErrorExitCode(int $code): void
+    {
+       $this->ErrorExitCode = $code;
+    }
+
+    public function SetErrorAddText(string $text): void
+    {
+       $this->ErrorAddText = $text;
+    }
+    
+    public function SetProgrammDescription(string $Description): void
+    {
+       $this->ProgrammDescription = $Description;
+    }
+    
+    public function SetHelpTextTop(string $text = ''): void
+    {
+       $this->HelpTextTop = $text;
+    }
+    
+    public function SetHelpTextBottom(string $text = ''): void
+    {
+       $this->HelpTextBottom = $text;
+    }
+    
+    public function SetOptionDescription(string $Option, string $Description): bool
+    {
+       $IdOption = $this->GetIdOption($Option);
+       if (is_null($IdOption))
+       {
+           return false;
+       }
+       $this->DescriptionOpts[$IdOption] = $Description;
+       return true;
+    }
+    
+    public function SetOptionValueMode(string $Option, int $ValueMode = AOP_NOTUSED): bool
+    {
+       $IdOption = $this->GetIdOption($Option);
+       if (is_null($IdOption))
+       {
+           return false;
+       }
+       if ($ValueMode < 0 || $ValueMode > 2)
+       {
+           return false;
+       }
+       $this->ValueModeOpts[$IdOption] = $ValueMode;
+       return true;
+    }
+
+    public function SetOptionDefaultValue(string $Option, ?string $Value = null): bool
+    {
+       $IdOption = $this->GetIdOption($Option);
+       if (is_null($IdOption))
+       {
+           return false;
+       }
+       if (!isset($this->ValueModeOpts[$IdOption]))
+       {
+           $this->ValueModeOpts[$IdOption] = AOP_NOTUSED;
+       }
+       if (is_null($Value))
+       {
+           $Value = false;
+       }
+       switch ($this->ValueModeOpts[$IdOption])
+       {
+           case AOP_NOTUSED:
+               $this->DefaultValueOpts[$IdOption] = false;
+               break;
+           case AOP_NOTREQUIRED:
+               $this->DefaultValueOpts[$IdOption] = $Value;
+               break;
+           case AOP_REQUIRED:
+               if ($Value !== false) 
+               {
+                   $this->DefaultValueOpts[$IdOption] = $Value;
+               }
+               else
+               {
+                   return false;
+               }
+               break;
+       }
+       return true;
+    }
+
+    public function SetOptionUseOnlyFirstValue(string $Option, bool $val = true): bool
+    {
+       $IdOption = $this->GetIdOption($Option);
+       if (is_null($IdOption))
+       {
+           return false;
+       }
+       $this->UseOnlyFirstValue[$IdOption] = $val;
+       return true;
+    }
+    
+    public function SetOptionConfilctGroup(string $Option, string $ConflictGroup): bool
+    {
+       $IdOption = $this->GetIdOption($Option);
+       if (is_null($IdOption))
+       {
+           return false;
+       }
+       if ($ConflictGroup == '')
+       {
+           return false;
+       }
+       $this->ConflictGroups[$ConflictGroup][] = $IdOption;
+       return true;
+    }
+
+    public function SetOptionIsRequired(string $Option, string $RequiredGroup = 'default'): bool
+    {
+       $IdOption = $this->GetIdOption($Option);
+       if (is_null($IdOption))
+       {
+           return false;
+       }
+       $this->IsRequiredList[$RequiredGroup][] = $IdOption;
+       return true;
+    }
+
+    public function SetOptionIsOne(string $Option): bool
+    {
+       $IdOption = $this->GetIdOption($Option);
+       if (is_null($IdOption))
+       {
+           return false;
+       }
+       $this->IsOneList[] = $IdOption;
+       return true;
+    }
+
+    public function SetOptionCallbackHandler(string $Option, callable $CallbackHandler = null, int $Order = 255): bool
+    {
+       $IdOption = $this->GetIdOption($Option);
+       if (is_null($IdOption))
+       {
+           return false;
+       }
+       $this->CallbackOpts[$IdOption] = $CallbackHandler;
+       $this->CallbackOptsOrder[$IdOption] = $Order;
+       return true;
+    }
+    
+    public function UseInternalHelp(bool $OnlyShort = false): void
+    {
+       if (!$this->InternalHelp)
+       {
+           
+           $long = ($OnlyShort)? null: 'help';
+           $this->InternalHelp = true;
+           $this->DeclareOption('h', $long);
+           $this->SetOptionDescription('h', 'show this help topic and quit');
+           $this->SetOptionCallbackHandler('h', [$this, 'HelpTopic']);
+           $this->SetOptionUseOnlyFirstValue('h');
+       }
+    }
+    
+    public function RunProcessing(): array
+    {
+       if (isset($this->ProcessedOptions))
+       {
+           return $this->ProcessedOptions;
+       }
+       global $argc, $argv;
+       //формирование доступных опций
+       $ShortOptsList = '';
+       foreach ($this->ShortOpts as $Option => $IdOption)
+       {
+           $ShortOptsList .= $Option;
+           if (isset($this->ValueModeOpts[$IdOption]))
+           {
+               switch ($this->ValueModeOpts[$IdOption])
+               {
+                   case AOP_NOTUSED:
+                       break;
+                   case AOP_NOTREQUIRED:
+                       $ShortOptsList .= '::';
+                       break;
+                   case AOP_REQUIRED:
+                       $ShortOptsList .= ':';
+                       break;
+               }
+               
+           }
+       }
+       $LongOptsList = [];
+       foreach ($this->LongOpts as $Option => $IdOption)
+       {
+           if (isset($this->ValueModeOpts[$IdOption]))
+           {
+               switch ($this->ValueModeOpts[$IdOption])
+               {
+                   case AOP_NOTUSED:
+                       break;
+                   case AOP_NOTREQUIRED:
+                       $Option .= '::';
+                       break;
+                   case AOP_REQUIRED:
+                       $Option .= ':';
+                       break;
+               }
+           }
+           $LongOptsList[] = $Option;
+       }
+       unset($Option, $IdOption);
+       
+       //базовый парсинг
+       $ParsedOptions = getopt($ShortOptsList, $LongOptsList,$index);
+       //приведение разноимённых опций но под одним ID к сокращенной опции с объединением значений 
+       foreach ($this->IdOpts as $IdOption => $Options)
+       {
+           if (!is_null($Options['ShortOpts']) && !is_null($Options['LongOpts']))
+           {
+               if (isset($ParsedOptions[$Options['ShortOpts']]) && isset($ParsedOptions[$Options['LongOpts']]))
+               {
+                   if (!is_array($ParsedOptions[$Options['ShortOpts']]))
+                   {
+                       $TempShortValue = $ParsedOptions[$Options['ShortOpts']];
+                       $ParsedOptions[$Options['ShortOpts']] = [];
+                       $ParsedOptions[$Options['ShortOpts']][] = $TempShortValue;
+                   }
+                   
+                   if (!is_array($ParsedOptions[$Options['LongOpts']]))
+                   {
+                       $TempLongValue = $ParsedOptions[$Options['LongOpts']];
+                       $ParsedOptions[$Options['LongOpts']] = [];
+                       $ParsedOptions[$Options['LongOpts']][] = $TempLongValue;
+                   }
+                   $ParsedOptions[$Options['ShortOpts']] = array_merge($ParsedOptions[$Options['ShortOpts']], $ParsedOptions[$Options['LongOpts']]);
+                   unset($ParsedOptions[$Options['LongOpts']]);
+               }
+           }
+       }
+       //докидывание опций, которые не были указаны но объявлены как имеющие значение по умолчанию
+       foreach ($this->DefaultValueOpts as $IdOption => $Value)
+       {
+           if (!isset($ParsedOptions[$this->IdOpts[$IdOption]['ShortOpts']]) && !isset($ParsedOptions[$this->IdOpts[$IdOption]['LongOpts']]))
+           {
+               if (!is_null($this->IdOpts[$IdOption]['ShortOpts']))
+               {
+                   $ParsedOptions[$this->IdOpts[$IdOption]['ShortOpts']] = $Value;
+               }
+               elseif (!is_null($this->IdOpts[$IdOption]['LongOpts']))
+               {
+                   $ParsedOptions[$this->IdOpts[$IdOption]['LongOpts']] = $Value;          
+               }
+           }
+       }
+       
+       //докидывание аргументов, как отдельной опции
+       for ($i = $index; $i < $argc; $i++)
+       {
+           $ParsedOptions['!ARGS'][] = $argv[$i];
+       }
+       if ($this->UseOnlyFirstArg && isset($ParsedOptions['!ARGS']))
+       {
+           $ParsedOptions['!ARGS'] = $ParsedOptions['!ARGS'][0];
+       }
+       
+       //обработка опции "использовать только одно значение" гарантирует что значение опции будет использовано первое найденное и не будет массивом значений
+       foreach ($ParsedOptions as $Option => $Values)
+       {
+           $IdOption = $this->GetIdOption($Option);
+           if (isset($this->UseOnlyFirstValue[$IdOption]) && is_array($Values))
+           {
+               if ($this->UseOnlyFirstValue[$IdOption])
+               {
+                   $ParsedOptions[$Option] = $Values[0];
+               }
+           }
+       }
+
+       //проверяем используется и вызывается ли встроенный help
+       if ($this->InternalHelp &&(isset($ParsedOptions['h']) || isset($ParsedOptions['help'])))
+       {
+           $this->HelpTopic();
+           exit();         
+       }
+
+       // проверка на пропущенность обязательных параметров
+
+       foreach ($this->IsRequiredList as $RequiredGroup => $IdOptions)
+       {
+           foreach ($IdOptions as $IdOption)
+           {
+               if ($RequiredGroup == 'default' && !isset($ParsedOptions[$this->IdOpts[$IdOption]['ShortOpts']]) && !isset($ParsedOptions[$this->IdOpts[$IdOption]['LongOpts']]))
+               {
+                   $OptionById = $this->GetOptionById($IdOption);
+                   $OptPrintname = (count($OptionById) == 1)? '-' . $OptionById['Short'] : '-' . $OptionById['Short'] .', --'.  $OptionById['Long'];
+                   fwrite(STDERR, "Required parameter {$OptPrintname} is missing!".PHP_EOL);
+                   fwrite(STDERR, $this->ErrorAddText);
+                   exit($this->ErrorExitCode);
+               }
+               if ($RequiredGroup != 'default' && !isset($ParsedOptions[$this->IdOpts[$IdOption]['ShortOpts']]) && !isset($ParsedOptions[$this->IdOpts[$IdOption]['LongOpts']]))
+               {
+                   $NFF = true;
+               }
+               else
+               {
+                   $NFF = false;
+                   break;
+               }
+           }
+           if ($RequiredGroup != 'default' && $NFF)
+           {
+               $OptPrintname = '';
+               $OptPrintnameSeparator = '';
+               foreach ($IdOptions as $IdOption)
+               {
+                   $OptionById = $this->GetOptionById($IdOption);
+                   $OptPrintname .= (count($OptionById) == 1)? $OptPrintnameSeparator. '-' . $OptionById['Short'] : $OptPrintnameSeparator . '-' . $OptionById['Short'] .', --'.  $OptionById['Long'];
+                   $OptPrintnameSeparator = ' or ';
+               }
+               fwrite(STDERR, "Required parameter {$OptPrintname} is missing!".PHP_EOL);
+               fwrite(STDERR, $this->ErrorAddText);
+               exit($this->ErrorExitCode);             
+           }
+           unset($NFF);
+       }
+       
+       //проверка на всякие ограничения
+       //проверка на запрещенность нескольких значений для опции
+       foreach ($ParsedOptions as $Option => $Values)
+       {
+           $IdOption = $this->GetIdOption($Option);
+           if (array_search($IdOption, $this->IsOneList) !== false && is_array($Values))
+           {
+               $OptPrintname = ((strlen($Option) == 1)? '-' : '--').$Option;
+               fwrite(STDERR, "Parameter {$OptPrintname} must be the one!".PHP_EOL);
+               fwrite(STDERR, $this->ErrorAddText);
+               exit($this->ErrorExitCode);
+           }
+       }
+       
+       //проверка на конфликты опций
+       $ParsedOptList = array_keys($ParsedOptions);    
+       $ParsedIdOptionList = [];
+       foreach ($ParsedOptList as $Option)
+       {
+           $ParsedIdOptionList [] = $this->GetIdOption($Option);
+       }
+       foreach ($this->ConflictGroups as $ConflictGroupOpts)
+       {
+           $CollList = array_intersect($ConflictGroupOpts, $ParsedIdOptionList);
+           if (count($CollList) > 1)
+           {
+               $OptPrintname = array_merge(array_keys(array_intersect($this->ShortOpts, $CollList)),array_keys(array_intersect($this->LongOpts, $CollList)));
+               sort($OptPrintname);
+               foreach ($OptPrintname as &$Value)
+               {
+                   $Value = ((strlen($Value) == 1)? '-' : '--').$Value;
+               }
+               fwrite(STDERR, "Options " . implode(', ', $OptPrintname) . " can not be used together.".PHP_EOL);
+               fwrite(STDERR, $this->ErrorAddText);
+               exit($this->ErrorExitCode);
+           }
+       }
+       // в этом месте надо перебрать опции и вызвать обработчики
+       $CBC = [];
+       foreach ($ParsedOptions as $OptName => $Values)
+       {
+           $IdOption = $this->GetIdOption($OptName);
+           if (isset($this->CallbackOpts[$IdOption]))
+           {
+               if (is_callable($this->CallbackOpts[$IdOption], true))
+               {
+                   $CBC[$this->CallbackOptsOrder[$IdOption]][$IdOption] = $Values;
+               }
+           }
+       }
+       ksort($CBC);
+       foreach ($CBC as $IdOptions)
+       {
+           foreach ($IdOptions as $IdOption => $Values)
+           {
+               call_user_func($this->CallbackOpts[$IdOption], $Values, $ParsedOptions);
+           }
+       }
+       unset($CBC);
+       
+       //проверяем есть ли обработчик для аргументов и если есть, то выполняем
+       if (isset($this->CallbackArgs))
+       {
+           if (is_callable($this->CallbackArgs, true))
+           {
+               $ARGS = (isset($ParsedOptions['!ARGS']))? $ParsedOptions['!ARGS']: null;
+               call_user_func($this->CallbackArgs, $ARGS, $ParsedOptions);
+           }
+       }
+       //сохраняем обработанные опции
+       $this->ProcessedOptions = $ParsedOptions;
+       return $this->ProcessedOptions;
+    }
+
+    public function SetArgsCallbackHandler(callable $CallbackHandler = null): void
+    {
+       $this->CallbackArgs = $CallbackHandler;
+    }
+    
+    public function SetArgsUseOnlyFirstArg(bool $val = true): void
+    {
+       $this->UseOnlyFirstArg = $val;
+    }
+    
+    protected function PrintWithWordWrap(string $text): void
+    {
+       
+       if($text == '')
+       {
+           return;
+       }
+       $pregsize = $this->GetScreenWidth() - 2;
+       preg_match_all('/(.{0,'.$pregsize.'})(\ |$)/', $text, $parts);
+       foreach ($parts[1] as $part)
+       {
+           if ($part == '')
+           {
+               continue;
+           }
+           echo $part, PHP_EOL;
+       }       
+    }
+    
+    protected function GetScreenWidth():int
+    {
+       if (!isset($this->ScreenWidth))
+       {
+           $WidthScreen = 80;
+           $stty = (array_map('trim',explode(';', shell_exec('stty -a'))));
+           foreach ($stty as $value)
+           {
+               if (strtok($value,' =') == 'columns')
+               {
+                   $WidthScreen = (int) strtok(' =');
+                   break;
+               }
+           }
+           unset($stty, $value);
+           strtok('', '');
+           $this->ScreenWidth = $WidthScreen;
+       }
+       return $this->ScreenWidth;
+    }
+}