a1c6ee4345ae397e7be3b85510445974915e29cf
[yaffs-website] / vendor / psy / psysh / src / Psy / Command / EditCommand.php
1 <?php
2
3 /*
4  * This file is part of Psy Shell.
5  *
6  * (c) 2012-2017 Justin Hileman
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Psy\Command;
13
14 use Psy\Context;
15 use Psy\ContextAware;
16 use Symfony\Component\Console\Input\InputArgument;
17 use Symfony\Component\Console\Input\InputInterface;
18 use Symfony\Component\Console\Input\InputOption;
19 use Symfony\Component\Console\Output\OutputInterface;
20
21 class EditCommand extends Command implements ContextAware
22 {
23     /**
24      * @var string
25      */
26     private $runtimeDir = '';
27
28     /**
29      * @var Context
30      */
31     private $context;
32
33     /**
34      * Constructor.
35      *
36      * @param string      $runtimeDir The directory to use for temporary files
37      * @param string|null $name       The name of the command; passing null means it must be set in configure()
38      *
39      * @throws \Symfony\Component\Console\Exception\LogicException When the command name is empty
40      */
41     public function __construct($runtimeDir, $name = null)
42     {
43         parent::__construct($name);
44
45         $this->runtimeDir = $runtimeDir;
46     }
47
48     protected function configure()
49     {
50         $this
51             ->setName('edit')
52             ->setDefinition(array(
53                 new InputArgument('file', InputArgument::OPTIONAL, 'The file to open for editing. If this is not given, edits a temporary file.', null),
54                 new InputOption(
55                     'exec',
56                     'e',
57                     InputOption::VALUE_NONE,
58                     'Execute the file content after editing. This is the default when a file name argument is not given.',
59                     null
60                 ),
61                 new InputOption(
62                     'no-exec',
63                     'E',
64                     InputOption::VALUE_NONE,
65                     'Do not execute the file content after editing. This is the default when a file name argument is given.',
66                     null
67                 ),
68             ))
69             ->setDescription('Open an external editor. Afterwards, get produced code in input buffer.')
70             ->setHelp('Set the EDITOR environment variable to something you\'d like to use.');
71     }
72
73     /**
74      * @param InputInterface  $input
75      * @param OutputInterface $output
76      *
77      * @throws \InvalidArgumentException when both exec and no-exec flags are given or if a given variable is not found in the current context
78      * @throws \UnexpectedValueException if file_get_contents on the edited file returns false instead of a string
79      */
80     protected function execute(InputInterface $input, OutputInterface $output)
81     {
82         if ($input->getOption('exec') &&
83             $input->getOption('no-exec')) {
84             throw new \InvalidArgumentException('The --exec and --no-exec flags are mutually exclusive.');
85         }
86
87         $filePath = $this->extractFilePath($input->getArgument('file'));
88
89         $execute = $this->shouldExecuteFile(
90             $input->getOption('exec'),
91             $input->getOption('no-exec'),
92             $filePath
93         );
94
95         $shouldRemoveFile = false;
96
97         if ($filePath === null) {
98             $filePath = tempnam($this->runtimeDir, 'psysh-edit-command');
99             $shouldRemoveFile = true;
100         }
101
102         $editedContent = $this->editFile($filePath, $shouldRemoveFile);
103
104         if ($execute) {
105             $this->getApplication()->addInput($editedContent);
106         }
107     }
108
109     /**
110      * @param bool        $execOption
111      * @param bool        $noExecOption
112      * @param string|null $filePath
113      *
114      * @return bool
115      */
116     private function shouldExecuteFile($execOption, $noExecOption, $filePath)
117     {
118         if ($execOption) {
119             return true;
120         }
121
122         if ($noExecOption) {
123             return false;
124         }
125
126         // By default, code that is edited is executed if there was no given input file path
127         return $filePath === null;
128     }
129
130     /**
131      * @param string|null $fileArgument
132      *
133      * @return string|null The file path to edit, null if the input was null, or the value of the referenced variable
134      *
135      * @throws \InvalidArgumentException If the variable is not found in the current context
136      */
137     private function extractFilePath($fileArgument)
138     {
139         // If the file argument was a variable, get it from the context
140         if ($fileArgument !== null &&
141             strlen($fileArgument) > 0 &&
142             $fileArgument[0] === '$') {
143             $fileArgument = $this->context->get(preg_replace('/^\$/', '', $fileArgument));
144         }
145
146         return $fileArgument;
147     }
148
149     /**
150      * @param string $filePath
151      * @param string $shouldRemoveFile
152      *
153      * @return string
154      *
155      * @throws \UnexpectedValueException if file_get_contents on $filePath returns false instead of a string
156      */
157     private function editFile($filePath, $shouldRemoveFile)
158     {
159         $escapedFilePath = escapeshellarg($filePath);
160
161         $pipes = array();
162         $proc = proc_open((getenv('EDITOR') ?: 'nano') . " {$escapedFilePath}", array(STDIN, STDOUT, STDERR), $pipes);
163         proc_close($proc);
164
165         $editedContent = @file_get_contents($filePath);
166
167         if ($shouldRemoveFile) {
168             @unlink($filePath);
169         }
170
171         if ($editedContent === false) {
172             throw new \UnexpectedValueException("Reading {$filePath} returned false");
173         }
174
175         return $editedContent;
176     }
177
178     /**
179      * Set the Context reference.
180      *
181      * @param Context $context
182      */
183     public function setContext(Context $context)
184     {
185         $this->context = $context;
186     }
187 }