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;
13
 
14
/**
15
 * A Psy Shell configuration path helper.
16
 */
17
class ConfigPaths
18
{
19
    private $configDir;
20
    private $dataDir;
21
    private $runtimeDir;
22
    private $env;
23
 
24
    /**
25
     * ConfigPaths constructor.
26
     *
27
     * Optionally provide `configDir`, `dataDir` and `runtimeDir` overrides.
28
     *
29
     * @see self::overrideDirs
30
     *
31
     * @param string[]     $overrides Directory overrides
32
     * @param EnvInterface $env
33
     */
34
    public function __construct(array $overrides = [], EnvInterface $env = null)
35
    {
36
        $this->overrideDirs($overrides);
37
        $this->env = $env ?: new SuperglobalsEnv();
38
    }
39
 
40
    /**
41
     * Provide `configDir`, `dataDir` and `runtimeDir` overrides.
42
     *
43
     * If a key is set but empty, the override will be removed. If it is not set
44
     * at all, any existing override will persist.
45
     *
46
     * @param string[] $overrides Directory overrides
47
     */
48
    public function overrideDirs(array $overrides)
49
    {
50
        if (\array_key_exists('configDir', $overrides)) {
51
            $this->configDir = $overrides['configDir'] ?: null;
52
        }
53
 
54
        if (\array_key_exists('dataDir', $overrides)) {
55
            $this->dataDir = $overrides['dataDir'] ?: null;
56
        }
57
 
58
        if (\array_key_exists('runtimeDir', $overrides)) {
59
            $this->runtimeDir = $overrides['runtimeDir'] ?: null;
60
        }
61
    }
62
 
63
    /**
64
     * Get the current home directory.
65
     *
66
     * @return string|null
67
     */
68
    public function homeDir()
69
    {
70
        if ($homeDir = $this->getEnv('HOME') ?: $this->windowsHomeDir()) {
71
            return \strtr($homeDir, '\\', '/');
72
        }
73
 
74
        return null;
75
    }
76
 
77
    private function windowsHomeDir()
78
    {
79
        if (\defined('PHP_WINDOWS_VERSION_MAJOR')) {
80
            $homeDrive = $this->getEnv('HOMEDRIVE');
81
            $homePath = $this->getEnv('HOMEPATH');
82
            if ($homeDrive && $homePath) {
83
                return $homeDrive.'/'.$homePath;
84
            }
85
        }
86
 
87
        return null;
88
    }
89
 
90
    private function homeConfigDir()
91
    {
92
        if ($homeConfigDir = $this->getEnv('XDG_CONFIG_HOME')) {
93
            return $homeConfigDir;
94
        }
95
 
96
        $homeDir = $this->homeDir();
97
 
98
        return $homeDir === '/' ? $homeDir.'.config' : $homeDir.'/.config';
99
    }
100
 
101
    /**
102
     * Get potential config directory paths.
103
     *
104
     * Returns `~/.psysh`, `%APPDATA%/PsySH` (when on Windows), and all
105
     * XDG Base Directory config directories:
106
     *
107
     *     http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
108
     *
109
     * @return string[]
110
     */
111
    public function configDirs(): array
112
    {
113
        if ($this->configDir !== null) {
114
            return [$this->configDir];
115
        }
116
 
117
        $configDirs = $this->getEnvArray('XDG_CONFIG_DIRS') ?: ['/etc/xdg'];
118
 
119
        return $this->allDirNames(\array_merge([$this->homeConfigDir()], $configDirs));
120
    }
121
 
122
    /**
123
     * @deprecated
124
     */
125
    public static function getConfigDirs(): array
126
    {
127
        return (new self())->configDirs();
128
    }
129
 
130
    /**
131
     * Get potential home config directory paths.
132
     *
133
     * Returns `~/.psysh`, `%APPDATA%/PsySH` (when on Windows), and the
134
     * XDG Base Directory home config directory:
135
     *
136
     *     http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
137
     *
138
     * @deprecated
139
     *
140
     * @return string[]
141
     */
142
    public static function getHomeConfigDirs(): array
143
    {
144
        // Not quite the same, but this is deprecated anyway /shrug
145
        return self::getConfigDirs();
146
    }
147
 
148
    /**
149
     * Get the current home config directory.
150
     *
151
     * Returns the highest precedence home config directory which actually
152
     * exists. If none of them exists, returns the highest precedence home
153
     * config directory (`%APPDATA%/PsySH` on Windows, `~/.config/psysh`
154
     * everywhere else).
155
     *
156
     * @see self::homeConfigDir
157
     *
158
     * @return string
159
     */
160
    public function currentConfigDir(): string
161
    {
162
        if ($this->configDir !== null) {
163
            return $this->configDir;
164
        }
165
 
166
        $configDirs = $this->allDirNames([$this->homeConfigDir()]);
167
 
168
        foreach ($configDirs as $configDir) {
169
            if (@\is_dir($configDir)) {
170
                return $configDir;
171
            }
172
        }
173
 
174
        return $configDirs[0];
175
    }
176
 
177
    /**
178
     * @deprecated
179
     */
180
    public static function getCurrentConfigDir(): string
181
    {
182
        return (new self())->currentConfigDir();
183
    }
184
 
185
    /**
186
     * Find real config files in config directories.
187
     *
188
     * @param string[] $names Config file names
189
     *
190
     * @return string[]
191
     */
192
    public function configFiles(array $names): array
193
    {
194
        return $this->allRealFiles($this->configDirs(), $names);
195
    }
196
 
197
    /**
198
     * @deprecated
199
     */
200
    public static function getConfigFiles(array $names, $configDir = null): array
201
    {
202
        return (new self(['configDir' => $configDir]))->configFiles($names);
203
    }
204
 
205
    /**
206
     * Get potential data directory paths.
207
     *
208
     * If a `dataDir` option was explicitly set, returns an array containing
209
     * just that directory.
210
     *
211
     * Otherwise, it returns `~/.psysh` and all XDG Base Directory data directories:
212
     *
213
     *     http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
214
     *
215
     * @return string[]
216
     */
217
    public function dataDirs(): array
218
    {
219
        if ($this->dataDir !== null) {
220
            return [$this->dataDir];
221
        }
222
 
223
        $homeDataDir = $this->getEnv('XDG_DATA_HOME') ?: $this->homeDir().'/.local/share';
224
        $dataDirs = $this->getEnvArray('XDG_DATA_DIRS') ?: ['/usr/local/share', '/usr/share'];
225
 
226
        return $this->allDirNames(\array_merge([$homeDataDir], $dataDirs));
227
    }
228
 
229
    /**
230
     * @deprecated
231
     */
232
    public static function getDataDirs(): array
233
    {
234
        return (new self())->dataDirs();
235
    }
236
 
237
    /**
238
     * Find real data files in config directories.
239
     *
240
     * @param string[] $names Config file names
241
     *
242
     * @return string[]
243
     */
244
    public function dataFiles(array $names): array
245
    {
246
        return $this->allRealFiles($this->dataDirs(), $names);
247
    }
248
 
249
    /**
250
     * @deprecated
251
     */
252
    public static function getDataFiles(array $names, $dataDir = null): array
253
    {
254
        return (new self(['dataDir' => $dataDir]))->dataFiles($names);
255
    }
256
 
257
    /**
258
     * Get a runtime directory.
259
     *
260
     * Defaults to `/psysh` inside the system's temp dir.
261
     *
262
     * @return string
263
     */
264
    public function runtimeDir(): string
265
    {
266
        if ($this->runtimeDir !== null) {
267
            return $this->runtimeDir;
268
        }
269
 
270
        // Fallback to a boring old folder in the system temp dir.
271
        $runtimeDir = $this->getEnv('XDG_RUNTIME_DIR') ?: \sys_get_temp_dir();
272
 
273
        return \strtr($runtimeDir, '\\', '/').'/psysh';
274
    }
275
 
276
    /**
277
     * @deprecated
278
     */
279
    public static function getRuntimeDir(): string
280
    {
281
        return (new self())->runtimeDir();
282
    }
283
 
284
    /**
285
     * Get a list of directories in PATH.
286
     *
287
     * If $PATH is unset/empty it defaults to '/usr/sbin:/usr/bin:/sbin:/bin'.
288
     *
289
     * @return string[]
290
     */
291
    public function pathDirs(): array
292
    {
293
        return $this->getEnvArray('PATH') ?: ['/usr/sbin', '/usr/bin', '/sbin', '/bin'];
294
    }
295
 
296
    /**
297
     * Locate a command (an executable) in $PATH.
298
     *
299
     * Behaves like 'command -v COMMAND' or 'which COMMAND'.
300
     * If $PATH is unset/empty it defaults to '/usr/sbin:/usr/bin:/sbin:/bin'.
301
     *
302
     * @param string $command the executable to locate
303
     *
304
     * @return string
305
     */
306
    public function which($command)
307
    {
308
        foreach ($this->pathDirs() as $path) {
309
            $fullpath = $path.\DIRECTORY_SEPARATOR.$command;
310
            if (@\is_file($fullpath) && @\is_executable($fullpath)) {
311
                return $fullpath;
312
            }
313
        }
314
 
315
        return null;
316
    }
317
 
318
    /**
319
     * Get all PsySH directory name candidates given a list of base directories.
320
     *
321
     * This expects that XDG-compatible directory paths will be passed in.
322
     * `psysh` will be added to each of $baseDirs, and we'll throw in `~/.psysh`
323
     * and a couple of Windows-friendly paths as well.
324
     *
325
     * @param string[] $baseDirs base directory paths
326
     *
327
     * @return string[]
328
     */
329
    private function allDirNames(array $baseDirs): array
330
    {
331
        $dirs = \array_map(function ($dir) {
332
            return \strtr($dir, '\\', '/').'/psysh';
333
        }, $baseDirs);
334
 
335
        // Add ~/.psysh
336
        if ($home = $this->getEnv('HOME')) {
337
            $dirs[] = \strtr($home, '\\', '/').'/.psysh';
338
        }
339
 
340
        // Add some Windows specific ones :)
341
        if (\defined('PHP_WINDOWS_VERSION_MAJOR')) {
342
            if ($appData = $this->getEnv('APPDATA')) {
343
                // AppData gets preference
344
                \array_unshift($dirs, \strtr($appData, '\\', '/').'/PsySH');
345
            }
346
 
347
            if ($windowsHomeDir = $this->windowsHomeDir()) {
348
                $dir = \strtr($windowsHomeDir, '\\', '/').'/.psysh';
349
                if (!\in_array($dir, $dirs)) {
350
                    $dirs[] = $dir;
351
                }
352
            }
353
        }
354
 
355
        return $dirs;
356
    }
357
 
358
    /**
359
     * Given a list of directories, and a list of filenames, find the ones that
360
     * are real files.
361
     *
362
     * @return string[]
363
     */
364
    private function allRealFiles(array $dirNames, array $fileNames): array
365
    {
366
        $files = [];
367
        foreach ($dirNames as $dir) {
368
            foreach ($fileNames as $name) {
369
                $file = $dir.'/'.$name;
370
                if (@\is_file($file)) {
371
                    $files[] = $file;
372
                }
373
            }
374
        }
375
 
376
        return $files;
377
    }
378
 
379
    /**
380
     * Ensure that $dir exists and is writable.
381
     *
382
     * Generates E_USER_NOTICE error if the directory is not writable or creatable.
383
     *
384
     * @param string $dir
385
     *
386
     * @return bool False if directory exists but is not writeable, or cannot be created
387
     */
388
    public static function ensureDir(string $dir): bool
389
    {
390
        if (!\is_dir($dir)) {
391
            // Just try making it and see if it works
392
            @\mkdir($dir, 0700, true);
393
        }
394
 
395
        if (!\is_dir($dir) || !\is_writable($dir)) {
396
            \trigger_error(\sprintf('Writing to directory %s is not allowed.', $dir), \E_USER_NOTICE);
397
 
398
            return false;
399
        }
400
 
401
        return true;
402
    }
403
 
404
    /**
405
     * Ensure that $file exists and is writable, make the parent directory if necessary.
406
     *
407
     * Generates E_USER_NOTICE error if either $file or its directory is not writable.
408
     *
409
     * @param string $file
410
     *
411
     * @return string|false Full path to $file, or false if file is not writable
412
     */
413
    public static function touchFileWithMkdir(string $file)
414
    {
415
        if (\file_exists($file)) {
416
            if (\is_writable($file)) {
417
                return $file;
418
            }
419
 
420
            \trigger_error(\sprintf('Writing to %s is not allowed.', $file), \E_USER_NOTICE);
421
 
422
            return false;
423
        }
424
 
425
        if (!self::ensureDir(\dirname($file))) {
426
            return false;
427
        }
428
 
429
        \touch($file);
430
 
431
        return $file;
432
    }
433
 
434
    private function getEnv($key)
435
    {
436
        return $this->env->get($key);
437
    }
438
 
439
    private function getEnvArray($key)
440
    {
441
        if ($value = $this->getEnv($key)) {
442
            return \explode(\PATH_SEPARATOR, $value);
443
        }
444
 
445
        return null;
446
    }
447
}