Просмотр файла vendor/psy/psysh/src/ConfigPaths.php

Размер файла: 10.38Kb
  1. <?php
  2.  
  3. /*
  4. * This file is part of Psy Shell.
  5. *
  6. * (c) 2012-2020 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($overrides = [], $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($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()
  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()
  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()
  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()
  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()
  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)
  193. {
  194. return $this->allRealFiles($this->configDirs(), $names);
  195. }
  196.  
  197. /**
  198. * @deprecated
  199. */
  200. public static function getConfigFiles(array $names, $configDir = null)
  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()
  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()
  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)
  245. {
  246. return $this->allRealFiles($this->dataDirs(), $names);
  247. }
  248.  
  249. /**
  250. * @deprecated
  251. */
  252. public static function getDataFiles(array $names, $dataDir = null)
  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()
  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()
  280. {
  281. return (new self())->runtimeDir();
  282. }
  283.  
  284. /**
  285. * Get all PsySH directory name candidates given a list of base directories.
  286. *
  287. * This expects that XDG-compatible directory paths will be passed in.
  288. * `psysh` will be added to each of $baseDirs, and we'll throw in `~/.psysh`
  289. * and a couple of Windows-friendly paths as well.
  290. *
  291. * @param string[] $baseDirs base directory paths
  292. *
  293. * @return string[]
  294. */
  295. private function allDirNames(array $baseDirs)
  296. {
  297. $dirs = \array_map(function ($dir) {
  298. return \strtr($dir, '\\', '/').'/psysh';
  299. }, $baseDirs);
  300.  
  301. // Add ~/.psysh
  302. if ($home = $this->getEnv('HOME')) {
  303. $dirs[] = \strtr($home, '\\', '/').'/.psysh';
  304. }
  305.  
  306. // Add some Windows specific ones :)
  307. if (\defined('PHP_WINDOWS_VERSION_MAJOR')) {
  308. if ($appData = $this->getEnv('APPDATA')) {
  309. // AppData gets preference
  310. \array_unshift($dirs, \strtr($appData, '\\', '/').'/PsySH');
  311. }
  312.  
  313. if ($windowsHomeDir = $this->windowsHomeDir()) {
  314. $dir = \strtr($windowsHomeDir, '\\', '/').'/.psysh';
  315. if (!\in_array($dir, $dirs)) {
  316. $dirs[] = $dir;
  317. }
  318. }
  319. }
  320.  
  321. return $dirs;
  322. }
  323.  
  324. /**
  325. * Given a list of directories, and a list of filenames, find the ones that
  326. * are real files.
  327. *
  328. * @return string[]
  329. */
  330. private function allRealFiles(array $dirNames, array $fileNames)
  331. {
  332. $files = [];
  333. foreach ($dirNames as $dir) {
  334. foreach ($fileNames as $name) {
  335. $file = $dir.'/'.$name;
  336. if (@\is_file($file)) {
  337. $files[] = $file;
  338. }
  339. }
  340. }
  341.  
  342. return $files;
  343. }
  344.  
  345. /**
  346. * Ensure that $dir exists and is writable.
  347. *
  348. * Generates E_USER_NOTICE error if the directory is not writable or creatable.
  349. *
  350. * @param string $dir
  351. *
  352. * @return bool False if directory exists but is not writeable, or cannot be created
  353. */
  354. public static function ensureDir($dir)
  355. {
  356. if (!\is_dir($dir)) {
  357. // Just try making it and see if it works
  358. @\mkdir($dir, 0700, true);
  359. }
  360.  
  361. if (!\is_dir($dir) || !\is_writable($dir)) {
  362. \trigger_error(\sprintf('Writing to directory %s is not allowed.', $dir), \E_USER_NOTICE);
  363.  
  364. return false;
  365. }
  366.  
  367. return true;
  368. }
  369.  
  370. /**
  371. * Ensure that $file exists and is writable, make the parent directory if necessary.
  372. *
  373. * Generates E_USER_NOTICE error if either $file or its directory is not writable.
  374. *
  375. * @param string $file
  376. *
  377. * @return string|false Full path to $file, or false if file is not writable
  378. */
  379. public static function touchFileWithMkdir($file)
  380. {
  381. if (\file_exists($file)) {
  382. if (\is_writable($file)) {
  383. return $file;
  384. }
  385.  
  386. \trigger_error(\sprintf('Writing to %s is not allowed.', $file), \E_USER_NOTICE);
  387.  
  388. return false;
  389. }
  390.  
  391. if (!self::ensureDir(\dirname($file))) {
  392. return false;
  393. }
  394.  
  395. \touch($file);
  396.  
  397. return $file;
  398. }
  399.  
  400. private function getEnv($key)
  401. {
  402. return $this->env->get($key);
  403. }
  404.  
  405. private function getEnvArray($key)
  406. {
  407. if ($value = $this->getEnv($key)) {
  408. return \explode(':', $value);
  409. }
  410.  
  411. return null;
  412. }
  413. }