Browse Source

Implement createFile on ConsoleIo

Having createFile() on ConsoleIo will let Commands use it. It also makes
moving shells to commands more easy.
Mark Story 8 years ago
parent
commit
c86ebc5bc7
2 changed files with 239 additions and 0 deletions
  1. 80 0
      src/Console/ConsoleIo.php
  2. 159 0
      tests/TestCase/Console/ConsoleIoTest.php

+ 80 - 0
src/Console/ConsoleIo.php

@@ -14,8 +14,11 @@
  */
 namespace Cake\Console;
 
+use Cake\Console\Exception\StopException;
 use Cake\Log\Engine\ConsoleLog;
 use Cake\Log\Log;
+use RuntimeException;
+use SplFileObject;
 
 /**
  * A wrapper around the various IO operations shell tasks need to do.
@@ -92,6 +95,13 @@ class ConsoleIo
     protected $_lastWritten = 0;
 
     /**
+     * Whether or not files should be overwritten
+     *
+     * @var bool
+     */
+    protected $forceOverwrite = false;
+
+    /**
      * Constructor
      *
      * @param \Cake\Console\ConsoleOutput|null $out A ConsoleOutput object for stdout.
@@ -508,4 +518,74 @@ class ConsoleIo
 
         return $this->_helpers->load($name, $settings);
     }
+
+    /**
+     * Create a file at the given path.
+     *
+     * This method will prompt the user if a file will be overwritten.
+     * Setting `forceOverwrite` to true will suppress this behavior
+     * and always overwrite the file.
+     *
+     * If the user replies `a` subsequent `forceOverwrite` parameters will
+     * be coerced to true and all files will be overwritten.
+     *
+     * @param string $path The path to create the file at.
+     * @param string $contents The contents to put into the file.
+     * @param bool $forceOverwrite Whether or not the file should be overwritten.
+     *   If true, no question will be asked about whether or not to overwrite existing files.
+     * @return bool Success.
+     * @throws \Cake\Console\Exception\StopException When `q` is given as an answer
+     *   to whether or not a file should be overwritten.
+     */
+    public function createFile($path, $contents, $forceOverwrite = false)
+    {
+        $path = str_replace(
+            DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR,
+            DIRECTORY_SEPARATOR,
+            $path
+        );
+
+        $this->out();
+        $forceOverwrite = $forceOverwrite || $this->forceOverwrite;
+
+        if (file_exists($path) && $forceOverwrite === false) {
+            $this->warning("File `{$path}` exists");
+            $key = $this->askChoice('Do you want to overwrite?', ['y', 'n', 'a', 'q'], 'n');
+            $key = strtolower($key);
+
+            if ($key === 'q') {
+                $this->error('Quitting.', 2);
+                throw new StopException('Not creating file. Quitting.');
+            }
+            if ($key === 'a') {
+                $this->forceOverwrite = true;
+                $key = 'y';
+            }
+            if ($key !== 'y') {
+                $this->out("Skip `{$path}`", 2);
+
+                return false;
+            }
+        } else {
+            $this->out("Creating file {$path}");
+        }
+
+        try {
+            $file = new SplFileObject($path, 'w');
+        } catch (RuntimeException $e) {
+            $this->error("Could not write to `{$path}`. Permission denied.", 2);
+
+            return false;
+        }
+
+        $file->rewind();
+        if ($file->fwrite($contents) > 0) {
+            $this->out("<success>Wrote</success> `{$path}`");
+
+            return true;
+        }
+        $this->error("Could not write to `{$path}`.", 2);
+
+        return false;
+    }
 }

+ 159 - 0
tests/TestCase/Console/ConsoleIoTest.php

@@ -15,6 +15,7 @@
 namespace Cake\Test\TestCase\Console;
 
 use Cake\Console\ConsoleIo;
+use Cake\Filesystem\Folder;
 use Cake\Log\Log;
 use Cake\TestSuite\TestCase;
 
