Skip to content

Commit

Permalink
Support generating individual URLs (#137)
Browse files Browse the repository at this point in the history
Co-authored-by: Jesse Leite <[email protected]>
  • Loading branch information
simonhamp and jesseleite committed Jul 14, 2023
1 parent 87c5909 commit b737c7a
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 28 deletions.
3 changes: 2 additions & 1 deletion src/Commands/StaticSiteGenerate.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class StaticSiteGenerate extends Command
* @var string
*/
protected $signature = 'statamic:ssg:generate
{urls?* : You may provide one or more explicit url arguments, otherwise whole site will be generated }
{--workers= : Speed up site generation significantly by installing spatie/fork and using multiple workers }
{--disable-clear : Disable clearing the destination directory when generating whole site }';

Expand Down Expand Up @@ -62,7 +63,7 @@ public function handle()
$this->generator
->workers($workers ?? 1)
->disableClear($this->option('disable-clear') ?? false)
->generate();
->generate($this->argument('urls') ?: '*');
} catch (GenerationFailedException $e) {
$this->line($e->getConsoleMessage());
$this->error('Static site generation failed.');
Expand Down
81 changes: 54 additions & 27 deletions src/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class Generator
protected $after;
protected $extraUrls;
protected $workers = 1;
protected $earlyTaskErrors = [];
protected $taskResults;
protected $disableClear = false;

Expand Down Expand Up @@ -85,16 +86,20 @@ public function disableClear(bool $disableClear = false)
return $this;
}

public function generate()
public function generate($urls = '*')
{
$this->checkConcurrencySupport();

Site::setCurrent(Site::default()->handle());

if (is_array($urls)) {
$this->disableClear = true;
}

$this
->bindGlide()
->clearDirectory()
->createContentFiles()
->createContentFiles($urls)
->createSymlinks()
->copyFiles()
->outputSummary();
Expand Down Expand Up @@ -178,15 +183,15 @@ public function copyFiles()
return $this;
}

protected function createContentFiles()
protected function createContentFiles($urls = '*')
{
$request = tap(Request::capture(), function ($request) {
$request->setConfig($this->config);
$this->app->instance('request', $request);
Cascade::withRequest($request);
});

$pages = $this->gatherContent();
$pages = $this->gatherContent($urls);

Partyline::line("Generating {$pages->count()} content files...");

Expand Down Expand Up @@ -215,24 +220,30 @@ protected function compileTasksResults(array $results)
$results = collect($results);

return [
'count' => $results->sum('count'),
'count' => count($this->earlyTaskErrors) + $results->sum('count'),
'warnings' => $results->flatMap->warnings,
'errors' => $results->flatMap->errors,
'errors' => collect($this->earlyTaskErrors)->merge($results->flatMap->errors),
];
}

protected function gatherContent()
protected function gatherContent($urls = '*')
{
if (is_array($urls)) {
return collect($urls)
->map(fn ($url) => $this->createPage(new Route($this->makeAbsoluteUrl($url))))
->reject(fn ($page) => $this->shouldRejectPage($page, true));
}

Partyline::line('Gathering content to be generated...');

$pages = $this->pages();
$pages = $this->gatherAllPages();

Partyline::line("\x1B[1A\x1B[2K<info>[✔]</info> Gathered content to be generated");

return $pages;
}

protected function pages()
protected function gatherAllPages()
{
return collect()
->merge($this->routes())
Expand All @@ -241,18 +252,10 @@ protected function pages()
->merge($this->terms())
->merge($this->scopedTerms())
->values()
->unique->url()
->reject(function ($page) {
foreach ($this->config['exclude'] as $url) {
if (Str::endsWith($url, '*')) {
if (Str::is($url, $page->url())) {
return true;
}
}
}

return in_array($page->url(), $this->config['exclude']);
})->shuffle();
->unique
->url()
->reject(fn ($page) => $this->shouldRejectPage($page))
->shuffle();
}

protected function makeContentGenerationClosures($pages, $request)
Expand Down Expand Up @@ -311,7 +314,9 @@ protected function outputTasksResults()
{
$results = $this->taskResults;

Partyline::line("\x1B[1A\x1B[2K<info>[✔]</info> Generated {$results['count']} content files");
$successCount = $results['count'] - $results['errors']->count();

Partyline::line("\x1B[1A\x1B[2K<info>[✔]</info> Generated {$successCount} content files");

$results['warnings']->merge($results['errors'])->each(fn ($error) => Partyline::line($error));
}
Expand Down Expand Up @@ -390,11 +395,9 @@ protected function urls()
$extra[] = '/404';
}

