123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- <?php
- namespace Knuckles\Camel;
- use Illuminate\Support\Arr;
- use Illuminate\Support\Str;
- use Knuckles\Camel\Output\OutputEndpointData;
- use Knuckles\Scribe\Tools\PathConfig;
- use Knuckles\Scribe\Tools\Utils;
- use Symfony\Component\Yaml\Yaml;
- class Camel
- {
- public static function cacheDir(PathConfig $paths): string
- {
- return $paths->intermediateOutputPath('endpoints.cache');
- }
- public static function camelDir(PathConfig $paths): string
- {
- return $paths->intermediateOutputPath('endpoints');
- }
- /**
- * Load endpoints from the Camel files into groups (arrays).
- *
- * @param string $folder
- *
- * @return array[] Each array is a group with keys including `name` and `endpoints`.
- */
- public static function loadEndpointsIntoGroups(string $folder): array
- {
- $groups = [];
- self::loadEndpointsFromCamelFiles($folder, function (array $group) use (&$groups) {
- $groups[$group['name']] = $group;
- });
- return $groups;
- }
- /**
- * Load endpoints from the Camel files into a flat list of endpoint arrays.
- * Useful when we don't care about groups, but simply want to compare endpoints contents
- * to see if anything changed.
- *
- * @param string $folder
- *
- * @return array[] List of endpoint arrays.
- */
- public static function loadEndpointsToFlatPrimitivesArray(string $folder): array
- {
- $endpoints = [];
- self::loadEndpointsFromCamelFiles($folder, function (array $group) use (&$endpoints) {
- foreach ($group['endpoints'] as $endpoint) {
- $endpoints[] = $endpoint;
- }
- });
- return $endpoints;
- }
- public static function loadEndpointsFromCamelFiles(string $folder, callable $callback): void
- {
- $contents = Utils::listDirectoryContents($folder);
- foreach ($contents as $object) {
- if (
- $object->isFile()
- && Str::endsWith(basename($object->path()), '.yaml')
- && !Str::startsWith(basename($object->path()), 'custom.')
- ) {
- $group = Yaml::parseFile($object['path']);
- $callback($group);
- }
- }
- }
- public static function loadUserDefinedEndpoints(string $folder): array
- {
- $contents = Utils::listDirectoryContents($folder);
- $userDefinedEndpoints = [];
- foreach ($contents as $object) {
- if (
- $object->isFile()
- && Str::endsWith(basename($object->path()), '.yaml')
- && Str::startsWith(basename($object->path()), 'custom.')
- ) {
- $endpoints = Yaml::parseFile($object->path());
- foreach (($endpoints ?: []) as $endpoint) {
- $userDefinedEndpoints[] = $endpoint;
- }
- }
- }
- return $userDefinedEndpoints;
- }
- public static function doesGroupContainEndpoint(array $group, OutputEndpointData $endpoint): bool
- {
- return boolval(Arr::first($group['endpoints'], function ($e) use ($endpoint) {
- return $e->endpointId() === $endpoint->endpointId();
- }));
- }
- /**
- * @param array[] $groupedEndpoints
- * @param array $configFileOrder The order for groups that users specified in their config file.
- *
- * @return array[]
- */
- public static function sortByConfigFileOrder(array $groupedEndpoints, array $configFileOrder): array
- {
- if (empty($configFileOrder)) {
- ksort($groupedEndpoints, SORT_NATURAL);
- return $groupedEndpoints;
- }
- // First, sort groups
- $groupsOrder = Utils::getTopLevelItemsFromMixedConfigList($configFileOrder);
- $groupsCollection = collect($groupedEndpoints);
- $wildcardPosition = array_search('*', $groupsOrder);
- if ($wildcardPosition !== false) {
- $promotedGroups = array_splice($groupsOrder, 0, $wildcardPosition);
- $demotedGroups = array_splice($groupsOrder, 1);
- $promotedOrderedGroups = $groupsCollection->filter(fn ($group, $groupName) => in_array($groupName, $promotedGroups))
- ->sortKeysUsing(self::getOrderListComparator($promotedGroups));
- $demotedOrderedGroups = $groupsCollection->filter(fn ($group, $groupName) => in_array($groupName, $demotedGroups))
- ->sortKeysUsing(self::getOrderListComparator($demotedGroups));
- $nonWildcardGroups = array_merge($promotedGroups, $demotedGroups);
- $wildCardOrderedGroups = $groupsCollection->filter(fn ($group, $groupName) => !in_array($groupName, $nonWildcardGroups))
- ->sortKeysUsing(self::getOrderListComparator($demotedGroups));
- $groupedEndpoints = $promotedOrderedGroups->merge($wildCardOrderedGroups)
- ->merge($demotedOrderedGroups);
- } else {
- $groupedEndpoints = $groupsCollection->sortKeysUsing(self::getOrderListComparator($groupsOrder));
- }
- return $groupedEndpoints->map(function (array $group, string $groupName) use ($configFileOrder) {
- $sortedEndpoints = collect($group['endpoints']);
- if (isset($configFileOrder[$groupName])) {
- // Second-level order list. Can contain endpoint or subgroup names
- $level2Order = Utils::getTopLevelItemsFromMixedConfigList($configFileOrder[$groupName]);
- $sortedEndpoints = $sortedEndpoints->sortBy(
- function (OutputEndpointData $e) use ($configFileOrder, $level2Order) {
- $endpointIdentifier = $e->httpMethods[0] . ' /' . $e->uri;
- // First, check if there's an ordering specified for the endpoint itself
- $indexOfEndpointInL2Order = array_search($endpointIdentifier, $level2Order);
- if ($indexOfEndpointInL2Order !== false) {
- return $indexOfEndpointInL2Order;
- }
- // Check if there's an ordering for the endpoint's subgroup
- $indexOfSubgroupInL2Order = array_search($e->metadata->subgroup, $level2Order);
- if ($indexOfSubgroupInL2Order !== false) {
- // There's a subgroup order; check if there's an endpoints order within that
- $orderOfEndpointsInSubgroup = $configFileOrder[$e->metadata->groupName][$e->metadata->subgroup] ?? [];
- $indexOfEndpointInSubGroup = array_search($endpointIdentifier, $orderOfEndpointsInSubgroup);
- return ($indexOfEndpointInSubGroup === false)
- ? $indexOfSubgroupInL2Order
- : ($indexOfSubgroupInL2Order + ($indexOfEndpointInSubGroup * 0.1));
- }
- return INF;
- },
- );
- }
- return [
- 'name' => $groupName,
- 'description' => $group['description'],
- 'endpoints' => $sortedEndpoints->all(),
- ];
- })->values()->all();
- }
- /**
- * Prepare endpoints to be turned into HTML.
- * Map them into OutputEndpointData DTOs, and sort them by the specified order in the config file.
- *
- * @param array<string,array[]> $groupedEndpoints
- *
- * @return array
- */
- public static function prepareGroupedEndpointsForOutput(array $groupedEndpoints, array $configFileOrder = []): array
- {
- $groups = array_map(function (array $group) {
- return [
- 'name' => $group['name'],
- 'description' => $group['description'],
- 'endpoints' => array_map(
- fn(array $endpoint) => OutputEndpointData::fromExtractedEndpointArray($endpoint), $group['endpoints']
- ),
- ];
- }, $groupedEndpoints);
- return Camel::sortByConfigFileOrder($groups, $configFileOrder);
- }
- /**
- * Given an $order list like ['first', 'second', ...], return a compare function that can be used to sort
- * a list of strings based on the order of items in $order.
- * Any strings not in the list are sorted with natural sort.
- *
- * @param array $order
- */
- protected static function getOrderListComparator(array $order): \Closure
- {
- return function ($a, $b) use ($order) {
- $indexOfA = array_search($a, $order);
- $indexOfB = array_search($b, $order);
- // If both are in the $order list, compare them normally based on their position in the list
- if ($indexOfA !== false && $indexOfB !== false) {
- return $indexOfA <=> $indexOfB;
- }
- // If only A is in the $order list, then it must come before B.
- if ($indexOfA !== false) {
- return -1;
- }
- // If only B is in the $order list, then it must come before A.
- if ($indexOfB !== false) {
- return 1;
- }
- // If neither is present, fall back to natural sort
- return strnatcmp($a, $b);
- };
- }
- }
|