<commit_before><?php

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
namespace CloudObjects\PhpMAE\Commands;

use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;
use CloudObjects\SDK\COIDParser;
use CloudObjects\PhpMAE\CredentialManager, CloudObjects\PhpMAE\ClassValidator;
use CloudObjects\PhpMAE\Exceptions\PhpMAEException;

class ClassCreateCommand extends Command {

    protected function configure() {
      $this->setName('class:create')
        ->setDescription('Create a new class for the phpMAE.')
        ->addArgument('coid', InputArgument::REQUIRED, 'The COID of the object.')
        ->addOption('http-invokable', 'hi', InputOption::VALUE_OPTIONAL, 'Makes the class HTTP-invokable.', false)
        ->addOption('force', 'f', InputOption::VALUE_OPTIONAL, 'Forces new object creation and replaces existing files.', false)
        ->addOption('confjob', null, InputOption::VALUE_OPTIONAL, 'Calls "cloudobjects" to create a configuration job for the new class.', false)
        ->addOption('autowire', null, InputOption::VALUE_OPTIONAL, 'Creates a constructor that autowires a PHP dependency.', null);
    }

    protected function execute(InputInterface $input, OutputInterface $output) {
      if (!CredentialManager::isConfigured())
        throw new \Exception("The 'cloudobjects' CLI tool must be installed and authorized.");

      $coid = COIDParser::fromString($input->getArgument('coid'));

      if (COIDParser::getType($coid)!=COIDParser::COID_VERSIONED
          && COIDParser::getType($coid)!=COIDParser::COID_UNVERSIONED)
        throw new \Exception("Invalid COID: ".(string)$coid);

      $name = COIDParser::getName($coid);
      $version = COIDParser::getVersion($coid);
      $fullName = isset($version) ? $name.".".$version : $name;
      $invokable = ($input->getOption('http-invokable') !== false);

      if (!file_exists($fullName.'.xml') || $input->getOption('force') !== false) {
        // Create RDF configuration file
        $content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
          . "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n"
          . "   xmlns:co=\"coid://cloudobjects.io/\"\n"
          . "   xmlns:phpmae=\"coid://phpmae.cloudobjects.io/\">\n"
          . "\n"
          . " <phpmae:".($invokable ? "HTTPInvokable" : "")."Class rdf:about=\"".(string)$coid."\">\n"
          . "  <co:isVisibleTo rdf:resource=\"coid://cloudobjects.io/Vendor\" />\n"
          . " </phpmae:".($invokable ? "HTTPInvokable" : "")."Class>\n"
          . "</rdf:RDF>";
        file_put_contents($fullName.'.xml', $content);
        $output->writeln("Written ".$fullName.".xml.");
      } else {
        // RDF configuration file already exists
        $output->writeln($fullName.".xml already exists.");
      }

      if (!file_exists($fullName.'.php') || $input->getOption('force') !== false) {
        $useStatements = [];
        $classVariables = [];
        $constructor = '';

        if ($input->getOption('autowire') != null) {
          $fullyQualifiedClassname = $input->getOption('autowire');
          $validator = new ClassValidator;
          if (!$validator->isWhitelisted($fullyQualifiedClassname))
            throw new PhpMAEException('Cannot autowire non-whitelisted type <'.$fullyQualifiedClassname.'>.');
          $useStatements[] = $fullyQualifiedClassname;
          $className = substr($fullyQualifiedClassname, strrpos($fullyQualifiedClassname, '\\') + 1);
          $variableName = strtolower($className[0]).substr($className, 1);
          $classVariables[] = $variableName;
          $constructor = "    public function __construct(".$className." \$".$variableName.") {\n"
              . "        \$this->".$variableName." = \$".$variableName.";\n"
              . "    }\n\n";
        }

        // Create PHP source file
        $content = "<?php\n"
          . "\n";
        foreach ($useStatements as $u)
          $content .= "use ".$u.";\n";
        if (count($useStatements) > 0)
          $content .= "\n";

        $content .=
            "/**\n"
          . " * Implementation for ".(string)$coid."\n"
          . " */\n"
          . "class ".$name." {\n"
          . "\n";
        foreach ($classVariables as $v)
            $content .= "    private \$".$v.";\n";
        if (count($classVariables) > 0)
          $content .= "\n";
        
        $content .=
            $constructor
          . ($invokable
            ? "    public function __invoke(\$args) {\n        // Add your code here\n    }\n"
            : "    // Add methods here ...\n")
          . "\n"
          . "}";
        file_put_contents($fullName.'.php', $content);
        $output->writeln("Written ".$fullName.".php.");
      } else {
        $output->writeln($fullName.".php already exists.");
      }

      if ($input->getOption('confjob') !== false) {
        $output->writeln("Calling cloudobjects ...");
        passthru("cloudobjects configuration-job:create ".$fullName.".xml");
      }
    }

}<commit_msg>Create phpMAE classes based on phpMAE interfaces using CLI<commit_after><?php

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
namespace CloudObjects\PhpMAE\Commands;

