diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5b2d9e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +## IDE +.idea + +## composer +composer.lock +vendor diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6a1579c --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +phpstan: + vendor/bin/phpstan analyse diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..96da11a --- /dev/null +++ b/composer.json @@ -0,0 +1,14 @@ +{ + "name": "utilitte/console", + "require": { + "php": ">= 8.1", + "phpstan/phpstan": "^1.5", + "nette/utils": "^3.2", + "utilitte/asserts": "^1.2" + }, + "autoload": { + "psr-4": { + "Utilitte\\Console\\": "src" + } + } +} diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..c4193a2 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 9 + paths: + - src + editorUrl: 'file://%%file%%' diff --git a/src/CommandLine.php b/src/CommandLine.php new file mode 100644 index 0000000..e0f77f0 --- /dev/null +++ b/src/CommandLine.php @@ -0,0 +1,63 @@ +file = $file; + + try { + $this->contents = (array) Json::decode(FileSystem::read($file), Json::FORCE_ARRAY); + } catch (JsonException $e) { + throw new InvalidArgumentException(sprintf('Composer file %s is not a valid json.', $file), previous: $e); + } + } + + public function getName(): string + { + return TypeAssert::string($this->getSection('name')); + } + + /** + * @return array + */ + public function getRequire(): array + { + /** @var array $require */ + $require = $this->getSectionWithDefault('require', []); + + return $require; + } + + /** + * @return array + */ + public function getDevRequire(): array + { + /** @var array $require */ + $require = $this->getSectionWithDefault('dev-require', []); + + return $require; + } + + /** + * @return array + */ + public function getLibraries(): array + { + $libs = []; + foreach (array_merge($this->getRequire(), $this->getDevRequire()) as $name => $version) { + if (!str_contains($name, '/')) { + continue; + } + + $libs[$name] = $version; + } + + return $libs; + } + + public function getLibraryPath(string $name): string + { + return $this->directory . '/vendor/' . $name; + } + + private function getSection(string $section): mixed + { + return $this->contents[$section] + ?? + throw new LogicException(sprintf('Composer file %s does not have section %s.', $this->file, $section)); + } + + private function getSectionWithDefault(string $section, mixed $default): mixed + { + return $this->contents[$section] ?? $default; + } + + public static function getParsedFileNullable(string $directory): ?self + { + try { + return new self($directory); + } catch (InvalidArgumentException) { + return null; + } + } + +} diff --git a/src/Git.php b/src/Git.php new file mode 100644 index 0000000..1b28efb --- /dev/null +++ b/src/Git.php @@ -0,0 +1,60 @@ +directory . '/.git'); + } + + public function hasUncommittedFiles(): bool + { + $this->validate(); + + return (bool) $this->command('git status -s'); + } + + /** + * @return string[] + */ + public function getUncommitedFiles(): array + { + return $this->command('git status -s'); + } + + public function hasUnpushedCommits(): bool + { + $this->validate(); + + $return = $this->command('git status -s -b'); + + return str_contains($return[0], '[ahead'); + } + + /** + * @return string[] + */ + private function command(string $command): array + { + return CommandLine::command(sprintf('cd "%s" && %s', $this->directory, $command), true); + } + + private function validate(): void + { + if (!$this->isGitDirectory()) { + throw new LogicException(sprintf('Directory %s, is not a git directory.', $this->directory)); + } + } + +} diff --git a/src/SemanticVersion.php b/src/SemanticVersion.php new file mode 100644 index 0000000..74a96b2 --- /dev/null +++ b/src/SemanticVersion.php @@ -0,0 +1,144 @@ +prefix = $matches[1]; + $this->major = (int) $matches[2]; + $this->minor = $this->convertStringToInt($matches[3]); + $this->patch = $this->convertStringToInt($matches[4]); + } + + public function withMajorIncrease(): self + { + return $this->withMajorAddition(1) + ->withMinor(0) + ->withPatch(0); + } + + public function withMinorIncrease(): self + { + return $this->withMinorAddition(1) + ->withPatch(0); + } + + public function withPatchIncrease(): self + { + return $this->withPatchAddition(1); + } + + public function withMajor(int $number): self + { + $clone = clone $this; + $clone->major = $number; + + return $clone; + } + + public function withMinor(int $number): self + { + $clone = clone $this; + $clone->minor = $number; + + return $clone; + } + + public function withPatch(int $number): self + { + $clone = clone $this; + $clone->patch = $number; + + return $clone; + } + + public function withMajorAddition(int $addition): self + { + $clone = clone $this; + $clone->major += $addition; + + return $clone; + } + + public function withMinorAddition(int $addition): self + { + $clone = clone $this; + $clone->minor += $addition; + + return $clone; + } + + public function withPatchAddition(int $addition): self + { + $clone = clone $this; + $clone->patch += $addition; + + return $clone; + } + + private function convertStringToInt(?string $version): ?int + { + return $version === null ? null : (int) $version; + } + + public function compareWith(self $version): int + { + return self::compare($this, $version); + } + + public static function compare(self $version1, self $version2): int + { + if ($version1->major > $version2->major) { + return 1; + } else if ($version1->major < $version2->major) { + return -1; + } + + if ($version1->minor > $version2->minor) { + return 1; + } else if ($version1->minor < $version2->minor) { + return -1; + } + + if ($version1->patch > $version2->patch) { + return 1; + } else if ($version1->patch < $version2->patch) { + return -1; + } + + return 0; + } + + public function __toString(): string + { + $version = $this->prefix . max(0, $this->major); + + if ($this->minor !== null) { + $version .= '.' . max(0, $this->minor); + } + + if (!$this->patch !== null) { + $version .= '.' . max(0, $this->patch); + } + + return $version; + } + +}