Pārlūkot izejas kodu

Basically working standalone. Needs to handle options and arguments.

- new standalone version: bin/composer-check.php
Frederic G. MARAND 7 gadi atpakaļ
vecāks
revīzija
997e7ed9ed

+ 9 - 4
ComposerCheck/DrushReporter.php

@@ -2,11 +2,15 @@
 
 namespace Fgm\ComposerCheck;
 
-
-use Symfony\Component\Yaml\Yaml;
-
+/**
+ * Class YamlReporter formats a requirements set as a console table.
+ */
 class DrushReporter extends ReporterBase {
-  public function report(MergeBox $mergeBox) {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function report(MergeBox $mergeBox): string {
     $header = ['Name', 'Kind', 'Requirement', 'Version'];
     $rows = [$header];
     foreach ($mergeBox->getRows() as $kind => $kindPackages) {
@@ -22,4 +26,5 @@ class DrushReporter extends ReporterBase {
     ksort($rows);
     drush_print_table($rows, FALSE);
   }
+
 }

+ 17 - 0
ComposerCheck/JsonReporter.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace Fgm\ComposerCheck;
+
+/**
+ * Class YamlReporter formats a requirements set as YAML.
+ */
+class JsonReporter extends ReporterBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function report(MergeBox $mergeBox): string {
+    return json_encode($mergeBox->getRows(), JSON_PRETTY_PRINT);
+  }
+
+}

+ 4 - 6
ComposerCheck/LoaderFactory.php

@@ -2,10 +2,11 @@
 
 namespace Fgm\ComposerCheck;
 
+/**
+ * Class LoaderFactory builds Loader instances.
+ */
 class LoaderFactory {
 
-  protected $all;
-
   /**
    * The absolute path to the directory holding the composer.(json|lock) pair.
    *
@@ -18,11 +19,8 @@ class LoaderFactory {
    *
    * @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;
+  public function __construct(string $directory) {
     $validatedDirectory = self::validateDirectory($directory);
     if (empty($validatedDirectory)) {
       throw new \InvalidArgumentException("Unusable directory: $directory");

+ 28 - 1
ComposerCheck/LoaderInterface.php

@@ -2,11 +2,38 @@
 
 namespace Fgm\ComposerCheck;
 
-
+/**
+ * Interface LoaderInterface defined the Loader classes contract.
+ */
 interface LoaderInterface {
 
+  /**
+   * LoaderInterface constructor.
+   *
+   * @param string $directory
+   *   The directory from which to load a composer file.
+   */
   public function __construct(string $directory);
 
+  /**
+   * Get the dev packages array.
+   *
+   * @return mixed
+   *   An array of requirements, indexed by lower-cased requirement name.
+   */
+  public function getDev();
+
+  /**
+   * Get the run-time packages array.
+   *
+   * @return mixed
+   *   An array of requirements, indexed by lower-cased requirement name.
+   */
+  public function getRun();
+
+  /**
+   * Load the file.
+   */
   public function load();
 
 }

+ 31 - 6
ComposerCheck/LockLoader.php

@@ -2,19 +2,45 @@
 
 namespace Fgm\ComposerCheck;
 
-
+/**
+ * Class LockLoader loads a composer.lock file.
+ */
 class LockLoader implements LoaderInterface {
 
   protected $file;
 
-  protected $platform;
+  protected $platform = [];
+
+  protected $dev = [];
 
+  protected $run = [];
+
+  /**
+   * {@inheritdoc}
+   */
   public function __construct(string $directory) {
     $this->file = realpath("${directory}/composer.lock");
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getDev() {
+    return $this->dev;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRun() {
+    return $this->run;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function load() {
-    $lockFile = json_decode(file_get_contents($lockPath), TRUE);
+    $lockFile = json_decode(file_get_contents($this->file), TRUE);
     $lockPackages = $lockFile['packages'];
     $lockDevPackages = $lockFile['packages-dev'];
 
@@ -25,9 +51,8 @@ class LockLoader implements LoaderInterface {
         'version' => $requirement,
       ];
     });
-    $lockPackages = array_merge($lockPackages, $lockPlatform);
-    $lockDevPackages = array_merge($lockDevPackages, $lockPlatform);
-
+    $this->run = array_merge($lockPackages, $lockPlatform);
+    $this->dev = array_merge($lockDevPackages, $lockPlatform);
   }
 
 }

+ 55 - 11
ComposerCheck/MergeBox.php

@@ -2,49 +2,93 @@
 
 namespace Fgm\ComposerCheck;
 
-
+/**
+ * Class MergeBox merges requirements with locked versions and platform.
+ */
 class MergeBox {
+
+  /**
+   * The composer.lock loader.
+   *
+   * @var \Fgm\ComposerCheck\LockLoader
+   */
+  protected $lock;
+
+  /**
+   * The merged packages.
+   *
+   * @var array
+   */
   protected $packages;
 
-  public function __construct(RequirementsLoader $requirementsLoader, LockLoader $lockLoader) {
+  /**
+   * The composer.json loader.
+   *
+   * @var \Fgm\ComposerCheck\RequirementsLoader
+   */
+  protected $requirements;
+
+  /**
+   * MergeBox constructor.
+   *
+   * @param \Fgm\ComposerCheck\RequirementsLoader $requirementsLoader
+   *   The composer.json loader.
+   * @param \Fgm\ComposerCheck\LockLoader $lockLoader
+   *   The composer.lock loader.
+   * @param bool $all
+   *   Include all packages or only those required ?
+   */
+  public function __construct(RequirementsLoader $requirementsLoader, LockLoader $lockLoader, bool $all) {
+    $this->requirements = $requirementsLoader;
+    $this->lock = $lockLoader;
+    $this->all = $all;
   }
 
+  /**
+   * Perform reconciliation of requirements and locked packages/platform.
+   */
   public function merge() {
     $packages = ['dev' => [], 'run' => []];
-    foreach ($jsonPackages as $package => $requirement) {
-      if ($all || !empty($requirement)) {
+
+    foreach ($this->requirements->getRun() as $package => $requirement) {
+      if ($this->all || !empty($requirement)) {
         $package = strtolower($package);
         $packages['run'][$package]['requirement'] = $requirement;
       }
     }
-    foreach ($jsonDevPackages as $package => $requirement) {
-      if ($all || !empty($requirement)) {
+    foreach ($this->requirements->getDev() as $package => $requirement) {
+      if ($this->all || !empty($requirement)) {
         $package = strtolower($package);
         $packages['dev'][$package]['requirement'] = $requirement;
       }
     }
-    foreach ($lockPackages as $packageInfo) {
+    foreach ($this->lock->getRun() as $packageInfo) {
       $package = strtolower($packageInfo['name']);
-      if ($all || !empty($packages['run'][$package])) {
+      if ($this->all || !empty($packages['run'][$package])) {
         $version = $packageInfo['version'];
         $packages['run'][$package]['version'] = $version;
       }
     }
-    foreach ($lockDevPackages as $packageInfo) {
+    foreach ($this->lock->getDev() as $packageInfo) {
       $package = strtolower($packageInfo['name']);
-      if ($all || !empty($packages['dev'][$package])) {
+      if ($this->all || !empty($packages['dev'][$package])) {
         $version = $packageInfo['version'];
         $packages['dev'][$package]['version'] = $version;
       }
     }
     ksort($packages['dev']);
     ksort($packages['run']);
+    $this->packages = $packages;
   }
 
   /**
+   * Return the merge results.
+   *
    * @return array
+   *   An array of requirements and matching locked versions.
    */
   public function getRows() {
-
+    return $this->packages;
   }
+
 }

+ 15 - 2
ComposerCheck/ReporterBase.php

@@ -2,7 +2,20 @@
 
 namespace Fgm\ComposerCheck;
 
-
+/**
+ * Class ReporterBase is a base class for Reporter classes.
+ */
 abstract class ReporterBase implements ReporterInterface {
-  public abstract function report(MergeBox $mergeBox);
+
+  /**
+   * Report on a set of Composer requirements.
+   *
+   * @param \Fgm\ComposerCheck\MergeBox $mergeBox
+   *   A merge box instance containing requirements.
+   *
+   * @return string
+   *   The formatted report output.
+   */
+  public abstract function report(MergeBox $mergeBox): string;
+
 }

+ 14 - 1
ComposerCheck/ReporterInterface.php

@@ -2,7 +2,20 @@
 
 namespace Fgm\ComposerCheck;
 
-
+/**
+ * Interface ReporterInterface specifies the Reporter classes contract.
+ */
 interface ReporterInterface {
+
+  /**
+   * Format a report on a set of packages.
+   *
+   * @param \Fgm\ComposerCheck\MergeBox $mergeBox
+   *   A merge box containing reconciled requirements and locked versions.
+   *
+   * @return string
+   *   The formatted report.
+   */
   public function report(MergeBox $mergeBox);
+
 }

+ 37 - 4
ComposerCheck/RequirementsLoader.php

@@ -2,16 +2,49 @@
 
 namespace Fgm\ComposerCheck;
 
-
+/**
+ * Class RequirementsLoader loads a composer.json file.
+ */
 class RequirementsLoader implements LoaderInterface {
 
+  protected $dev = [];
+
+  protected $run = [];
+
+  /**
+   * {@inheritdoc}
+   */
   public function __construct(string $directory) {
     $this->file = realpath("${directory}/composer.json");
   }
 
+  /**
+   * Return the --dev requirements.
+   *
+   * @return array
+   *   A requirements array, keyed by requirement string.
+   */
+  public function getDev() {
+    return $this->dev;
+  }
+
+  /**
+   * Return the non- --dev requirements.
+   *
+   * @return array
+   *   A requirements array, keyed by requirement string.
+   */
+  public function getRun() {
+    return $this->run;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function load() {
-    $json = json_decode(file_get_contents($jsonPath), TRUE);
-    $jsonPackages = $json['require'] ?? [];
-    $jsonDevPackages = $json['require-dev'] ?? [];
+    $json = json_decode(file_get_contents($this->file), TRUE);
+    $this->run = $json['require'] ?? [];
+    $this->dev = $json['require-dev'] ?? [];
   }
+
 }

+ 44 - 0
ComposerCheck/Tests/JsonReporterTest.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace Fgm\ComposerCheck\Tests;
+
+use Fgm\ComposerCheck\JsonReporter;
+
+/**
+ * Test case for the JsonReporter.
+ */
+class JsonReporterTest extends ReporterTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function tearDown() {
+    $this->reporter = new JsonReporter();
+    parent::tearDown();
+  }
+
+  /**
+   * Test reporting on an empty requirements/lock list.
+   */
+  public function testEmptyReport() {
+    $this->prophecy->getRows()->willReturn([]);
+    $this->expected = '[]';
+  }
+
+  /**
+   * Test reporting on a normal non-empty list.
+   */
+  public function testNonEmptyReport() {
+    $this->prophecy->getRows()->willReturn([
+      'dev' => [],
+      'run' => [],
+    ]);
+    $this->expected = <<<JSON
+{
+    "dev": [],
+    "run": []
+}
+JSON;
+  }
+
+}

+ 27 - 3
ComposerCheck/Tests/LoaderFactoryTest.php

@@ -13,30 +13,49 @@ class LoaderFactoryTest extends \PHPUnit_Framework_TestCase {
   const NS = 'Fgm\\ComposerCheck\\';
 
   /**
+   * Test the normal construction case.
+   *
+   * @covers ::__construct()
+   * @covers ::validateDirectory()
    */
   public function testHappyConstructor() {
     // This is a Composer project, so it should be installed to run its tests.
-    $dir =  __DIR__ . '/../..';
+    $dir = __DIR__ . '/../..';
     $factory = new LoaderFactory($dir);
     $this->assertInstanceOf(self::NS . 'LoaderFactory', $factory);
   }
 
   /**
+   * Tests constructing on an existing directory with missing composer files.
+   *
+   * @covers ::__construct()
+   * @covers ::validateDirectory()
+   *
    * @expectedException \InvalidArgumentException
    */
   public function testSadConstructorMissing() {
-    $dir =  __DIR__;
+    $dir = __DIR__;
     new LoaderFactory($dir);
   }
 
   /**
+   * Tests constructing on a missing directory.
+   *
+   * @covers ::__construct()
+   * @covers ::validateDirectory()
+   *
    * @expectedException \InvalidArgumentException
    */
   public function testSadConstructorInvalid() {
-    $dir =  '/dev/null/nowhere';
+    $dir = '/dev/null/nowhere';
     new LoaderFactory($dir);
   }
 
+  /**
+   * Testes a successful load for requirements and lock.
+   *
+   * @covers ::createLoader()
+   */
   public function testHappyCreateLoader() {
     $dir = __DIR__ . '/../..';
     $factory = new LoaderFactory($dir);
@@ -48,6 +67,10 @@ class LoaderFactoryTest extends \PHPUnit_Framework_TestCase {
   }
 
   /**
+   * Tests an attempt to load an invalid type of composer file.
+   *
+   * @covers ::createLoader()
+   *
    * @expectedException \InvalidArgumentException
    */
   public function testSadCreateLoader() {
@@ -55,4 +78,5 @@ class LoaderFactoryTest extends \PHPUnit_Framework_TestCase {
     $factory = new LoaderFactory($dir);
     $factory->createLoader('invalid');
   }
+
 }

+ 31 - 0
ComposerCheck/Tests/ReporterTestBase.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace Fgm\ComposerCheck\Tests;
+
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Base class for Reporter tests.
+ */
+class ReporterTestBase extends TestCase {
+  const NS = '\\Fgm\\ComposerCheck\\';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    $this->prophecy = $this->prophesize();
+    $this->prophecy->willExtend(self::NS . 'MergeBox');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function tearDown() {
+    $mergeBox = $this->prophecy->reveal();
+
+    $actual = $this->reporter->report($mergeBox);
+    $this->assertEquals($this->expected, $actual);
+  }
+
+}

+ 44 - 0
ComposerCheck/Tests/YamlReporterTest.php

@@ -0,0 +1,44 @@
+<?php
+
+namespace Fgm\ComposerCheck\Tests;
+
+use Fgm\ComposerCheck\YamlReporter;
+
+/**
+ * Test case for the YamlReporter.
+ */
+class YamlReporterTest extends ReporterTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function tearDown() {
+    $this->reporter = new YamlReporter();
+    parent::tearDown();
+  }
+
+  /**
+   * Test reporting on an empty requirements/lock list.
+   */
+  public function testEmptyReport() {
+    $this->prophecy->getRows()->willReturn([]);
+    $this->expected = '{  }';
+  }
+
+  /**
+   * Test reporting on a normal non-empty list.
+   */
+  public function testNonEmptyReport() {
+    $this->prophecy->getRows()->willReturn([
+      'dev' => [],
+      'run' => [],
+    ]);
+
+    $this->expected = <<<YAML
+dev: {  }
+run: {  }
+
+YAML;
+  }
+
+}

+ 9 - 3
ComposerCheck/YamlReporter.php

@@ -2,12 +2,18 @@
 
 namespace Fgm\ComposerCheck;
 
-
 use Symfony\Component\Yaml\Yaml;
 
+/**
+ * Class YamlReporter formats a requirements set as YAML.
+ */
 class YamlReporter extends ReporterBase {
-  public function report(MergeBox $mergeBox) {
-    echo Yaml::dump($mergeBox->getRows(), 3);
 
+  /**
+   * {@inheritdoc}
+   */
+  public function report(MergeBox $mergeBox): string {
+    return Yaml::dump($mergeBox->getRows(), 3);
   }
+
 }

+ 23 - 0
bin/composer-check.php

@@ -0,0 +1,23 @@
+#!/usr/bin/env php
+<?php
+namespace Fgm\ComposerCheck;
+
+require_once __DIR__ . '/../vendor/autoload.php';
+
+// TODO use options.
+$all = FALSE;
+$useYaml = TRUE;
+
+$factory = new LoaderFactory(__DIR__ . '/..', $all);
+
+$requirementsLoader = $factory->createLoader('requirements');
+$requirementsLoader->load();
+
+$lockLoader = $factory->createLoader('lock');
+$lockLoader->load();
+
+$merger = new MergeBox($requirementsLoader, $lockLoader, $all);
+$merger->merge();
+
+$reporter = $useYaml ? new YamlReporter() : new JsonReporter();
+echo $reporter->report($merger);

+ 3 - 1
composer.json

@@ -4,6 +4,7 @@
       "Fgm\\ComposerCheck\\": "ComposerCheck/"
     }
   },
+  "bin": ["composer-check.php"],
   "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"],
@@ -14,7 +15,8 @@
     "symfony/yaml": "^2.8"
   },
   "require-dev": {
-    "phpunit/phpunit": "^5.7"
+    "phpunit/phpunit": "^5.7",
+    "phpspec/prophecy": "^1.6"
   },
   "type": "drupal-drush",
   "support": {

+ 4 - 5
composer_check.drush.inc

@@ -5,11 +5,10 @@
  * A Drush plugin to compare composer.json and composer.lock.
  */
 
-use Drupal\Component\Utility\Unicode;
 use Fgm\ComposerCheck\DrushReporter;
 use Fgm\ComposerCheck\LoaderFactory;
+use Fgm\ComposerCheck\MergeBox;
 use Fgm\ComposerCheck\YamlReporter;
-use Symfony\Component\Yaml\Yaml;
 
 /**
  * Implements hook_drush_command().
@@ -40,14 +39,14 @@ function composer_check_drush_command() {
  *   Optional. The path to a directory holding a composer.[json|lock] file pair.
  */
 function drush_composer_check($path = DRUPAL_ROOT) {
-  $all = !!drush_get_option('all');
-  $useYaml = !!drush_get_option('yaml');
+  $all = (bool) drush_get_option('all', FALSE);
+  $useYaml = (bool) drush_get_option('yaml', FALSE);
 
   $factory = new LoaderFactory($path, $all);
   $requirementsLoader = $factory->createLoader('requirements');
   $lockLoader = $factory->createLoader('lock');
 
-  $merger = new Merger($requirementsLoader, $lockLoader);
+  $merger = new MergeBox($requirementsLoader, $lockLoader, $all);
 
   $reporter = $useYaml ? new YamlReporter() : new DrushReporter();
   $reporter->report($merger);

+ 3 - 3
phpunit.xml.dist

@@ -4,7 +4,7 @@
          xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
          backupGlobals="false"
          beStrictAboutChangesToGlobalState="true"
-         beStrictAboutCoversAnnotation="true"
+         beStrictAboutCoversAnnotation="false"
          beStrictAboutOutputDuringTests="true"
          beStrictAboutTestsThatDoNotTestAnything="true"
          bootstrap="vendor/autoload.php"
@@ -23,9 +23,9 @@
 
   <filter>
     <whitelist>
-      <directory>./</directory>
+      <directory>./ComposerCheck</directory>
       <exclude>
-        <directory>./Tests</directory>
+        <directory>./ComposerCheck/Tests</directory>
         <directory>./vendor</directory>
       </exclude>
     </whitelist>