use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;
use CloudObjects\SDK\COIDParser;
use CloudObjects\PhpMAE\CredentialManager, CloudObjects\PhpMAE\ClassValidator;
use CloudObjects\PhpMAE\Exceptions\PhpMAEException;

class ClassCreateCommand extends Command {

    protected function configure() {
      $this->setName('class:create')
        ->setDescription('Create a new class for the phpMAE.')
        ->addArgument('coid', InputArgument::REQUIRED, 'The COID of the object.')
        ->addOption('http-invokable', 'hi', InputOption::VALUE_OPTIONAL, 'Makes the class HTTP-invokable.', false)
        ->addOption('force', 'f', InputOption::VALUE_OPTIONAL, 'Forces new object creation and replaces existing files.', false)
        ->addOption('confjob', null, InputOption::VALUE_OPTIONAL, 'Calls "cloudobjects" to create a configuration job for the new class.', false)
        ->addOption('autowire', null, InputOption::VALUE_OPTIONAL, 'Creates a constructor that autowires a PHP dependency.', null)
        ->addOption('implements', null, InputOption::VALUE_OPTIONAL, 'Make this class an implementation of the phpMAE interface with the specified COID.', null);
    }

    private function getAndValidateInterfaceConfig($implements) {
      // Check interface COID
      $interfaceCoid = COIDParser::fromString($implements);
      if (COIDParser::getType($interfaceCoid)!=COIDParser::COID_VERSIONED
          && COIDParser::getType($interfaceCoid)!=COIDParser::COID_UNVERSIONED)
        throw new \Exception("Invalid Interface COID: ".(string)$interfaceCoid);
      
      // Retrieve interface configuration
      $implements = (string)$interfaceCoid;
      $interfaceConfig = shell_exec("cloudobjects get ".$implements);
      if (!isset($interfaceConfig))
        throw new \Exception("Could not retrieve interface configuration.");

      // Parse and validate interface configuration
      $parser = \ARC2::getRDFXMLParser();
      $parser->parse('', $interfaceConfig);
      $index = $parser->getSimpleIndex(false);
      if (!isset($index) || !isset($index[$implements])
          || !isset($index[$implements]['http://www.w3.org/1999/02/22-rdf-syntax-ns#type']))
        throw new \Exception("<".$implements."> is not a valid CloudObjects object.");
      
      $isInterface = false;
      foreach ($index[$implements]['http://www.w3.org/1999/02/22-rdf-syntax-ns#type'] as $property => $values) {
        if ($values['value'] == 'coid://phpmae.cloudobjects.io/Interface')
          $isInterface = true;
      }

      if (!$isInterface || !isset($index[$implements]['coid://phpmae.cloudobjects.io/hasDefinitionFile']))
        throw new \Exception("<".$implements."> is not a valid phpMAE interface.");
            
      return [
        'classname' => COIDParser::getName($interfaceCoid),
        'filename' => basename($index[$implements]['coid://phpmae.cloudobjects.io/hasDefinitionFile']
        [0]['value'])
      ];
    }

    private function getAndParseInterfaceCode($interface, $filename) {
      // Retrieve interface code
      $interfaceCode = shell_exec("cloudobjects attachment:get ".$interface
        ." ".$filename);
      
      // Run through validator
      $validator = new ClassValidator;
      $validator->validateInterface($interfaceCode);

      // Find use statements
      $matches = [];
      preg_match_all("/use\s+(.+);/", $interfaceCode, $matches);
      $use = $matches[1];

      // Parse method definitions
      // (this is the same algorithm as the DirectoryTemplateVariableGenerator,
      // but less filtering on comment string)
      $matches = [];
      preg_match_all("/(?:\/\*\*((?:[\s\S](?!\/\*))*?)\*\/+\s*)?public\s+function\s+(\w+)\s*\((.+)\)/",
          $interfaceCode, $matches);

      // The following groups are captured through RegExes:
      // 0 - complete definition block
      // 1 - comment string
      // 2 - method name
      // 3 - method parameters

      $methods = [];
      for ($i = 0; $i < count($matches[0]); $i++) {
          // List methods with parameters and comment
          $methods[] = [
              'name' => $matches[2][$i],
              'params' => trim($matches[3][$i]),
              'comment' => trim($matches[1][$i])
           ];
      }

      return [
        'methods' => $methods,
        'use' => $use
      ];
    }