return collect($this->config['urls'] ?? [])->merge($extra)->map(function ($url) {
$url = URL::tidy(Str::start($url, $this->config['base_url'].'/'));

return $this->createPage(new Route($url));
});
return collect($this->config['urls'] ?? [])
->merge($extra)
->map(fn ($url) => $this->createPage(new Route($this->makeAbsoluteUrl($url))));
}

protected function routes()
Expand Down Expand Up @@ -453,4 +456,28 @@ protected function shouldSetCarbonFormat($page)
|| $content instanceof \Statamic\Contracts\Taxonomies\Term
|| $content instanceof StatamicRoute;
}

protected function makeAbsoluteUrl($url)
{
return URL::tidy(Str::start($url, $this->config['base_url'].'/'));
}

protected function shouldRejectPage($page, $outputError = false)
{
foreach ($this->config['exclude'] as $url) {
if (Str::endsWith($url, '*')) {
if (Str::is($url, $page->url())) {
return true;
}
}
}

$excluded = in_array($page->url(), $this->config['exclude']);

if ($excluded && $outputError) {
$this->earlyTaskErrors[] = '<fg=red>[✘]</> '.URL::makeRelative($page->url()).' (Excluded in config/statamic/ssg.php)';
}

return $excluded;
}
}
98 changes: 98 additions & 0 deletions tests/GenerateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,30 @@ public function it_generates_pages_for_site_fixture()
$this->assertStringContainsString('<h1>Article Title: Eight</h1>', $files['articles/eight/index.html']);
}

/** @test */
public function it_generates_specific_pages_when_passing_urls_as_args()
{
$this
->partialMock(Filesystem::class)
->shouldReceive('deleteDirectory')
->with(config('statamic.ssg.destination'), true)
->never();

$files = $this->generate(['urls' => ['/', 'topics', 'articles']]);

$expectedFiles = [
'index.html',
'topics/index.html',
'articles/index.html',
];

$this->assertEqualsCanonicalizing($expectedFiles, array_keys($files));

$this->assertStringContainsString('<h1>Page Title: Home</h1>', $files['index.html']);
$this->assertStringContainsString('<h1>Page Title: Topics</h1>', $files['topics/index.html']);
$this->assertStringContainsString('<h1>Articles Index Page Title</h1>', $files['articles/index.html']);
}

/** @test */
public function it_generates_pages_to_custom_destination()
{
Expand Down Expand Up @@ -256,4 +280,78 @@ public function it_generates_pagination_with_custom_page_name_and_route()
$this->assertStringContainsString('Total Pages: 3', $index);
$this->assertStringContainsString('Prev Link: /articles/p-2', $index);
}

/** @test */
public function it_generates_associated_paginated_pages_when_generating_only_urls_with_pagination()
{
$this->files->put(resource_path('views/articles/index.antlers.html'), <<<'EOT'
{{ collection:articles sort="date:asc" paginate="3" as="articles" }}
{{ articles }}
<a href="{{ permalink }}">{{ title }}</a>
{{ /articles }}
{{ paginate }}
Current Page: {{ current_page }}
Total Pages: {{ total_pages }}
Prev Link: {{ prev_page }}
Next Link: {{ next_page }}
{{ /paginate }}
{{ /collection:articles }}
EOT
);

$this
->partialMock(Filesystem::class)
->shouldReceive('deleteDirectory')
->with(config('statamic.ssg.destination'), true)
->never();

$files = $this->generate(['urls' => ['articles']]);

$expectedArticlesFiles = [
'articles/index.html',
'articles/page/1/index.html',
'articles/page/2/index.html',
'articles/page/3/index.html',
];

$this->assertEqualsCanonicalizing($expectedArticlesFiles, array_keys($files));

// Index assertions on implicit page 1
$index = $files['articles/index.html'];
$this->assertStringContainsStrings(['One', 'Two', 'Three'], $index);
$this->assertStringNotContainsStrings(['Four', 'Five', 'Six'], $index);
$this->assertStringNotContainsStrings(['Seven', 'Eight'], $index);
$this->assertStringContainsString('Current Page: 1', $index);
$this->assertStringContainsString('Total Pages: 3', $index);
$this->assertStringContainsString('Next Link: /articles/page/2', $index);

// Index assertions on explicit page 1
$index = $files['articles/page/1/index.html'];
$this->assertStringContainsStrings(['One', 'Two', 'Three'], $index);
$this->assertStringNotContainsStrings(['Four', 'Five', 'Six'], $index);
$this->assertStringNotContainsStrings(['Seven', 'Eight'], $index);
$this->assertStringContainsString('Current Page: 1', $index);
$this->assertStringContainsString('Total Pages: 3', $index);
$this->assertStringContainsString('Next Link: /articles/page/2', $index);

// Index assertions on page 2
$index = $files['articles/page/2/index.html'];
$this->assertStringNotContainsStrings(['One', 'Two', 'Three'], $index);
$this->assertStringContainsStrings(['Four', 'Five', 'Six'], $index);
$this->assertStringNotContainsStrings(['Seven', 'Eight'], $index);
$this->assertStringContainsString('Current Page: 2', $index);
$this->assertStringContainsString('Total Pages: 3', $index);
$this->assertStringContainsString('Prev Link: /articles/page/1', $index);
$this->assertStringContainsString('Next Link: /articles/page/3', $index);

// Index assertions on page 3
$index = $files['articles/page/3/index.html'];
$this->assertStringNotContainsStrings(['One', 'Two', 'Three'], $index);
$this->assertStringNotContainsStrings(['Four', 'Five', 'Six'], $index);
$this->assertStringContainsStrings(['Seven', 'Eight'], $index);
$this->assertStringContainsString('Current Page: 3', $index);
$this->assertStringContainsString('Total Pages: 3', $index);
$this->assertStringContainsString('Prev Link: /articles/page/2', $index);
}
}
61 changes: 61 additions & 0 deletions tests/Localized/GenerateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Tests\Localized;

