Implement stow config file and folder unwrapping
Move includes to their own folder, config.php will otherwise be included and cleans up nicely
This commit is contained in:
85
php/includes/argvParser.php
Normal file
85
php/includes/argvParser.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
class argParsed
|
||||
{
|
||||
public function __construct(
|
||||
private array $parsed = [],
|
||||
) {}
|
||||
|
||||
public function initOptions(array $options, mixed $default = false): self
|
||||
{
|
||||
foreach (array_keys($options) as $name) {
|
||||
$this->set($name, $default);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return $this->parsed[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value): self
|
||||
{
|
||||
$this->parsed[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRest(): string
|
||||
{
|
||||
return $this->get('rest', '');
|
||||
}
|
||||
}
|
||||
|
||||
readonly class argvParser
|
||||
{
|
||||
/**
|
||||
* @param array<string, string> $options
|
||||
* @param array<string, string> $arguments
|
||||
*/
|
||||
public function __construct(
|
||||
private array $options = [],
|
||||
private array $arguments = [],
|
||||
) {}
|
||||
|
||||
public function getOptionsHelp(): string
|
||||
{
|
||||
$line = '';
|
||||
foreach ($this->options as $option => $description) {
|
||||
$line .= sprintf('[-%s (%s)] ', $option, $description);
|
||||
}
|
||||
foreach ($this->arguments as $argument => $description) {
|
||||
$line .= sprintf('<%s (%s)> ', $argument, $description);
|
||||
}
|
||||
return $line;
|
||||
}
|
||||
|
||||
public function parseArgv(array $argv): argParsed|false
|
||||
{
|
||||
array_shift($argv); // shift the script name
|
||||
|
||||
$parsed = (new argParsed())->initOptions($this->options);
|
||||
while (str_starts_with($argv[0] ?? '', '-')) {
|
||||
$option = substr($argv[0], 1, 1);
|
||||
if (substr($argv[0], 2, 1) === ':') {
|
||||
$data = substr($argv[0], 3);
|
||||
} else {
|
||||
$data = true;
|
||||
}
|
||||
if ($this->options[$option]) {
|
||||
$parsed->set($option, $data);
|
||||
}
|
||||
array_shift($argv);
|
||||
}
|
||||
if (count($argv) < count($this->arguments)) {
|
||||
return false;
|
||||
}
|
||||
foreach ($this->arguments as $arg => $description) {
|
||||
$parsed->set($arg, array_shift($argv));
|
||||
}
|
||||
$parsed->set('rest', implode(' ', $argv));
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
}
|
71
php/includes/config.php
Normal file
71
php/includes/config.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/functions.php';
|
||||
|
||||
readonly class homeConfig extends config
|
||||
{
|
||||
private const CONFIG_BASE_PATH = '.config';
|
||||
|
||||
public function __construct(
|
||||
string $name
|
||||
)
|
||||
{
|
||||
parent::__construct(path(
|
||||
getenv('HOME'),
|
||||
self::CONFIG_BASE_PATH,
|
||||
$name
|
||||
));
|
||||
$this->createConfig();
|
||||
}
|
||||
|
||||
private function createConfig(): void
|
||||
{
|
||||
if (file_exists($this->getConfigFile())) {
|
||||
return;
|
||||
} else {
|
||||
if (!is_dir($this->getConfigPath())) {
|
||||
mkdir($this->getConfigPath(), 0755, true);
|
||||
}
|
||||
$this->writeConfig([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly class config
|
||||
{
|
||||
public function __construct(
|
||||
private string $path,
|
||||
private string $fileName = 'config.php',
|
||||
) {}
|
||||
|
||||
public function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
if (!file_exists($this->getConfigFile())) {
|
||||
return $default;
|
||||
}
|
||||
$config = include $this->getConfigFile();
|
||||
return $config[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value): void
|
||||
{
|
||||
$config = include $this->getConfigFile();
|
||||
$config[$key] = $value;
|
||||
$this->writeConfig($config);
|
||||
}
|
||||
|
||||
protected function getConfigFile(): string
|
||||
{
|
||||
return path($this->getConfigPath(), $this->fileName);
|
||||
}
|
||||
|
||||
protected function getConfigPath(): string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
protected function writeConfig(array $config): void
|
||||
{
|
||||
file_put_contents($this->getConfigFile(), '<?php return ' . var_export($config, true) . ';');
|
||||
}
|
||||
}
|
11
php/includes/functions.php
Normal file
11
php/includes/functions.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
function path(...$segments): string
|
||||
{
|
||||
return implode(DIRECTORY_SEPARATOR, $segments);
|
||||
}
|
||||
|
||||
function line(string $line): void
|
||||
{
|
||||
echo $line . PHP_EOL;
|
||||
}
|
66
php/includes/snips.php
Normal file
66
php/includes/snips.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
readonly class snips
|
||||
{
|
||||
public function __construct(
|
||||
private string $baseUrl,
|
||||
private string $apiKey,
|
||||
) {}
|
||||
|
||||
public function getSnip(int $id): string
|
||||
{
|
||||
$url = $this->baseUrl . 'snip/' . $id;
|
||||
$headers = [
|
||||
'X-AUTH-TOKEN: ' . $this->apiKey,
|
||||
'Accept: application/json',
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
$response = $this->getResponse($response);
|
||||
|
||||
if (isset($response['success']) && $response['success'] === true) {
|
||||
return $response['data']['content'];
|
||||
} else {
|
||||
throw new Exception('Error fetching snip: ' . json_encode($response));
|
||||
}
|
||||
// rewrite to curl
|
||||
}
|
||||
|
||||
public function postSnip(int $id, string $content): string
|
||||
{
|
||||
$url = $this->baseUrl . 'snip/' . $id;
|
||||
$headers = [
|
||||
'X-AUTH-TOKEN: ' . $this->apiKey,
|
||||
'Accept: application/json',
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, ['content' => $content]);
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
$response = $this->getResponse($response);
|
||||
|
||||
if (isset($response['success']) && $response['success'] === true) {
|
||||
return $response['data']['content'];
|
||||
} else {
|
||||
throw new Exception('Error updating snip: ' . json_encode($response));
|
||||
}
|
||||
}
|
||||
|
||||
private function getResponse(bool|string $response): array
|
||||
{
|
||||
$response = json_decode($response, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception('Error decoding JSON response: ' . json_last_error_msg());
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
118
php/includes/stow.php
Normal file
118
php/includes/stow.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
readonly class stow
|
||||
{
|
||||
public const ACTION_STOW = 'stow';
|
||||
public const ACTION_UNSTOW = 'unstow';
|
||||
|
||||
public function __construct(
|
||||
private string $action = self::ACTION_STOW,
|
||||
private bool $backup = false,
|
||||
private bool $force = false,
|
||||
private bool $unwrap = false,
|
||||
) {}
|
||||
|
||||
private function getNoStow(string $path): array
|
||||
{
|
||||
if (file_exists(path($path, '.nostow'))) {
|
||||
$noStow = file_get_contents(path($path, '.nostow'));
|
||||
if ($noStow === false) {
|
||||
return [];
|
||||
}
|
||||
return array_values(array_filter(array_map('trim', explode(PHP_EOL, $noStow))));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function run(string $sourcePath, string $targetPath): void
|
||||
{
|
||||
$noStow = $this->getNoStow($sourcePath);
|
||||
$sourceFiles = array_diff(scandir($sourcePath), ['..', '.', 'config.php', '.nostow', ...$noStow]);
|
||||
|
||||
foreach ($sourceFiles as $sourceFile) {
|
||||
$targetFile = path($targetPath, $sourceFile);
|
||||
$stowFile = path($sourcePath, $sourceFile);
|
||||
|
||||
switch ($this->action) {
|
||||
case self::ACTION_STOW:
|
||||
$this->stow($stowFile, $targetFile);
|
||||
break;
|
||||
case self::ACTION_UNSTOW:
|
||||
$this->unstow($stowFile, $targetFile);
|
||||
break;
|
||||
default:
|
||||
line("Unknown action: {$this->action}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function stow(string $stowFile, string $targetFile): void
|
||||
{
|
||||
if (is_link($targetFile)) {
|
||||
$targetLink = readlink($targetFile);
|
||||
if ($targetLink === $stowFile) {
|
||||
line("File $targetFile is already stowed from $stowFile");
|
||||
} else {
|
||||
if ($this->unwrap) {
|
||||
line("Unwrapping $targetFile from $targetLink");
|
||||
unlink($targetFile);
|
||||
mkdir($targetFile);
|
||||
$this->run($targetLink, $targetFile);
|
||||
$this->run($stowFile, $targetFile);
|
||||
} else {
|
||||
line("File $targetFile is linked from $targetLink, ignoring, add -w to unwrap");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (is_dir($targetFile)) {
|
||||
if (is_dir($stowFile)) {
|
||||
line("File $targetFile and $stowFile are folders, recurse into existing folders");
|
||||
$this->run($stowFile, $targetFile);
|
||||
} else {
|
||||
line("File $targetFile is a folder, but stow file $stowFile is not a folder");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (is_file($targetFile)) {
|
||||
if ($this->backup) {
|
||||
$backFile = $targetFile . '.bak';
|
||||
if (is_file($backFile)) {
|
||||
line("Backup file $backFile already exists");
|
||||
return;
|
||||
}
|
||||
rename($targetFile, $backFile);
|
||||
line("Backup file $targetFile to $backFile");
|
||||
} elseif ($this->force) {
|
||||
line("Todo: delete file $targetFile");
|
||||
return;
|
||||
} else {
|
||||
line("File $targetFile already exists");
|
||||
return;
|
||||
}
|
||||
}
|
||||
line("Stow $targetFile from $stowFile");
|
||||
symlink($stowFile, $targetFile);
|
||||
}
|
||||
|
||||
public function unstow(string $stowFile, string $targetFile): void
|
||||
{
|
||||
if (is_link($targetFile) && readlink($targetFile) === $stowFile) {
|
||||
line("Unstow $targetFile from $stowFile");
|
||||
unlink($targetFile);
|
||||
return;
|
||||
}
|
||||
if (is_dir($targetFile)) {
|
||||
if (is_dir($stowFile)) {
|
||||
line("File $targetFile and $stowFile are folders, recurse into existing folders");
|
||||
$this->run($stowFile, $targetFile);
|
||||
return;
|
||||
} else {
|
||||
line("File $targetFile is a folder, but stow file $stowFile is not a folder");
|
||||
}
|
||||
}
|
||||
line("File $targetFile is not stowed from $stowFile");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user