Passed
Push — master ( 4d3d2e...ce241e )
by Jakub
02:43
created

Generator::updateLinks()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 26
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 20
nc 7
nop 3
dl 0
loc 26
ccs 21
cts 21
cp 1
crap 6
rs 8.439
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
 * @method void onBeforeGenerate()
21
 * @method void onCreatePage(string $html, Generator $generator, string $filename)
22
 * @method void onAfterGenerate()
23
 */
24 1
final class Generator {
25 1
  use \Nette\SmartObject;
26
  
27
  /** @var string */
28
  protected $templateFile = __DIR__ . "/template.html";
29
  /** @var string[] */
30
  protected $ignoredFiles = [
31
    "README.md",
32
  ];
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) {
77 1
    if(is_dir($source)) {
78 1
      $this->source = realpath($source);
79
    }
80 1
  }
81
  
82
  public function getOutput(): string {
83 1
    return $this->output;
84
  }
85
  
86
  public function setOutput(string $output) {
87 1
    $this->output = realpath($output);
88 1
  }
89
  
90
  protected function createMetaResolver(): OptionsResolver {
91 1
    $resolver = new OptionsResolver();
92 1
    $resolver->setDefaults([
93 1
      "title" => "",
94
      "styles" => [],
95
      "scripts" => [],
96
    ]);
97 1
    $isArrayOfStrings = function(array $value) {
98 1
      return Validators::everyIs($value, "string");
99 1
    };
100 1
    $resolver->setAllowedTypes("title", "string");
101 1
    $resolver->setAllowedTypes("styles", "array");
102 1
    $resolver->setAllowedValues("styles", $isArrayOfStrings);
103 1
    $resolver->setAllowedTypes("scripts", "array");
104 1
    $resolver->setAllowedValues("scripts", $isArrayOfStrings);
105 1
    return $resolver;
106
  }
107
  
108
  protected function getMetafileName(string $filename): string {
109 1
    return str_replace(".md", ".neon", $filename);
110
  }
111
  
112
  protected function getMeta(string $filename, string &$html): array {
113 1
    $resolver = $this->createMetaResolver();
114 1
    $metaFilename = $this->getMetafileName($filename);
115 1
    $meta = [];
116 1
    if(file_exists($metaFilename)) {
117 1
      $meta = Neon::decode(file_get_contents($metaFilename));
118
    }
119 1
    $result = $resolver->resolve($meta);
120 1
    foreach($this->metaNormalizers as $normalizer) {
121 1
      $normalizer($result, $html, $filename);
122
    }
123 1
    return $result;
124
  }
125
  
126
  protected function addAsset(string $asset): void {
127 1
    $asset = realpath($asset);
128 1
    if(!in_array($asset, $this->assets, true)) {
129 1
      $this->assets[] = $asset;
130
    }
131 1
  }
132
  
