Explorar o código

WIP: LoaderFactory complete with coverage.

Frederic G. MARAND %!s(int64=7) %!d(string=hai) anos
pai
achega
3ea825e62d

+ 1 - 0
.gitignore

@@ -1,3 +1,4 @@
 /.idea/
 /vendor/
 /composer.lock
+/coverage/

+ 25 - 0
ComposerCheck/DrushReporter.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Fgm\ComposerCheck;
+
+
+use Symfony\Component\Yaml\Yaml;
+
+class DrushReporter extends ReporterBase {
+  public function report(MergeBox $mergeBox) {
+    $header = ['Name', 'Kind', 'Requirement', 'Version'];
+    $rows = [$header];
+    foreach ($mergeBox->getRows() as $kind => $kindPackages) {
+      foreach ($kindPackages as $package => $info) {
+        $rows["$package/$kind"] = [
+          $package,
+          $kind,
+          $info['requirement'] ?? '',
+          $info['version'] ?? '',
+        ];
+      }
+    }
+    ksort($rows);
+    drush_print_table($rows, FALSE);
+  }
+}

+ 80 - 0
ComposerCheck/LoaderFactory.php

@@ -0,0 +1,80 @@
+<?php
+
+namespace Fgm\ComposerCheck;
+
+class LoaderFactory {
+
+  protected $all;
+
+  /**
+   * The absolute path to the directory holding the composer.(json|lock) pair.
+   *
+   * @var string
+   */
+  protected $directory;
+
+  /**
+   * LoaderFactory constructor.
+   *
+   * @param string $directory
+   *   The path of a directory holding a composer.(json|lock) file pair.
+   * @param bool $all
+   *   Include all entries, or just the required ones ?
+   */
+  public function __construct(string $directory, bool $all = FALSE) {
+    $this->all = $all;
+    $validatedDirectory = self::validateDirectory($directory);
+    if (empty($validatedDirectory)) {
+      throw new \InvalidArgumentException("Unusable directory: $directory");
+    }
+    $this->directory = $validatedDirectory;
+  }
+
+  /**
+   * Build the absolute path to the directory holding the file pair.
+   *
+   * @param string $directory
+   *   A directory path to validate.
+   *
+   * @return false|string
+   *   The absolute path to the directory, or FALSE if it does not contain
+   *   a readable file pair.
+   */
+  public static function validateDirectory(string $directory) {
+    $path = realpath($directory);
+    // PHP is_dir() follows symlinks to report on the link target.
+    if (empty($path) || !is_dir($path) || !is_readable($path)) {
+      return FALSE;
+    }
+
+    foreach (['json', 'lock'] as $kind) {
+      $filePath = realpath("${path}/composer.${kind}");
+      if (empty($filePath) || !is_file($filePath) || !is_readable($filePath)) {
+        return FALSE;
+      }
+    }
+
+    return $path;
+  }
+
+  /**
+   * The actual factory method.
+   *
+   * @param string $type
+   *   The kind of loader to instantiate: "lock" or "requirements".
+   *
+   * @return \Fgm\ComposerCheck\LoaderInterface
+   *   A loader instance.
+   */
+  public function createLoader(string $type) {
+    $className = __NAMESPACE__ . '\\' . ucfirst($type) . 'Loader';
+    if (!class_exists($className)) {
+      throw new \InvalidArgumentException("Unknown loader type: $type.");
+    }
+
+    /** @var \Fgm\ComposerCheck\LoaderInterface $loader */
+    $loader = new $className($this->directory);
+    return $loader;
+  }
+
+}

+ 12 - 0
ComposerCheck/LoaderInterface.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace Fgm\ComposerCheck;
+
+
+interface LoaderInterface {
+
+  public function __construct(string $directory);
+
+  public function load();
+
+}

