Completed
Branch 3-page-s-language (8b1658)
by Jakub
02:55
created

Generator::addHtmlLanguage()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 2
c 0
b 0
f 0
nc 2
nop 3
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 2
rs 10
1
<?php
2
declare(strict_types=1);
3
4
namespace Nexendrie\SiteGenerator;
5
6
use Nette\Utils\Finder;
7
use Nette\Neon\Neon;
8
use Nette\Utils\FileSystem;
9
use Symfony\Component\OptionsResolver\OptionsResolver;
10
use Nette\Utils\Validators;
11
use Nette\Utils\Strings;
12
13
/**
14
 * Generator
15
 *
16
 * @author Jakub Konečný
17
 * @property string $source
18
 * @property string $output
19
 * @property-read Finder|\SplFileInfo[] $filesToProcess
20
 * @property string[] $ignoredFiles
21
 * @property string[] $ignoredFolders
22
 * @method void onBeforeGenerate()
23
 * @method void onCreatePage(string $html, Generator $generator, string $filename)
24
 * @method void onAfterGenerate()
25
 */
26 1
final class Generator {
27 1
  use \Nette\SmartObject;
28
  
29
  /** @var string */
30
  protected $templateFile = __DIR__ . "/template.html";
31
  /** @var string[] */
32
  protected $ignoredFiles = [];
33
  /** @var string[] */
34
  protected $ignoredFolders = [
35
    "vendor", ".git", "tests",
36
  ];
37
  /** @var string */
38
  protected $source;
39
  /** @var string */
40
  protected $output;
41
  /** @var Finder|\SplFileInfo[] */
42
  protected $filesToProcess;
43
  /** @var string[] */
44
  protected $assets = [];
45
  /** @var callable[] */
46
  protected $metaNormalizers = [];
47
  /** @var callable[] */
48
  public $onBeforeGenerate = [];
49
  /** @var callable[] */
50
  public $onCreatePage = [];
51
  /** @var callable[] */
52
  public $onAfterGenerate = [];
53
  
54
  public function __construct(string $source, string $output) {
55 1
    $this->setSource($source);
56 1
    FileSystem::createDir($output);
57 1
    $this->setOutput($output);
58 1
    $this->onBeforeGenerate[] = [$this, "getFilesToProcess"];
59 1
    $this->onBeforeGenerate[] = [$this, "clearOutputFolder"];
60 1
    $this->onCreatePage[] = [$this, "processImages"];
61 1
    $this->onAfterGenerate[] = [$this, "copyAssets"];
62 1
    $this->addMetaNormalizer([$this, "normalizeTitle"]);
63 1
    $this->addMetaNormalizer([$this, "normalizeStyles"]);
64 1
    $this->addMetaNormalizer([$this, "normalizeScripts"]);
65 1
    $this->addMetaNormalizer([$this, "updateLinks"]);
66 1
    $this->addMetaNormalizer([$this, "addHtmlLanguage"]);
67 1
  }
68
  
69
  public function addMetaNormalizer(callable $callback): void {
70 1
    $this->metaNormalizers[] = $callback;
71 1
  }
72
  
73
  public function getSource(): string {
74 1
    return $this->source;
75
  }
76
  
77
  public function setSource(string $source): void {
78 1
    if(is_dir($source)) {
79 1
      $this->source = (string) realpath($source);
80
    }
81 1
  }
82
83
  public function getOutput(): string {
84 1
    return $this->output;
85
  }
86
87
  public function setOutput(string $output): void {
88 1
    $this->output = (string) realpath($output);
89 1
  }
90
  
91
  /**
92
   * @return string[]
93
   */
94
  public function getIgnoredFiles(): array {
95 1
    return $this->ignoredFiles;
96
  }
97
  
98
  /**
99
   * @param string[] $ignoredFiles
100
   */
101
  public function setIgnoredFiles(array $ignoredFiles): void {
102 1
    $this->ignoredFiles = [];
103 1
    foreach($ignoredFiles as $ignoredFile) {
104
      $this->ignoredFiles[] = (string) $ignoredFile;
105
    }
106 1
  }
107
  
108
  /**
109
   * @return string[]
110
   */
111
  public function getIgnoredFolders(): array {
112 1
    return $this->ignoredFolders;
113
  }
114
  
115
  /**
116
   * @param string[] $ignoredFolders
117
   */
118
  public function setIgnoredFolders(array $ignoredFolders): void {
119 1
    $this->ignoredFolders = [];
120 1
    foreach($ignoredFolders as $ignoredFolder) {
121 1
      $this->ignoredFolders[] = (string) $ignoredFolder;
122
    }
123 1
  }
124
  
125
  protected function createMetaResolver(): OptionsResolver {
126 1
    $resolver = new OptionsResolver();
127 1
    $resolver->setDefaults([
128 1
      "title" => "",
129
      "htmlLang" => "",
130
      "styles" => [],
131
      "scripts" => [],
132
    ]);
133 1
    $isArrayOfStrings = function(array $value) {
134 1
      return Validators::everyIs($value, "string");
135 1
    };
136 1
    $resolver->setAllowedTypes("title", "string");
137 1
    $resolver->setAllowedTypes("htmlLang", "string");
138 1
    $resolver->setAllowedTypes("styles", "array");
139 1
    $resolver->setAllowedValues("styles", $isArrayOfStrings);
140 1
    $resolver->setAllowedTypes("scripts", "array");
141 1
    $resolver->setAllowedValues("scripts", $isArrayOfStrings);
142 1
    return $resolver;
143
  }
144
  
145
  protected function getMetafileName(string $filename): string {
146 1
    return str_replace(".md", ".neon", $filename);
147
  }
148
  
149
  protected function getMeta(string $filename, string &$html): array {
150 1
    $resolver = $this->createMetaResolver();
151 1
    $metaFilename = $this->getMetafileName($filename);
152 1
    $meta = [];
153 1
    if(file_exists($metaFilename)) {
154 1
      $meta = Neon::decode(file_get_contents($metaFilename));
155
    }
156 1
    $result = $resolver->resolve($meta);
157 1
    foreach($this->metaNormalizers as $normalizer) {
158 1
      $normalizer($result, $html, $filename);
159
    }
160 1
    return $result;
161
  }
162
  
163
  protected function addAsset(string $asset): void {
164 1
    $asset = realpath($asset);
165 1
    if(is_string($asset) AND !in_array($asset, $this->assets, true)) {
166 1
      $this->assets[] = $asset;
167
    }
168 1
  }
169
  
170
  protected function normalizeTitle(array &$meta, string &$html, string $filename): void {
1 ignored issue
show
introduced by
The method parameter $filename is never used
Loading history...
171 1
    if(strlen($meta["title"]) === 0) {
172 1
      unset($meta["title"]);
173 1
      $html = str_replace("
174 1
  <title>%%title%%</title>", "", $html);
175
    }
176 1
  }
177
  
178
  protected function removeInvalidFiles(array &$input, string $basePath): void {
179 1
    $input = array_filter($input, function($value) use($basePath) {
180 1
      return file_exists("$basePath/$value");
181 1
    });
182 1
  }
183
  
184
  protected function normalizeStyles(array &$meta, string &$html, string $filename): void {
185 1
    $basePath = dirname($filename);
186 1
    $this->removeInvalidFiles($meta["styles"], $basePath);
187 1
    if(count($meta["styles"]) === 0) {
188 1
      unset($meta["styles"]);
189 1
      $html = str_replace("
190 1
  %%styles%%", "", $html);
191 1
      return;
192
    }
193 1
    array_walk($meta["styles"], function(&$value) use($basePath) {
194 1
      $this->addAsset("$basePath/$value");
195 1
      $value = "<link rel=\"stylesheet\" type=\"text/css\" href=\"$value\">";
196 1
    });
197 1
    $meta["styles"] = implode("\n  ", $meta["styles"]);
198 1
  }
199
  
200
  protected function normalizeScripts(array &$meta, string &$html, string $filename): void {
201 1
    $basePath = dirname($filename);
202 1
    $this->removeInvalidFiles($meta["scripts"], $basePath);
203 1
    if(count($meta["scripts"]) === 0) {
204 1
      unset($meta["scripts"]);
205 1
      $html = str_replace("
206 1
  %%scripts%%", "", $html);
207 1
      return;
208
    }
209 1
    array_walk($meta["scripts"], function(&$value) use($basePath) {
210 1
      $this->addAsset("$basePath/$value");
211 1
      $value = "<script type=\"text/javascript\" src=\"$value\"></script>";
212 1
    });
213 1
    $meta["scripts"] = implode("\n  ", $meta["scripts"]);
214 1
  }
215
  
216
  protected function updateLinks(array &$meta, string &$html, string $filename): void {
1 ignored issue
show
introduced by
The method parameter $meta is never used
Loading history...
217 1
    $dom = new \DOMDocument();
218 1
    set_error_handler(function($errno) {
219 1
      return $errno === E_WARNING;
220 1
    });
221 1
    $dom->loadHTML($html);
222 1
    restore_error_handler();
223 1
    $links = $dom->getElementsByTagName("a");
224
    /** @var \DOMElement $link */
225 1
    foreach($links as $link) {
226 1
      $oldContent = $dom->saveHTML($link);
227 1
      $needsUpdate = false;
228 1
      $target = $link->getAttribute("href");
229 1
      $target = dirname($filename) .  "/" . $target;
230 1
      foreach($this->filesToProcess as $file) {
231 1
        if($target === $file->getRealPath() AND Strings::endsWith($target, ".md")) {
232 1
          $needsUpdate = true;
233 1
          continue;
234
        }
235
      }
236 1
      if(!$needsUpdate) {
237 1
        continue;
238
      }
239 1
      $link->setAttribute("href", str_replace(".md", ".html", $link->getAttribute("href")));
240 1
      $newContent = $dom->saveHTML($link);
241 1
      $html = str_replace($oldContent, $newContent, $html);
242
    }
243 1
  }
244
245
  protected function addHtmlLanguage(array &$meta, string &$html, string $filename): void {
2 ignored issues
show
Unused Code introduced by
The parameter $filename is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

245
  protected function addHtmlLanguage(array &$meta, string &$html, /** @scrutinizer ignore-unused */ string $filename): void {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
introduced by
The method parameter $filename is never used
Loading history...
246 1
    if(strlen($meta["htmlLang"]) > 0) {
247 1
      $html = str_replace("<html>", "<html lang=\"{$meta["htmlLang"]}\">", $html);
248
    }
249 1
  }
250
  
251
  protected function createMarkdownParser(): \cebe\markdown\Markdown {
252 1
    return new MarkdownParser();
253
  }
254
  
255
  protected function createHtml(string $filename): string {
256 1
    $parser = $this->createMarkdownParser();
257 1
    $source = $parser->parse(file_get_contents($filename));
258 1
    $html = file_get_contents($this->templateFile);
259 1
    $html = str_replace("%%source%%", $source, $html);
260 1
    return $html;
261
  }
262
  
263
  /**
264
   * @internal
265
   * @return Finder|\SplFileInfo[]
266
   */
267
  public function getFilesToProcess(): Finder {
268 1
    $this->filesToProcess = Finder::findFiles("*.md")
269 1
      ->exclude($this->ignoredFiles)
270 1
      ->from($this->source)
271 1
      ->exclude($this->ignoredFolders);
272 1
    return $this->filesToProcess;
273
  }
274
  
275
  /**
276
   * @internal
277
   */
278
  public function clearOutputFolder(): void {
279 1
    FileSystem::delete($this->output);
280 1
  }
281
  
282
  /**
283
   * @internal
284
   */
285
  public function copyAssets(): void {
286 1
    foreach($this->assets as $asset) {
287 1
      $path = str_replace($this->source, "", $asset);
288 1
      $target = "$this->output$path";
289 1
      FileSystem::copy($asset, $target);
290 1
      echo "Copied $path";
291
    }
292 1
  }
293
  
294
  /**
295
   * @internal
296
   */
297
  public function processImages(string $html, self $generator, string $filename): void {
298 1
    $dom = new \DOMDocument();
299 1
    $dom->loadHTML($html);
300 1
    $images = $dom->getElementsByTagName("img");
301
    /** @var \DOMElement $image */
302 1
    foreach($images as $image) {
303 1
      $path = dirname($filename) . "/" . $image->getAttribute("src");
304 1
      if(file_exists($path)) {
305 1
        $generator->addAsset($path);
306
      }
307
    }
308 1
  }
309
  
310
  /**
311
   * Generate the site
312
   */
313
  public function generate(): void {
314 1
    $this->onBeforeGenerate();
315 1
    foreach($this->filesToProcess as $file) {
316 1
      $path = str_replace($this->source, "", dirname($file->getRealPath()));
317 1
      $html = $this->createHtml($file->getRealPath());
318 1
      $meta = $this->getMeta($file->getRealPath(), $html);
319 1
      foreach($meta as $key => $value) {
320 1
        $html = str_replace("%%$key%%", $value, $html);
321
      }
322 1
      $basename = $file->getBasename(".md") . ".html";
323 1
      $filename = "$this->output$path/$basename";
324 1
      FileSystem::write($filename, $html);
325 1
      echo "Created $path/$basename\n";
326 1
      $this->onCreatePage($html, $this, $file->getRealPath());
327
    }
328 1
    $this->onAfterGenerate();
329 1
  }
330
}
331
?>