133
  protected function normalizeTitle(array &$meta, string &$html, string $filename): void {
134 1
    if(strlen($meta["title"]) === 0) {
135 1
      unset($meta["title"]);
136 1
      $html = str_replace("
137 1
  <title>%%title%%</title>", "", $html);
138
    }
139 1
  }
140
  
141
  protected function removeInvalidFiles(array &$input, string $basePath): void {
142 1
    $input = array_filter($input, function($value) use($basePath) {
143 1
      return file_exists("$basePath/$value");
144 1
    });
145 1
  }
146
  
147
  protected function normalizeStyles(array &$meta, string &$html, string $filename): void {
148 1
    $basePath = dirname($filename);
149 1
    $this->removeInvalidFiles($meta["styles"], $basePath);
150 1
    if(count($meta["styles"]) === 0) {
151 1
      unset($meta["styles"]);
152 1
      $html = str_replace("
153 1
  %%styles%%", "", $html);
154 1
      return;
155
    }
156 1
    array_walk($meta["styles"], function(&$value) use($basePath) {
157 1
      $this->addAsset("$basePath/$value");
158 1
      $value = "<link rel=\"stylesheet\" type=\"text/css\" href=\"$value\">";
159 1
    });
160 1
    $meta["styles"] = implode("\n  ", $meta["styles"]);
161 1
  }
162
  
163
  protected function normalizeScripts(array &$meta, string &$html, string $filename): void {
164 1
    $basePath = dirname($filename);
165 1
    $this->removeInvalidFiles($meta["scripts"], $basePath);
166 1
    if(count($meta["scripts"]) === 0) {
167 1
      unset($meta["scripts"]);
168 1
      $html = str_replace("
169 1
  %%scripts%%", "", $html);
170 1
      return;
171
    }
172 1
    array_walk($meta["scripts"], function(&$value) use($basePath) {
173 1
      $this->addAsset("$basePath/$value");
174 1
      $value = "<script type=\"text/javascript\" src=\"$value\"></script>";
175 1
    });
176 1
    $meta["scripts"] = implode("\n  ", $meta["scripts"]);
177 1
  }
178
  
179
  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...
180 1
    $dom = new \DOMDocument();
181 1
    set_error_handler(function($errno) {
182 1
      return $errno === E_WARNING;
183 1
    });
184 1
    $dom->loadHTML($html);
185 1
    restore_error_handler();
186 1
    $links = $dom->getElementsByTagName("a");
187
    /** @var \DOMElement $link */
188 1
    foreach($links as $link) {
189 1
      $oldContent = $dom->saveHTML($link);
190 1
      $needsUpdate = false;
191 1
      $target = $link->getAttribute("href");
192 1
      $target = dirname($filename) .  "/" . $target;
193 1
      foreach($this->filesToProcess as $file) {
194 1
        if($target === $file->getRealPath() AND Strings::endsWith($target, ".md")) {
195 1
          $needsUpdate = true;
196 1
          continue;
197
        }
198
      }
199 1
      if(!$needsUpdate) {
200 1
        continue;
201
      }
202 1
      $link->setAttribute("href", str_replace(".md", ".html", $link->getAttribute("href")));
203 1
      $newContent = $dom->saveHTML($link);
204 1
      $html = str_replace($oldContent, $newContent, $html);
205
    }
206 1
  }
207
  
208
  protected function createMarkdownParser(): \cebe\markdown\Markdown {
209 1
    return new MarkdownParser();
210
  }
211
  
212
  protected function createHtml(string $filename): string {
213 1
    $parser = $this->createMarkdownParser();
214 1
    $source = $parser->parse(file_get_contents($filename));
215 1
    $html = file_get_contents($this->templateFile);
216 1
    $html = str_replace("%%source%%", $source, $html);
217 1
    return $html;
218
  }
219
  
220
  /**
221
   * @internal
222
   * @return Finder|\SplFileInfo[]
223
   */
224
  public function getFilesToProcess(): Finder {
225 1
    $this->filesToProcess = Finder::findFiles("*.md")
1 ignored issue
show
Bug introduced by
The property filesToProcess is declared read-only in Nexendrie\SiteGenerator\Generator.
Loading history...
226 1
      ->exclude($this->ignoredFiles)
227 1
      ->from($this->source)
228 1
      ->exclude($this->ignoredFolders);
229 1
    return $this->filesToProcess;
230
  }
231
  
232
  /**
233
   * @internal
234
   */
235
  public function clearOutputFolder(): void {
236 1
    FileSystem::delete($this->output);
237 1
  }
238
  
239
  /**
240
   * @internal
241
   */
242
  public function copyAssets(): void {
243 1
    foreach($this->assets as $asset) {
244 1
      $path = str_replace($this->source, "", $asset);
245 1
      $target = "$this->output$path";
246 1
      FileSystem::copy($asset, $target);
247 1
      echo "Copied $path";
248
    }
249 1
  }
250
  
251
  /**
252
   * @internal
253
   */
254
  public function processImages(string $html, self $generator, string $filename): void {
255 1
    $dom = new \DOMDocument();
256 1
    $dom->loadHTML($html);
257 1
    $images = $dom->getElementsByTagName("img");
258
    /** @var \DOMElement $image */
259 1
    foreach($images as $image) {
260 1
      $path = dirname($filename) . "/" . $image->getAttribute("src");
261 1
      if(file_exists($path)) {
262 1
        $generator->addAsset($path);
263
      }
264
    }
265 1
  }
266
  
267
  /**
268
   * Generate the site
269
   */
270
  public function generate(): void {
271 1
    $this->onBeforeGenerate();
272 1
    foreach($this->filesToProcess as $file) {
273 1
      $path = str_replace($this->source, "", dirname($file->getRealPath()));
274 1
      $html = $this->createHtml($file->getRealPath());
275 1
      $meta = $this->getMeta($file->getRealPath(), $html);
276 1
      foreach($meta as $key => $value) {
277 1
        $html = str_replace("%%$key%%", $value, $html);
278
      }
279 1
      $basename = $file->getBasename(".md") . ".html";
280 1
      $filename = "$this->output$path/$basename";
281 1
      FileSystem::write($filename, $html);
282 1
      echo "Created $path/$basename\n";
283 1
      $this->onCreatePage($html, $this, $file->getRealPath());
284
    }
285 1
    $this->onAfterGenerate();
286 1
  }
287
}
288
?>