+ 33 - 0
ComposerCheck/LockLoader.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace Fgm\ComposerCheck;
+
+
+class LockLoader implements LoaderInterface {
+
+  protected $file;
+
+  protected $platform;
+
+  public function __construct(string $directory) {
+    $this->file = realpath("${directory}/composer.lock");
+  }
+
+  public function load() {
+    $lockFile = json_decode(file_get_contents($lockPath), TRUE);
+    $lockPackages = $lockFile['packages'];
+    $lockDevPackages = $lockFile['packages-dev'];
+
+    $lockPlatform = $lockFile['platform'];
+    array_walk($lockPlatform, function (&$requirement, $component) {
+      $requirement = [
+        'name' => $component,
+        'version' => $requirement,
+      ];
+    });
+    $lockPackages = array_merge($lockPackages, $lockPlatform);
+    $lockDevPackages = array_merge($lockDevPackages, $lockPlatform);
+
+  }
+
+}

+ 50 - 0
ComposerCheck/MergeBox.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace Fgm\ComposerCheck;
+
+
+class MergeBox {
+  protected $packages;
+
+  public function __construct(RequirementsLoader $requirementsLoader, LockLoader $lockLoader) {
+  }
+
+  public function merge() {
+    $packages = ['dev' => [], 'run' => []];
+    foreach ($jsonPackages as $package => $requirement) {
+      if ($all || !empty($requirement)) {
+        $package = strtolower($package);
+        $packages['run'][$package]['requirement'] = $requirement;
+      }
+    }
+    foreach ($jsonDevPackages as $package => $requirement) {
+      if ($all || !empty($requirement)) {
+        $package = strtolower($package);
+        $packages['dev'][$package]['requirement'] = $requirement;
+      }
+    }
+    foreach ($lockPackages as $packageInfo) {
+      $package = strtolower($packageInfo['name']);
+      if ($all || !empty($packages['run'][$package])) {
+        $version = $packageInfo['version'];
+        $packages['run'][$package]['version'] = $version;
+      }
+    }
+    foreach ($lockDevPackages as $packageInfo) {
+      $package = strtolower($packageInfo['name']);
+      if ($all || !empty($packages['dev'][$package])) {
+        $version = $packageInfo['version'];
+        $packages['dev'][$package]['version'] = $version;
+      }
+    }
+    ksort($packages['dev']);
+    ksort($packages['run']);
+  }
+
+  /**
+   * @return array
+   */
+  public function getRows() {
+
+  }
+}

+ 8 - 0
ComposerCheck/ReporterBase.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace Fgm\ComposerCheck;
+
+
+abstract class ReporterBase implements ReporterInterface {
+  public abstract function report(MergeBox $mergeBox);
+}

+ 8 - 0
ComposerCheck/ReporterInterface.php

@@ -0,0 +1,8 @@
+<?php
+
+namespace Fgm\ComposerCheck;
+
+
+interface ReporterInterface {
+  public function report(MergeBox $mergeBox);
+}

+ 17 - 0
ComposerCheck/RequirementsLoader.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Fgm\ComposerCheck;
+
+
+class RequirementsLoader implements LoaderInterface {
+
+  public function __construct(string $directory) {
+    $this->file = realpath("${directory}/composer.json");
+  }
+
+  public function load() {
+    $json = json_decode(file_get_contents($jsonPath), TRUE);
+    $jsonPackages = $json['require'] ?? [];
+    $jsonDevPackages = $json['require-dev'] ?? [];
+  }
+}