use Illuminate\Filesystem\Filesystem;
use Statamic\Facades\Config;
use Tests\Concerns\RunsGeneratorCommand;
use Tests\TestCase;
Expand Down Expand Up @@ -238,4 +239,64 @@ public function it_generates_localized_pagination_with_custom_page_name_and_rout
$this->assertStringContainsString('Total Pages: 2', $index);
$this->assertStringContainsString('Prev Link: /fr/le-articles/p-1', $index);
}

/** @test */
public function it_generates_associated_paginated_pages_when_generating_only_localized_urls_with_pagination()
{
$this->files->put(resource_path('views/articles/index.antlers.html'), <<<'EOT'
{{ collection:articles sort="date:asc" paginate="3" as="articles" }}
{{ articles }}
<a href="{{ permalink }}">{{ title }}</a>
{{ /articles }}
{{ paginate }}
Current Page: {{ current_page }}
Total Pages: {{ total_pages }}
Prev Link: {{ prev_page }}
Next Link: {{ next_page }}
{{ /paginate }}
{{ /collection:articles }}
EOT
);

$this
->partialMock(Filesystem::class)
->shouldReceive('deleteDirectory')
->with(config('statamic.ssg.destination'), true)
->never();

$files = $this->generate(['urls' => ['fr/le-articles']]);

$expectedArticlesFiles = [
'fr/le-articles/index.html',
'fr/le-articles/page/1/index.html',
'fr/le-articles/page/2/index.html',
];

$this->assertEqualsCanonicalizing($expectedArticlesFiles, array_keys($files));

// Index assertions on implicit page 1
$index = $files['fr/le-articles/index.html'];
$this->assertStringContainsStrings(['Le One', 'Le Two', 'Le Three'], $index);
$this->assertStringNotContainsStrings(['Le Four', 'Le Five'], $index);
$this->assertStringContainsString('Current Page: 1', $index);
$this->assertStringContainsString('Total Pages: 2', $index);
$this->assertStringContainsString('Next Link: /fr/le-articles/page/2', $index);

// Index assertions on explicit page 1
$index = $files['fr/le-articles/page/1/index.html'];
$this->assertStringContainsStrings(['Le One', 'Le Two', 'Le Three'], $index);
$this->assertStringNotContainsStrings(['Le Four', 'Le Five'], $index);
$this->assertStringContainsString('Current Page: 1', $index);
$this->assertStringContainsString('Total Pages: 2', $index);
$this->assertStringContainsString('Next Link: /fr/le-articles/page/2', $index);

// Index assertions on page 2
$index = $files['fr/le-articles/page/2/index.html'];
$this->assertStringNotContainsStrings(['Le One', 'Le Two', 'Le Three'], $index);
$this->assertStringContainsStrings(['Le Four', 'Le Five'], $index);
$this->assertStringContainsString('Current Page: 2', $index);
$this->assertStringContainsString('Total Pages: 2', $index);
$this->assertStringContainsString('Prev Link: /fr/le-articles/page/1', $index);
}
}

0 comments on commit b737c7a

Please sign in to comment.