diff --git a/camel/Camel.php b/camel/Camel.php index c6b0d41e..28d03d30 100644 --- a/camel/Camel.php +++ b/camel/Camel.php @@ -121,7 +121,26 @@ public static function sortByConfigFileOrder(array $groupedEndpoints, array $con // First, sort groups $groupsOrder = Utils::getTopLevelItemsFromMixedConfigList($configFileOrder); - $groupedEndpoints = collect($groupedEndpoints)->sortKeysUsing(self::getOrderListComparator($groupsOrder)); + $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']); diff --git a/config/scribe.php b/config/scribe.php index a1662723..688cef13 100644 --- a/config/scribe.php +++ b/config/scribe.php @@ -317,7 +317,8 @@ * By default, Scribe will sort groups alphabetically, and endpoints in the order their routes are defined. * You can override this by listing the groups, subgroups and endpoints here in the order you want them. * - * Any groups, subgroups or endpoints you don't list here will be added as usual after the ones here. + * Any groups, subgroups or endpoints you don't list here will be added as usual after the ones here unless you + * use the "*" character as this specifies the position of all unspecified groups * If an endpoint/subgroup is listed under a group it doesn't belong in, it will be ignored. * Note: you must include the initial '/' when writing an endpoint. */ diff --git a/tests/GenerateDocumentation/OutputTest.php b/tests/GenerateDocumentation/OutputTest.php index 28e41a34..52f4ede8 100644 --- a/tests/GenerateDocumentation/OutputTest.php +++ b/tests/GenerateDocumentation/OutputTest.php @@ -349,6 +349,65 @@ public function sorts_groups_and_endpoints_in_the_specified_order() $this->assertEquals("POST api/action13b", $thirdGroupEndpointsAndSubgroups->getNode(7)->textContent); } + /** @test */ + public function sorts_groups_and_endpoints_in_the_specified_order_with_wildcard() + { + config(['scribe.groups.order' => [ + '10. Group 10', + '*', + '13. Group 13' => [ + 'SG B' => [ + 'POST /api/action13d', + 'GET /api/action13a', + ], + 'SG A', + 'PUT /api/action13c', + ], + ]]); + + RouteFacade::get('/api/action1', [TestGroupController::class, 'action1']); + RouteFacade::get('/api/action1b', [TestGroupController::class, 'action1b']); + RouteFacade::get('/api/action2', [TestGroupController::class, 'action2']); + RouteFacade::get('/api/action10', [TestGroupController::class, 'action10']); + RouteFacade::get('/api/action13a', [TestGroupController::class, 'action13a']); + RouteFacade::post('/api/action13b', [TestGroupController::class, 'action13b']); + RouteFacade::put('/api/action13c', [TestGroupController::class, 'action13c']); + RouteFacade::post('/api/action13d', [TestGroupController::class, 'action13d']); + RouteFacade::get('/api/action13e', [TestGroupController::class, 'action13e']); + + $this->generate(); + + $crawler = new Crawler(file_get_contents($this->htmlOutputPath())); + $headings = $crawler->filter('h1')->getIterator(); + $this->assertCount(6, $headings); // intro, auth, four groups + [$_, $_, $firstGroup, $secondGroup, $thirdGroup, $fourthGroup] = $headings; + + $this->assertEquals('10. Group 10', $firstGroup->textContent); + $this->assertEquals('1. Group 1', $secondGroup->textContent); + $this->assertEquals('2. Group 2', $thirdGroup->textContent); + $this->assertEquals('13. Group 13', $fourthGroup->textContent); + + $firstGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="'.Str::slug($firstGroup->textContent).'"]'); + $this->assertEquals(1, $firstGroupEndpointsAndSubgroups->count()); + $this->assertEquals("GET api/action10", $firstGroupEndpointsAndSubgroups->getNode(0)->textContent); + + $secondGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="'.Str::slug($secondGroup->textContent).'"]'); + $this->assertEquals(2, $secondGroupEndpointsAndSubgroups->count()); + $this->assertEquals("GET api/action1", $secondGroupEndpointsAndSubgroups->getNode(0)->textContent); + $this->assertEquals("GET api/action1b", $secondGroupEndpointsAndSubgroups->getNode(1)->textContent); + + $fourthGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="'.Str::slug($fourthGroup->textContent).'"]'); + $this->assertEquals(8, $fourthGroupEndpointsAndSubgroups->count()); + $this->assertEquals("SG B", $fourthGroupEndpointsAndSubgroups->getNode(0)->textContent); + $this->assertEquals("POST api/action13d", $fourthGroupEndpointsAndSubgroups->getNode(1)->textContent); + $this->assertEquals("GET api/action13a", $fourthGroupEndpointsAndSubgroups->getNode(2)->textContent); + $this->assertEquals("SG A", $fourthGroupEndpointsAndSubgroups->getNode(3)->textContent); + $this->assertEquals("GET api/action13e", $fourthGroupEndpointsAndSubgroups->getNode(4)->textContent); + $this->assertEquals("PUT api/action13c", $fourthGroupEndpointsAndSubgroups->getNode(5)->textContent); + $this->assertEquals("SG C", $fourthGroupEndpointsAndSubgroups->getNode(6)->textContent); + $this->assertEquals("POST api/action13b", $fourthGroupEndpointsAndSubgroups->getNode(7)->textContent); + } + /** @test */ public function merges_and_correctly_sorts_user_defined_endpoints() {