Completed
Push — master ( b36824...9ef7dd )
by Jakub
02:42
created

Generator::setIgnoredFiles()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2.1481

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 4
ccs 2
cts 3
cp 0.6667
crap 2.1481
rs 10
c 0
b 0
f 0
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
  }
67
  
68
  public function addMetaNormalizer(callable $callback): void {
69 1
    $this->metaNormalizers[] = $callback;
70 1
  }
71
  
72
  public function getSource(): string {
73 1
    return $this->source;
74
  }
75
  
76
  public function setSource(string $source): void {
77 1
    if(is_dir($source)) {
78 1
      $this->source = (string) realpath($source);
79
    }
80 1
  }
81
82
  public function getOutput(): string {
83 1
    return $this->output;
84
  }
85
86
  public function setOutput(string $output): void {
87 1
    $this->output = (string) realpath($output);
88 1
  }
89
  
90
  /**
91
   * @return string[]
92
   */
93
  public function getIgnoredFiles(): array {
94 1
    return $this->ignoredFiles;
95
  }
96
  
97
  /**
98
   * @param string[] $ignoredFiles
99
   */
100
  public function setIgnoredFiles(array $ignoredFiles): void {
101 1
    $this->ignoredFiles = [];
102 1
    foreach($ignoredFiles as $ignoredFile) {
103
      $this->ignoredFiles[] = (string) $ignoredFile;
104
    }
105 1
  }
106
  
107
  /**
108
   * @return string[]
109
   */
110
  public function getIgnoredFolders(): array {
111 1
    return $this->ignoredFolders;
112
  }
113
  
114
  /**
115
   * @param string[] $ignoredFolders
116
   */
117
  public function setIgnoredFolders(array $ignoredFolders): void {
118 1
    $this->ignoredFolders = [];
119 1
    foreach($ignoredFolders as $ignoredFolder) {
120 1
      $this->ignoredFolders[] = (string) $ignoredFolder;
121
    }
122 1
  }
123
  
124
  protected function createMetaResolver(): OptionsResolver {
125 1
    $resolver = new OptionsResolver();
126 1
    $resolver->setDefaults([
127 1
      "title" => "",
128
      "styles" => [],
129
      "scripts" => [],
130
    ]);
131 1
    $isArrayOfStrings = function(array $value) {
132 1
      return Validators::everyIs($value, "string");
133 1
    };
134 1
    $resolver->setAllowedTypes("title", "string");
135 1
    $resolver->setAllowedTypes("styles", "array");
136 1
    $resolver->setAllowedValues("styles", $isArrayOfStrings);
137 1
    $resolver->setAllowedTypes("scripts", "array");
138 1
    $resolver->setAllowedValues("scripts", $isArrayOfStrings);
139 1
    return $resolver;
140
  }
141
  
142
  protected function getMetafileName(string $filename): string {
143 1
    return str_replace(".md", ".neon", $filename);
144
  }
145
  
146
  protected function getMeta(string $filename, string &$html): array {
147 1
    $resolver = $this->createMetaResolver();
148 1
    $metaFilename = $this->getMetafileName($filename);
149 1
    $meta = [];
150 1
    if(file_exists($metaFilename)) {
151 1
      $meta = Neon::decode(file_get_contents($metaFilename));
152
    }
153 1
    $result = $resolver->resolve($meta);
154 1
    foreach($this->metaNormalizers as $normalizer) {
155 1
      $normalizer($result, $html, $filename);
156
    }
157 1
    return $result;
158
  }
159
  
160
  protected function addAsset(string $asset): void {
161 1
    $asset = realpath($asset);
162 1
    if(is_string($asset) AND !in_array($asset, $this->assets, true)) {
163 1
      $this->assets[] = $asset;
164
    }
165 1
  }
166
  
167
  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...
168 1
    if(strlen($meta["title"]) === 0) {
169 1
      unset($meta["title"]);
170 1
      $html = str_replace("
171 1
  <title>%%title%%</title>", "", $html);
172
    }
173 1
  }
174
  
175
  protected function removeInvalidFiles(array &$input, string $basePath): void {
176 1
    $input = array_filter($input, function($value) use($basePath) {
177 1
      return file_exists("$basePath/$value");
178 1
    });
179 1
  }
180
  