+ 58 - 0
ComposerCheck/Tests/LoaderFactoryTest.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace Fgm\ComposerCheck\Tests;
+
+use Fgm\ComposerCheck\LoaderFactory;
+
+/**
+ * Class LoaderFactoryTest.
+ *
+ * @coversDefaultClass \Fgm\ComposerCheck\LoaderFactory
+ */
+class LoaderFactoryTest extends \PHPUnit_Framework_TestCase {
+  const NS = 'Fgm\\ComposerCheck\\';
+
+  /**
+   */
+  public function testHappyConstructor() {
+    // This is a Composer project, so it should be installed to run its tests.
+    $dir =  __DIR__ . '/../..';
+    $factory = new LoaderFactory($dir);
+    $this->assertInstanceOf(self::NS . 'LoaderFactory', $factory);
+  }
+
+  /**
+   * @expectedException \InvalidArgumentException
+   */
+  public function testSadConstructorMissing() {
+    $dir =  __DIR__;
+    new LoaderFactory($dir);
+  }
+
+  /**
+   * @expectedException \InvalidArgumentException
+   */
+  public function testSadConstructorInvalid() {
+    $dir =  '/dev/null/nowhere';
+    new LoaderFactory($dir);
+  }
+
+  public function testHappyCreateLoader() {
+    $dir = __DIR__ . '/../..';
+    $factory = new LoaderFactory($dir);
+    $loader = $factory->createLoader('requirements');
+    $this->assertInstanceOf(self::NS . 'RequirementsLoader', $loader);
+
+    $loader = $factory->createLoader('lock');
+    $this->assertInstanceOf(self::NS . 'LockLoader', $loader);
+  }
+
+  /**
+   * @expectedException \InvalidArgumentException
+   */
+  public function testSadCreateLoader() {
+    $dir = __DIR__ . '/../..';
+    $factory = new LoaderFactory($dir);
+    $factory->createLoader('invalid');
+  }
+}

+ 13 - 0
ComposerCheck/YamlReporter.php

@@ -0,0 +1,13 @@
+<?php
+
+namespace Fgm\ComposerCheck;
+
+
+use Symfony\Component\Yaml\Yaml;
+
+class YamlReporter extends ReporterBase {
+  public function report(MergeBox $mergeBox) {
+    echo Yaml::dump($mergeBox->getRows(), 3);
+
+  }
+}

+ 14 - 6
composer.json

@@ -1,16 +1,24 @@
 {
-  "name": "fgm/composer-check",
-  "type": "drupal-drush",
+  "autoload": {
+    "psr-4": {
+      "Fgm\\ComposerCheck\\": "ComposerCheck/"
+    }
+  },
   "description": "A Drush plugin to check installed versus requested versions of vendor packages in Drupal 8 projects.",
+  "homepage": "http://code.osinet.fr/fgm/drush_plugin__composer_check",
   "keywords": ["composer", "drupal", "drush", "update", "versions"],
   "license": ["GPL-2.0+", "CECILL-2.1+"],
-  "homepage": "http://code.osinet.fr/fgm/drush_plugin__composer_check",
   "minimum-stability": "dev",
+  "name": "fgm/composer-check",
+  "require": {
+    "symfony/yaml": "^2.8"
+  },
+  "require-dev": {
+    "phpunit/phpunit": "^5.7"
+  },
+  "type": "drupal-drush",
   "support": {
     "issues": "http://code.osinet.fr/fgm/drush_plugin__composer_check/issues",
     "source": "http://code.osinet.fr/fgm/drush_plugin__composer_check.git"
-  },
-  "require": {
-    "symfony/yaml": "^2.8"
   }
 }

+ 13 - 89
composer_check.drush.inc

@@ -6,6 +6,9 @@
  */
 
 use Drupal\Component\Utility\Unicode;
+use Fgm\ComposerCheck\DrushReporter;
+use Fgm\ComposerCheck\LoaderFactory;
+use Fgm\ComposerCheck\YamlReporter;
 use Symfony\Component\Yaml\Yaml;
 
 /**
@@ -33,98 +36,19 @@ function composer_check_drush_command() {
 /**
  * Command callback for composer-check.
  *
- * @param null|string $lockPath
- *   Optional. The path to a composer.lock file.
+ * @param null|string $path
+ *   Optional. The path to a directory holding a composer.[json|lock] file pair.
  */
