Passed
Branch master (2e3595)
by Jakub
02:01
created

Generator.php$0 ➔ parse()   A

Complexity

Conditions 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
rs 9.4285
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
12
/**
13
 * Generator
14
 *
15
 * @author Jakub Konečný
16
 * @property string $source
17
 * @property string $output
18
 * @method void onBeforeGenerate()
19
 * @method void onCreatePage(string $html, Generator $generator, string $filename)
20
 * @method void onAfterGenerate()
21
 */
22 1
final class Generator {
23 1
  use \Nette\SmartObject;
24
  
25
  /** @var string */
26
  protected $templateFile = __DIR__ . "/template.html";
27
  /** @var string[] */
28
  protected $ignoredFiles = [
29
    "README.md",
30
  ];
31
  /** @var string[] */
32
  protected $ignoredFolders = [
33
    "vendor", ".git", "tests",
34
  ];
35
  /** @var string */
36
  protected $source;
37
  /** @var string */
38
  protected $output;
39
  /** @var string[] */
40
  protected $assets = [];
41
  /** @var callable[] */
42
  protected $metaNormalizers = [];
43
  /** @var callable[] */
44
  public $onBeforeGenerate = [];
45
  /** @var callable[] */
46
  public $onCreatePage = [];
47
  /** @var callable[] */
48
  public $onAfterGenerate = [];
49
  
50
  public function __construct(string $source, string $output) {
51 1
    $this->setSource($source);
52 1
    FileSystem::createDir($output);
53 1
    $this->setOutput($output);
54 1
    $this->onBeforeGenerate[] = [$this, "clearOutputFolder"];
55 1
    $this->onCreatePage[] = [$this, "processImages"];
56 1
    $this->onAfterGenerate[] = [$this, "copyAssets"];
57 1
    $this->addMetaNormalizer([$this, "normalizeTitle"]);
58 1
    $this->addMetaNormalizer([$this, "normalizeStyles"]);
59 1
    $this->addMetaNormalizer([$this, "normalizeScripts"]);
60 1
  }
61
  
62
  public function addMetaNormalizer(callable $callback): void {
63 1
    $this->metaNormalizers[] = $callback;
64 1
  }
65
  
66
  public function getSource(): string {
67 1
    return $this->source;
68
  }
69
  
70
  public function setSource(string $source) {
71 1
    if(is_dir($source)) {
72 1
      $this->source = realpath($source);
73
    }
74 1
  }
75
  
76
  public function getOutput(): string {
77 1
    return $this->output;
78
  }
79
  
80
  public function setOutput(string $output) {
81 1
    $this->output = realpath($output);
82 1
  }
83
  
84
  protected function createMetaResolver(): OptionsResolver {
85 1
    $resolver = new OptionsResolver();
86 1
    $resolver->setDefaults([
87 1
      "title" => "",
88
      "styles" => [],
89
      "scripts" => [],
90
    ]);
91 1
    $isArrayOfStrings = function(array $value) {
92 1
      return Validators::everyIs($value, "string");
93 1
    };
94 1
    $resolver->setAllowedTypes("title", "string");
95 1
    $resolver->setAllowedTypes("styles", "array");
96 1
    $resolver->setAllowedValues("styles", $isArrayOfStrings);
97 1
    $resolver->setAllowedTypes("scripts", "array");
98 1
    $resolver->setAllowedValues("scripts", $isArrayOfStrings);
99 1
    return $resolver;
100
  }
101
  
102
  protected function getMetafileName(string $filename): string {
103 1
    return str_replace(".md", ".neon", $filename);
104
  }
105
  
106
  protected function getMeta(string $filename, string &$html): array {
107 1
    $resolver = $this->createMetaResolver();
108 1
    $metaFilename = $this->getMetafileName($filename);
109 1
    $meta = [];
110 1
    if(file_exists($metaFilename)) {
111 1
      $meta = Neon::decode(file_get_contents($metaFilename));
112
    }
113 1
    $result = $resolver->resolve($meta);
114 1
    foreach($this->metaNormalizers as $normalizer) {
115 1
      $normalizer($result, $html, $filename);
116
    }
117 1
    return $result;
118
  }
119
  
120
  protected function addAsset(string $asset): void {
121 1
    $asset = realpath($asset);
122 1
    if(!in_array($asset, $this->assets, true)) {
123 1
      $this->assets[] = $asset;
124
    }
125 1
  }
126
  
127
  protected function normalizeTitle(array &$meta, string &$html, string $filename): void {
128 1
    if(strlen($meta["title"]) === 0) {
129 1
      unset($meta["title"]);
130 1
      $html = str_replace("
131 1
  <title>%%title%%</title>", "", $html);
132
    }
133 1
  }
134
  
135
  protected function removeInvalidFiles(array &$input, string $basePath): void {
136 1
    $input = array_filter($input, function($value) use($basePath) {
137 1
      return file_exists("$basePath/$value");
138 1
    });
139 1
  }
140
  
141 View Code Duplication
  protected function normalizeStyles(array &$meta, string &$html, string $filename): void {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
142 1
    $basePath = dirname($filename);
143 1
    $this->removeInvalidFiles($meta["styles"], $basePath);
144 1
    if(count($meta["styles"]) === 0) {
145 1
      unset($meta["styles"]);
146 1
      $html = str_replace("
147 1
  %%styles%%", "", $html);
148 1
      return;
149
    }
150 1
    array_walk($meta["styles"], function(&$value) use($basePath) {
151 1
      $this->addAsset("$basePath/$value");
152 1
      $value = "<link rel=\"stylesheet\" type=\"text/css\" href=\"$value\">";
153 1
    });
154 1
    $meta["styles"] = implode("\n  ", $meta["styles"]);
155 1
  }
156
  
157 View Code Duplication
  protected function normalizeScripts(array &$meta, string &$html, string $filename): void {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
158 1
    $basePath = dirname($filename);
159 1
    $this->removeInvalidFiles($meta["scripts"], $basePath);
160 1
    if(count($meta["scripts"]) === 0) {
161 1
      unset($meta["scripts"]);
162 1
      $html = str_replace("
163 1
  %%scripts%%", "", $html);
164 1
      return;
165
    }
166 1
    array_walk($meta["scripts"], function(&$value) use($basePath) {
167 1
      $this->addAsset("$basePath/$value");
168 1
      $value = "<script type=\"text/javascript\" src=\"$value\"></script>";
169 1
    });
170 1
    $meta["scripts"] = implode("\n  ", $meta["scripts"]);
171 1
  }
172
  
173
  protected function createMarkdownParser(): \cebe\markdown\Markdown {
174 1
    return new MarkdownParser();
175
  }
176
  
177
  protected function createHtml(string $filename): string {
178 1
    $parser = $this->createMarkdownParser();
179 1
    $source = $parser->parse(file_get_contents($filename));
180 1
    $html = file_get_contents($this->templateFile);
181 1
    $html = str_replace("%%source%%", $source, $html);
182 1
    return $html;
183
  }
184
  
185
  /**
186
   * @internal
187
   */
188
  public function clearOutputFolder(): void {
189 1
    FileSystem::delete($this->output);
190 1
  }
191
  
192
  /**
193
   * @internal
194
   */
195
  public function copyAssets(): void {
196 1
    foreach($this->assets as $asset) {
197 1
      $path = str_replace($this->source, "", $asset);
198 1
      $target = "$this->output$path";
199 1
      FileSystem::copy($asset, $target);
200 1
      echo "Copied $path";
201
    }
202 1
  }
203
  
204
  /**
205
   * @internal
206
   */
207
  public function processImages(string $html, self $generator, string $filename): void {
208 1
    $dom = new \DOMDocument();
209 1
    $dom->loadHTML($html);
210 1
    $images = $dom->getElementsByTagName("img");
211
    /** @var \DOMElement $image */
212 1
    foreach($images as $image) {
213 1
      $path = dirname($filename) . "/" . $image->getAttribute("src");
214 1
      if(file_exists($path)) {
215 1
        $generator->addAsset($path);
216
      }
217
    }
218 1
  }
219
  
220
  /**
221
   * Generate the site
222
   */
223
  public function generate(): void {
224 1
    $this->onBeforeGenerate();
225 1
    $files = Finder::findFiles("*.md")
226 1
      ->exclude($this->ignoredFiles)
227 1
      ->from($this->source)
228 1
      ->exclude($this->ignoredFolders);
229
    /** @var \SplFileInfo $file */
230 1
    foreach($files as $file) {
231 1
      $path = str_replace($this->source, "", dirname($file->getRealPath()));
232 1
      $html = $this->createHtml($file->getRealPath());
233 1
      $meta = $this->getMeta($file->getRealPath(), $html);
234 1
      foreach($meta as $key => $value) {
235 1
        $html = str_replace("%%$key%%", $value, $html);
236
      }
237 1
      $basename = $file->getBasename(".md") . ".html";
238 1
      $filename = "$this->output$path/$basename";
239 1
      FileSystem::write($filename, $html);
240 1
      echo "Created $path/$basename\n";
241 1
      $this->onCreatePage($html, $this, $file->getRealPath());
242
    }
243 1
    $this->onAfterGenerate();
244 1
  }
245
}
246
?>