181
  protected function normalizeStyles(array &$meta, string &$html, string $filename): void {
182 1
    $basePath = dirname($filename);
183 1
    $this->removeInvalidFiles($meta["styles"], $basePath);
184 1
    if(count($meta["styles"]) === 0) {
185 1
      unset($meta["styles"]);
186 1
      $html = str_replace("
187 1
  %%styles%%", "", $html);
188 1
      return;
189
    }
190 1
    array_walk($meta["styles"], function(&$value) use($basePath) {
191 1
      $this->addAsset("$basePath/$value");
192 1
      $value = "<link rel=\"stylesheet\" type=\"text/css\" href=\"$value\">";
193 1
    });
194 1
    $meta["styles"] = implode("\n  ", $meta["styles"]);
195 1
  }
196
  
197
  protected function normalizeScripts(array &$meta, string &$html, string $filename): void {
198 1
    $basePath = dirname($filename);
199 1
    $this->removeInvalidFiles($meta["scripts"], $basePath);
200 1
    if(count($meta["scripts"]) === 0) {
201 1
      unset($meta["scripts"]);
202 1
      $html = str_replace("
203 1
  %%scripts%%", "", $html);
204 1
      return;
205
    }
206 1
    array_walk($meta["scripts"], function(&$value) use($basePath) {
207 1
      $this->addAsset("$basePath/$value");
208 1
      $value = "<script type=\"text/javascript\" src=\"$value\"></script>";
209 1
    });
210 1
    $meta["scripts"] = implode("\n  ", $meta["scripts"]);
211 1
  }
212
  
213
  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...
214 1
    $dom = new \DOMDocument();
215 1
    set_error_handler(function($errno) {
216 1
      return $errno === E_WARNING;
217 1
    });
218 1
    $dom->loadHTML($html);
219 1
    restore_error_handler();
220 1
    $links = $dom->getElementsByTagName("a");
221
    /** @var \DOMElement $link */
222 1
    foreach($links as $link) {
223 1
      $oldContent = $dom->saveHTML($link);
224 1
      $needsUpdate = false;
225 1
      $target = $link->getAttribute("href");
226 1
      $target = dirname($filename) .  "/" . $target;
227 1
      foreach($this->filesToProcess as $file) {
228 1
        if($target === $file->getRealPath() AND Strings::endsWith($target, ".md")) {
229 1
          $needsUpdate = true;
230 1
          continue;
231
        }
232
      }
233 1
      if(!$needsUpdate) {
234 1
        continue;
235
      }
236 1
      $link->setAttribute("href", str_replace(".md", ".html", $link->getAttribute("href")));
237 1
      $newContent = $dom->saveHTML($link);
238 1
      $html = str_replace($oldContent, $newContent, $html);
239
    }
240 1
  }
241
  
242
  protected function createMarkdownParser(): \cebe\markdown\Markdown {
243 1
    return new MarkdownParser();
244
  }
245
  
246
  protected function createHtml(string $filename): string {
247 1
    $parser = $this->createMarkdownParser();
248 1
    $source = $parser->parse(file_get_contents($filename));
249 1
    $html = file_get_contents($this->templateFile);
250 1
    $html = str_replace("%%source%%", $source, $html);
251 1
    return $html;
252
  }
253
  
254
  /**
255
   * @internal
256
   * @return Finder|\SplFileInfo[]
257
   */
258
  public function getFilesToProcess(): Finder {
259 1
    $this->filesToProcess = Finder::findFiles("*.md")
260 1
      ->exclude($this->ignoredFiles)
261 1
      ->from($this->source)
262 1
      ->exclude($this->ignoredFolders);
263 1
    return $this->filesToProcess;
264
  }
265
  
266
  /**
267
   * @internal
268
   */
269
  public function clearOutputFolder(): void {
270 1
    FileSystem::delete($this->output);
271 1
  }
272
  
273
  /**
274
   * @internal
275
   */
276
  public function copyAssets(): void {
277 1
    foreach($this->assets as $asset) {
278 1
      $path = str_replace($this->source, "", $asset);
279 1
      $target = "$this->output$path";
280 1
      FileSystem::copy($asset, $target);
281 1
      echo "Copied $path";
282
    }
283 1
  }
284
  
285
  /**
286
   * @internal
287
   */
288
  public function processImages(string $html, self $generator, string $filename): void {
289 1
    $dom = new \DOMDocument();
290 1
    $dom->loadHTML($html);
291 1
    $images = $dom->getElementsByTagName("img");
292
    /** @var \DOMElement $image */
293 1
    foreach($images as $image) {
294 1
      $path = dirname($filename) . "/" . $image->getAttribute("src");
295 1
      if(file_exists($path)) {
296 1
        $generator->addAsset($path);
297
      }
298
    }
299 1
  }
300
  
301
  /**
302
   * Generate the site
303
   */
304
  public function generate(): void {
305 1
    $this->onBeforeGenerate();
306 1
    foreach($this->filesToProcess as $file) {
307 1
      $path = str_replace($this->source, "", dirname($file->getRealPath()));
308 1
      $html = $this->createHtml($file->getRealPath());
309 1
      $meta = $this->getMeta($file->getRealPath(), $html);
310 1
      foreach($meta as $key => $value) {
311 1
        $html = str_replace("%%$key%%", $value, $html);
312
      }
313 1
      $basename = $file->getBasename(".md") . ".html";
314 1
      $filename = "$this->output$path/$basename";
315 1
      FileSystem::write($filename, $html);
316 1
      echo "Created $path/$basename\n";
317 1
      $this->onCreatePage($html, $this, $file->getRealPath());
318
    }
319 1
    $this->onAfterGenerate();
320 1
  }
321
}
322
?>