-function drush_composer_check($lockPath = NULL) {
-  if (empty($lockPath)) {
-    $lockPath = DRUPAL_ROOT . '/composer.lock';
-  }
-  if (!is_file($lockPath) && is_readable($lockPath)) {
-    drush_die("Cannot read lock file");
-  }
-
-  $jsonPath = dirname($lockPath) . '/composer.json';
-  if (!is_file($jsonPath) && is_readable($jsonPath)) {
-    drush_die("Cannot read json file");
-  }
-
-  $json = json_decode(file_get_contents($jsonPath), TRUE);
-  $jsonPackages = $json['require'] ?? [];
-  $jsonDevPackages = $json['require-dev'] ?? [];
-
-  $lockFile = json_decode(file_get_contents($lockPath), TRUE);
-  $lockPackages = $lockFile['packages'];
-  $lockDevPackages = $lockFile['packages-dev'];
-
-  $lockPlatform = $lockFile['platform'];
-  array_walk($lockPlatform, function (&$requirement, $component) {
-    $requirement = [
-      'name' => $component,
-      'version' => $requirement,
-    ];
-  });
-  $lockPackages = array_merge($lockPackages, $lockPlatform);
-  $lockDevPackages = array_merge($lockDevPackages, $lockPlatform);
-
+function drush_composer_check($path = DRUPAL_ROOT) {
   $all = !!drush_get_option('all');
-  $packages = ['dev' => [], 'run' => []];
-  foreach ($jsonPackages as $package => $requirement) {
-    if ($all || !empty($requirement)) {
-      $package = Unicode::strtolower($package);
-      $packages['run'][$package]['requirement'] = $requirement;
-    }
-  }
-  foreach ($jsonDevPackages as $package => $requirement) {
-    if ($all || !empty($requirement)) {
-      $package = Unicode::strtolower($package);
-      $packages['dev'][$package]['requirement'] = $requirement;
-    }
-  }
-  foreach ($lockPackages as $packageInfo) {
-    $package = Unicode::strtolower($packageInfo['name']);
-    if ($all || !empty($packages['run'][$package])) {
-      $version = $packageInfo['version'];
-      $packages['run'][$package]['version'] = $version;
-    }
-  }
-  foreach ($lockDevPackages as $packageInfo) {
-    $package = Unicode::strtolower($packageInfo['name']);
-    if ($all || !empty($packages['dev'][$package])) {
-      $version = $packageInfo['version'];
-      $packages['dev'][$package]['version'] = $version;
-    }
-  }
-  ksort($packages['dev']);
-  ksort($packages['run']);
+  $useYaml = !!drush_get_option('yaml');
 
-  if (drush_get_option('yaml')) {
-    echo Yaml::dump($packages, 3);
-    return;
-  }
+  $factory = new LoaderFactory($path, $all);
+  $requirementsLoader = $factory->createLoader('requirements');
+  $lockLoader = $factory->createLoader('lock');
 
-  _composer_check_output_human($packages);
-}
+  $merger = new Merger($requirementsLoader, $lockLoader);
 
-/**
- * Display a package comparison as a text table.
- *
- * @param array $packages
- *   A package comparison array.
- */
-function _composer_check_output_human($packages) {
-  $header = ['Name', 'Kind', 'Requirement', 'Version'];
-  $rows = [$header];
-  foreach ($packages as $kind => $kindPackages) {
-    foreach ($kindPackages as $package => $info) {
-      $rows["$package/$kind"] = [
-        $package,
-        $kind,
-        $info['requirement'] ?? '',
-        $info['version'] ?? '',
-      ];
-    }
-  }
-  ksort($rows);
-  drush_print_table($rows, FALSE);
+  $reporter = $useYaml ? new YamlReporter() : new DrushReporter();
+  $reporter->report($merger);
 }

+ 33 - 0
phpunit.xml.dist

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
+         backupGlobals="false"
+         beStrictAboutChangesToGlobalState="true"
+         beStrictAboutCoversAnnotation="true"
+         beStrictAboutOutputDuringTests="true"
+         beStrictAboutTestsThatDoNotTestAnything="true"
+         bootstrap="vendor/autoload.php"
+         checkForUnintentionallyCoveredCode="true"
+         colors="false"
+>
+  <php>
+    <ini name="error_reporting" value="-1"/>
+  </php>
+
+  <testsuites>
+    <testsuite name="FGM Composer Check Test Suite">
+      <directory>./ComposerCheck/Tests/</directory>
+    </testsuite>
+  </testsuites>
+
+  <filter>
+    <whitelist>
+      <directory>./</directory>
+      <exclude>
+        <directory>./Tests</directory>
+        <directory>./vendor</directory>
+      </exclude>
+    </whitelist>
+  </filter>
+</phpunit>