Completed
Push — master ( ce241e...dbd2bb )
by Jakub
03:36
created

Generator::setIgnoredFiles()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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