Subversion-Projekte lars-tiefland.laravel_shop

Revision

Zur aktuellen Revision | Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of Psy Shell.
5
 *
6
 * (c) 2012-2022 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([
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
     * @return int 0 if everything went fine, or an exit code
78
     *
79
     * @throws \InvalidArgumentException when both exec and no-exec flags are given or if a given variable is not found in the current context
80
     * @throws \UnexpectedValueException if file_get_contents on the edited file returns false instead of a string
81
     */
82
    protected function execute(InputInterface $input, OutputInterface $output)
83
    {
84
        if ($input->getOption('exec') &&
85
            $input->getOption('no-exec')) {
86
            throw new \InvalidArgumentException('The --exec and --no-exec flags are mutually exclusive');
87
        }
88
 
89
        $filePath = $this->extractFilePath($input->getArgument('file'));
90
 
91
        $execute = $this->shouldExecuteFile(
92
            $input->getOption('exec'),
93
            $input->getOption('no-exec'),
94
            $filePath
95
        );
96
 
97
        $shouldRemoveFile = false;
98
 
99
        if ($filePath === null) {
100
            $filePath = \tempnam($this->runtimeDir, 'psysh-edit-command');
101
            $shouldRemoveFile = true;
102
        }
103
 
104
        $editedContent = $this->editFile($filePath, $shouldRemoveFile);
105
 
106
        if ($execute) {
107
            $this->getApplication()->addInput($editedContent);
108
        }
109
 
110
        return 0;
111
    }
112
 
113
    /**
114
     * @param bool        $execOption
115
     * @param bool        $noExecOption
116
     * @param string|null $filePath
117
     *
118
     * @return bool
119
     */
120
    private function shouldExecuteFile(bool $execOption, bool $noExecOption, string $filePath = null): bool
121
    {
122
        if ($execOption) {
123
            return true;
124
        }
125
 
126
        if ($noExecOption) {
127
            return false;
128
        }
129
 
130
        // By default, code that is edited is executed if there was no given input file path
131
        return $filePath === null;
132
    }
133
 
134
    /**
135
     * @param string|null $fileArgument
136
     *
137
     * @return string|null The file path to edit, null if the input was null, or the value of the referenced variable
138
     *
139
     * @throws \InvalidArgumentException If the variable is not found in the current context
140
     */
141
    private function extractFilePath(string $fileArgument = null)
142
    {
143
        // If the file argument was a variable, get it from the context
144
        if ($fileArgument !== null &&
145
            $fileArgument !== '' &&
146
            $fileArgument[0] === '$') {
147
            $fileArgument = $this->context->get(\preg_replace('/^\$/', '', $fileArgument));
148
        }
149
 
150
        return $fileArgument;
151
    }
152
 
153
    /**
154
     * @param string $filePath
155
     * @param bool   $shouldRemoveFile
156
     *
157
     * @return string
158
     *
159
     * @throws \UnexpectedValueException if file_get_contents on $filePath returns false instead of a string
160
     */
161
    private function editFile(string $filePath, bool $shouldRemoveFile): string
162
    {
163
        $escapedFilePath = \escapeshellarg($filePath);
164
        $editor = (isset($_SERVER['EDITOR']) && $_SERVER['EDITOR']) ? $_SERVER['EDITOR'] : 'nano';
165
 
166
        $pipes = [];
167
        $proc = \proc_open("{$editor} {$escapedFilePath}", [\STDIN, \STDOUT, \STDERR], $pipes);
168
        \proc_close($proc);
169
 
170
        $editedContent = @\file_get_contents($filePath);
171
 
172
        if ($shouldRemoveFile) {
173
            @\unlink($filePath);
174
        }
175
 
176
        if ($editedContent === false) {
177
            throw new \UnexpectedValueException("Reading {$filePath} returned false");
178
        }
179
 
180
        return $editedContent;
181
    }
182
 
183
    /**
184
     * Set the Context reference.
185
     *
186
     * @param Context $context
187
     */
188
    public function setContext(Context $context)
189
    {
190
        $this->context = $context;
191
    }
192
}