@@ -47,6 +48,20 @@ class ConsoleIoTest extends TestCase
     }
 
     /**
+     * teardown method
+     *
+     * @return void
+     */
+    public function tearDown()
+    {
+        parent::tearDown();
+        if (is_dir(TMP . 'shell_test')) {
+            $folder = new Folder(TMP . 'shell_test');
+            $folder->delete();
+        }
+    }
+
+    /**
      * Provider for testing choice types.
      *
      * @return array
@@ -557,4 +572,148 @@ class ConsoleIoTest extends TestCase
         $this->io->{$method}('Just a test');
         $this->io->{$method}(['Just', 'a test']);
     }
+
+    /**
+     * Test that createFile
+     *
+     * @return void
+     */
+    public function testCreateFileSuccess()
+    {
+        $path = TMP . 'shell_test';
+        mkdir($path);
+
+        $file = $path . DS . 'file1.php';
+        $contents = 'some content';
+        $result = $this->io->createFile($file, $contents);
+
+        $this->assertTrue($result);
+        $this->assertFileExists($file);
+        $this->assertEquals($contents, file_get_contents($file));
+    }
+
+    /**
+     * Test that createFile with permissions error.
+     *
+     * @return void
+     */
+    public function testCreateFilePermissionsError()
+    {
+        $this->skipIf(DS === '\\', 'Cant perform operations using permissions on windows.');
+
+        $path = TMP . 'shell_test';
+        $file = $path . DS . 'no_perms';
+
+        if (!is_dir($path)) {
+            mkdir($path);
+        }
+        chmod($path, 0444);
+
+        $this->io->createFile($file, 'testing');
+        $this->assertFileNotExists($file);
+
+        chmod($path, 0744);
+        rmdir($path);
+    }
+
+    /**
+     * Test that `q` raises an error.
+     *
+     * @expectedException \Cake\Console\Exception\StopException
+     * @return void
+     */
+    public function testCreateFileOverwriteQuit()
+    {
+        $path = TMP . 'shell_test';
+        mkdir($path);
+
+        $file = $path . DS . 'file1.php';
+        touch($file);
+
+        $this->in->expects($this->once())
+            ->method('read')
+            ->will($this->returnValue('q'));
+
+        $this->io->createFile($file, 'some content');
+    }
+
+    /**
+     * Test that `n` raises an error.
+     *
+     * @return void
+     */
+    public function testCreateFileOverwriteNo()
+    {
+        $path = TMP . 'shell_test';
+        mkdir($path);
+
+        $file = $path . DS . 'file1.php';
+        file_put_contents($file, 'original');
+        touch($file);
+
+        $this->in->expects($this->once())
+            ->method('read')
+            ->will($this->returnValue('n'));
+
+        $contents = 'new content';
+        $result = $this->io->createFile($file, $contents);
+
+        $this->assertFalse($result);
+        $this->assertFileExists($file);
+        $this->assertEquals('original', file_get_contents($file));
+    }
+
+    /**
+     * Test the forceOverwrite parameter
+     *
+     * @return void
+     */
+    public function testCreateFileOverwriteParam()
+    {
+        $path = TMP . 'shell_test';
+        mkdir($path);
+
+        $file = $path . DS . 'file1.php';
+        file_put_contents($file, 'original');
+        touch($file);
+
+        $contents = 'new content';
+        $result = $this->io->createFile($file, $contents, true);
+
+        $this->assertTrue($result);
+        $this->assertFileExists($file);
+        $this->assertEquals($contents, file_get_contents($file));
+    }
+
+    /**
+     * Test the `a` response
+     *
+     * @return void
+     */
+    public function testCreateFileOverwriteAll()
+    {
+        $path = TMP . 'shell_test';
+        mkdir($path);
+
+        $file = $path . DS . 'file1.php';
+        file_put_contents($file, 'original');
+        touch($file);
+
+        $this->in->expects($this->once())
+            ->method('read')
+            ->will($this->returnValue('a'));
+
+        $this->io->createFile($file, 'new content');
+        $this->assertEquals('new content', file_get_contents($file));
+
+        $this->io->createFile($file, 'newer content');
+        $this->assertEquals('newer content', file_get_contents($file));
+
+        $this->io->createFile($file, 'newest content', false);
+        $this->assertEquals(
+            'newest content',
+            file_get_contents($file),
+            'overwrite state replaces parameter'
+        );
+    }
 }