    protected function execute(InputInterface $input, OutputInterface $output) {
      if (!CredentialManager::isConfigured())
        throw new \Exception("The 'cloudobjects' CLI tool must be installed and authorized.");

      $coid = COIDParser::fromString($input->getArgument('coid'));

      if (COIDParser::getType($coid)!=COIDParser::COID_VERSIONED
          && COIDParser::getType($coid)!=COIDParser::COID_UNVERSIONED)
        throw new \Exception("Invalid COID: ".(string)$coid);

      $name = COIDParser::getName($coid);
      $version = COIDParser::getVersion($coid);
      $fullName = isset($version) ? $name.".".$version : $name;
      $invokable = ($input->getOption('http-invokable') !== false);

      if ($input->getOption('implements') != null) {
        if ($invokable)
          throw new \Exception("The 'implements' option cannot be used with 'http-invokable'.");

        $implements = $input->getOption('implements');
        $output->writeln("Fetching configuration for ".$implements." ...");
        $interfaceData = $this->getAndValidateInterfaceConfig($implements);
      }

      if (!file_exists($fullName.'.xml') || $input->getOption('force') !== false) {
        // Create RDF configuration file
        $content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
          . "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n"
          . "   xmlns:co=\"coid://cloudobjects.io/\"\n"
          . "   xmlns:phpmae=\"coid://phpmae.cloudobjects.io/\">\n"
          . "\n"
          . " <phpmae:".($invokable ? "HTTPInvokable" : "")."Class rdf:about=\"".(string)$coid."\">\n"
          . "  <co:isVisibleTo rdf:resource=\"coid://cloudobjects.io/Vendor\" />\n";
        if (isset($implements))
          $content .= "  <rdf:type rdf:resource=\"".$implements."\" />\n";
        $content .=" </phpmae:".($invokable ? "HTTPInvokable" : "")."Class>\n"
          . "</rdf:RDF>";
        file_put_contents($fullName.'.xml', $content);
        $output->writeln("Written ".$fullName.".xml.");
      } else {
        // RDF configuration file already exists
        $output->writeln($fullName.".xml already exists.");
      }

      if (!file_exists($fullName.'.php') || $input->getOption('force') !== false) {
        $useStatements = [];
        $classVariables = [];
        $constructor = '';

        if ($input->getOption('autowire') != null) {
          $fullyQualifiedClassname = $input->getOption('autowire');
          $validator = new ClassValidator;
          if (!$validator->isWhitelisted($fullyQualifiedClassname))
            throw new PhpMAEException('Cannot autowire non-whitelisted type <'.$fullyQualifiedClassname.'>.');
          $useStatements[] = $fullyQualifiedClassname;
          $className = substr($fullyQualifiedClassname, strrpos($fullyQualifiedClassname, '\\') + 1);
          $variableName = strtolower($className[0]).substr($className, 1);
          $classVariables[] = $variableName;
          $constructor = "    public function __construct(".$className." \$".$variableName.") {\n"
              . "        \$this->".$variableName." = \$".$variableName.";\n"
              . "    }\n\n";
        }

        if (isset($implements)) {
          // Retrieve interface code
          $output->writeln("Fetching code for ".$implements." ...");
          $parsedInterface = $this->getAndParseInterfaceCode($implements, $interfaceData['filename']);
          // Add use statements
          foreach ($parsedInterface['use'] as $u)
            $useStatements[] = $u;
        }

        // Create PHP source file
        $content = "<?php\n"
          . "\n";
        foreach ($useStatements as $u)
          $content .= "use ".$u.";\n";
        if (count($useStatements) > 0)
          $content .= "\n";

        $content .=
          "/**\n"
          . " * Implementation for ".(string)$coid."\n"
          . (isset($implements) ? " * Using interface ".$implements."\n" : "")
          . " */\n"
          . "class ".$name." ".(isset($implements) ? "implements ".$interfaceData['classname']." " : "")
          . "{\n"
          . "\n";
        foreach ($classVariables as $v)
            $content .= "    private \$".$v.";\n";
        if (count($classVariables) > 0)
          $content .= "\n"
            . $constructor;
        
        if (isset($implements)) {
          // Build PHP template with interface methods
          foreach ($parsedInterface['methods'] as $m) {
            $content .= ($m['comment'] != "" ? "    /**\n     ".$m['comment']."\n     */\n" : "")
                . "    public function ".$m['name']."(".$m['params'].") {\n"
                . "        // TODO: Implement this\n"
                . "    }\n\n";
          }
        } else {
          // Build PHP template for standard or HTTP-invokable class          
          $content .= ($invokable
              ? "    public function __invoke(\$args) {\n        // TODO: Add your code here\n    }\n"
              : "    // TODO: Add methods here ...\n")
            . "\n";
        }

        $content .= "}";
        
        file_put_contents($fullName.'.php', $content);
        $output->writeln("Written ".$fullName.".php.");
      } else {
        $output->writeln($fullName.".php already exists.");
      }

      if ($input->getOption('confjob') !== false) {
        $output->writeln("Calling cloudobjects ...");
        passthru("cloudobjects configuration-job:create ".$fullName.".xml");
      }
    }

}
{'fraction_non_alphanumeric': 0.17240939254721796, 'fraction_numerical': 0.004594180704441042, 'mean_word_length': 2.528365601080594, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 2, '>': 84, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 18}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4110155', 'n_tokens_mistral': 4684, 'n_tokens_neox': 4594, 'n_words': 948}