From 0679422aa4e018772a77657ed2602e328aaafaaf Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Sun, 25 Jun 2023 13:07:34 +0100 Subject: [PATCH 1/5] Support generating individual URLs --- src/Commands/StaticSiteGenerate.php | 4 +-- src/Generator.php | 55 +++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/Commands/StaticSiteGenerate.php b/src/Commands/StaticSiteGenerate.php index a944a8d..9242224 100644 --- a/src/Commands/StaticSiteGenerate.php +++ b/src/Commands/StaticSiteGenerate.php @@ -22,7 +22,7 @@ class StaticSiteGenerate extends Command * * @var string */ - protected $signature = 'statamic:ssg:generate {--workers=}'; + protected $signature = 'statamic:ssg:generate {--workers=} {--url=*}'; /** * The console command description. @@ -59,7 +59,7 @@ public function handle() try { $this->generator ->workers($workers ?? 1) - ->generate(); + ->generate($this->option('url')); } catch (GenerationFailedException $e) { $this->line($e->getConsoleMessage()); $this->error('Static site generation failed.'); diff --git a/src/Generator.php b/src/Generator.php index 3584c4f..e3383ff 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -78,17 +78,28 @@ public function addUrls($closure) $this->extraUrls[] = $closure; } - public function generate() + public function generate(array $urls = []) { $this->checkConcurrencySupport(); Site::setCurrent(Site::default()->handle()); - $this - ->bindGlide() - ->clearDirectory() - ->createContentFiles() - ->createSymlinks() + $this->bindGlide(); + + if (empty($urls)) { + $this->clearDirectory() + ->createContentFiles(); + } else { + foreach ($urls as $url) { + try { + $this->createContentFile(Str::start($url, '/')); + } catch (GenerationFailedException $e) { + // When generating multiple URLs, we don't want to fail the entire process when one fails. + } + } + } + + $this->createSymlinks() ->copyFiles() ->outputSummary(); @@ -194,6 +205,38 @@ protected function createContentFiles() return $this; } + protected function createContentFile($url) + { + $request = tap(Request::capture(), function ($request) { + $request->setConfig($this->config); + $this->app->instance('request', $request); + Cascade::withRequest($request); + }); + + $page = collect([Entry::findByUri($url)]) + ->map(function ($content) { + return $this->createPage($content); + }) + ->filter + ->isGeneratable(); + + Partyline::line("Generating content file..."); + + $closures = $this->makeContentGenerationClosures($page, $request); + + $results = $this->tasks->run(...$closures); + + if ($this->anyTasksFailed($results)) { + throw GenerationFailedException::withConsoleMessage("\x1B[1A\x1B[2K"); + } + + $this->taskResults = $this->compileTasksResults($results); + + $this->outputTasksResults(); + + return $this; + } + protected function anyTasksFailed($results) { return collect($results)->contains(''); From 6ef61c43aa58edb6f9f783dfa4bc6ebfe363548f Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Sun, 25 Jun 2023 13:10:37 +0100 Subject: [PATCH 2/5] WIP: Generate a batch of paginated files --- src/Generator.php | 52 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/src/Generator.php b/src/Generator.php index e3383ff..c2e399e 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -91,10 +91,17 @@ public function generate(array $urls = []) ->createContentFiles(); } else { foreach ($urls as $url) { - try { - $this->createContentFile(Str::start($url, '/')); - } catch (GenerationFailedException $e) { - // When generating multiple URLs, we don't want to fail the entire process when one fails. + if (Str::contains($url, ':')) { + // We're looking to loop over some paginated set + // How to fetch those pages should be established elsewhere, but here we can loop over them and + // generate the static files for each page from just a single URL. + $this->createPaginatedFiles(...(explode(':', $url))); + } else { + try { + $this->createContentFile(Str::start($url, '/')); + } catch (GenerationFailedException $e) { + // When generating multiple URLs, we don't want to fail the entire process when one fails. + } } } } @@ -237,6 +244,43 @@ protected function createContentFile($url) return $this; } + protected function createPaginatedFiles($url, $collection, $perPage = 10, $pathPart = 'page') + { + $request = tap(Request::capture(), function ($request) { + $request->setConfig($this->config); + $this->app->instance('request', $request); + Cascade::withRequest($request); + }); + + $total = Entry::query() + ->where('collection', $collection) + ->where('status', 'published') + ->count(); + + $pages = collect(range(1, ceil($total / $perPage))) + ->map(fn ($pageNum) => implode('/', [$url, $pathPart, $pageNum])) + ->map(function ($url) { + $url = Str::start($url, '/'); + return $this->createPage(new Route($url)); + }); + + Partyline::line("Generating paginated content files..."); + + $closures = $this->makeContentGenerationClosures($pages, $request); + + $results = $this->tasks->run(...$closures); + + if ($this->anyTasksFailed($results)) { + throw GenerationFailedException::withConsoleMessage("\x1B[1A\x1B[2K"); + } + + $this->taskResults = $this->compileTasksResults($results); + + $this->outputTasksResults(); + + return $this; + } + protected function anyTasksFailed($results) { return collect($results)->contains(''); From 157bd9e1e25b6866ee5bc0279d6fcd05572acb1e Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Sun, 25 Jun 2023 13:37:36 +0100 Subject: [PATCH 3/5] Move to methods --- src/Commands/StaticSiteGenerate.php | 3 ++- src/Generator.php | 12 ++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Commands/StaticSiteGenerate.php b/src/Commands/StaticSiteGenerate.php index 9242224..49c2e56 100644 --- a/src/Commands/StaticSiteGenerate.php +++ b/src/Commands/StaticSiteGenerate.php @@ -59,7 +59,8 @@ public function handle() try { $this->generator ->workers($workers ?? 1) - ->generate($this->option('url')); + ->explicitUrls($this->option('url')) + ->generate(); } catch (GenerationFailedException $e) { $this->line($e->getConsoleMessage()); $this->error('Static site generation failed.'); diff --git a/src/Generator.php b/src/Generator.php index e3383ff..397d010 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -34,6 +34,7 @@ class Generator protected $config; protected $request; protected $after; + protected $explicitUrls = []; protected $extraUrls; protected $workers = 1; protected $taskResults; @@ -73,6 +74,13 @@ public function after($after) return $this; } + public function explicitUrls(array $urls = []) + { + $this->explicitUrls = $urls; + + return $this; + } + public function addUrls($closure) { $this->extraUrls[] = $closure; @@ -86,11 +94,11 @@ public function generate(array $urls = []) $this->bindGlide(); - if (empty($urls)) { + if (empty($this->explicitUrls)) { $this->clearDirectory() ->createContentFiles(); } else { - foreach ($urls as $url) { + foreach ($this->explicitUrls as $url) { try { $this->createContentFile(Str::start($url, '/')); } catch (GenerationFailedException $e) { From 1fc385bfedda0bbcbd00807ef9debd1e4e028aff Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Sun, 25 Jun 2023 15:38:42 +0100 Subject: [PATCH 4/5] Make pagination builds configurable --- config/ssg.php | 4 ++ src/Generator.php | 138 +++++++++++++++++++--------------------------- 2 files changed, 61 insertions(+), 81 deletions(-) diff --git a/config/ssg.php b/config/ssg.php index e3436ab..99242fb 100644 --- a/config/ssg.php +++ b/config/ssg.php @@ -60,6 +60,10 @@ // ], + 'paginators' => [ + // + ], + /* |-------------------------------------------------------------------------- | Exclude URLs diff --git a/src/Generator.php b/src/Generator.php index c2e399e..016d2e0 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -91,17 +91,20 @@ public function generate(array $urls = []) ->createContentFiles(); } else { foreach ($urls as $url) { - if (Str::contains($url, ':')) { - // We're looking to loop over some paginated set - // How to fetch those pages should be established elsewhere, but here we can loop over them and - // generate the static files for each page from just a single URL. - $this->createPaginatedFiles(...(explode(':', $url))); - } else { - try { - $this->createContentFile(Str::start($url, '/')); - } catch (GenerationFailedException $e) { - // When generating multiple URLs, we don't want to fail the entire process when one fails. + try { + // Create the content file for this specific URL + $this->createContentFiles( + $this->page($url) + ); + + // If this page is is the start of a paginated collected, generate all of the paginated URLs too + if (array_key_exists($url, ($this->config['paginators'] ?? []))) { + $this->createContentFiles( + $this->paginatedEntries($url, ...$this->config['paginators'][$url]) + ); } + } catch (GenerationFailedException $e) { + // When generating multiple URLs, we don't want to fail the entire process when one fails. } } } @@ -185,7 +188,7 @@ public function copyFiles() return $this; } - protected function createContentFiles() + protected function createContentFiles(\Illuminate\Support\Collection $pages = null) { $request = tap(Request::capture(), function ($request) { $request->setConfig($this->config); @@ -193,7 +196,7 @@ protected function createContentFiles() Cascade::withRequest($request); }); - $pages = $this->gatherContent(); + $pages = $pages ?? $this->gatherContent(); Partyline::line("Generating {$pages->count()} content files..."); @@ -212,75 +215,6 @@ protected function createContentFiles() return $this; } - protected function createContentFile($url) - { - $request = tap(Request::capture(), function ($request) { - $request->setConfig($this->config); - $this->app->instance('request', $request); - Cascade::withRequest($request); - }); - - $page = collect([Entry::findByUri($url)]) - ->map(function ($content) { - return $this->createPage($content); - }) - ->filter - ->isGeneratable(); - - Partyline::line("Generating content file..."); - - $closures = $this->makeContentGenerationClosures($page, $request); - - $results = $this->tasks->run(...$closures); - - if ($this->anyTasksFailed($results)) { - throw GenerationFailedException::withConsoleMessage("\x1B[1A\x1B[2K"); - } - - $this->taskResults = $this->compileTasksResults($results); - - $this->outputTasksResults(); - - return $this; - } - - protected function createPaginatedFiles($url, $collection, $perPage = 10, $pathPart = 'page') - { - $request = tap(Request::capture(), function ($request) { - $request->setConfig($this->config); - $this->app->instance('request', $request); - Cascade::withRequest($request); - }); - - $total = Entry::query() - ->where('collection', $collection) - ->where('status', 'published') - ->count(); - - $pages = collect(range(1, ceil($total / $perPage))) - ->map(fn ($pageNum) => implode('/', [$url, $pathPart, $pageNum])) - ->map(function ($url) { - $url = Str::start($url, '/'); - return $this->createPage(new Route($url)); - }); - - Partyline::line("Generating paginated content files..."); - - $closures = $this->makeContentGenerationClosures($pages, $request); - - $results = $this->tasks->run(...$closures); - - if ($this->anyTasksFailed($results)) { - throw GenerationFailedException::withConsoleMessage("\x1B[1A\x1B[2K"); - } - - $this->taskResults = $this->compileTasksResults($results); - - $this->outputTasksResults(); - - return $this; - } - protected function anyTasksFailed($results) { return collect($results)->contains(''); @@ -308,12 +242,23 @@ protected function gatherContent() return $pages; } + protected function page(string $url, string $site = null): \Illuminate\Support\Collection + { + return collect([Entry::findByUri(Str::start($url, '/'), $site ?? 'default')]) + ->map(function ($content) { + return $this->createPage($content); + }) + ->filter + ->isGeneratable(); + } + protected function pages() { return collect() ->merge($this->routes()) ->merge($this->urls()) ->merge($this->entries()) + ->merge($this->paginatedEntries()) ->merge($this->terms()) ->merge($this->scopedTerms()) ->values() @@ -418,6 +363,37 @@ protected function entries() ->isGeneratable(); } + protected function paginatedEntries(string $url = null, string $collection = null, int $perPage = 10, string $pageName = 'page') + { + if ($url && $collection) { + $config = [ + $url => [ + 'collection' => $collection, + 'perPage' => $perPage, + 'pageName' => $pageName, + ] + ]; + } + + $paginators = $config ?? $this->config['paginators']; + + foreach ($paginators as $path => $config) { + $total = Entry::query() + ->where('collection', $config['collection']) + ->where('status', 'published') + ->count(); + + $pages = collect(range(1, ceil($total / ($config['perPage'] ?? 10)))) + ->map(fn ($pageNum) => implode('/', [$path, ($config['pageName'] ?? 'page'), $pageNum])) + ->map(function ($url) { + $url = Str::start($url, '/'); + return $this->createPage(new Route($url)); + }); + } + + return $pages; + } + protected function terms() { return Term::all() From caab84ee199496faaa79f09628e6fad327defc3a Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Sun, 25 Jun 2023 15:42:24 +0100 Subject: [PATCH 5/5] Remove redundant param + tidy up --- src/Generator.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Generator.php b/src/Generator.php index 614a309..4d5e7cd 100644 --- a/src/Generator.php +++ b/src/Generator.php @@ -86,7 +86,7 @@ public function addUrls($closure) $this->extraUrls[] = $closure; } - public function generate(array $urls = []) + public function generate() { $this->checkConcurrencySupport(); @@ -105,7 +105,7 @@ public function generate(array $urls = []) $this->page($url) ); - // If this page is is the start of a paginated collected, generate all of the paginated URLs too + // If this page is the start of a paginated collection, generate all the paginated URLs too if (array_key_exists($url, ($this->config['paginators'] ?? []))) { $this->createContentFiles( $this->paginatedEntries($url, ...$this->config['paginators'][$url])