Completed
Push — master ( 169cd3...acbf97 )
by Jakub
03:56
created

Generator::getMetafileName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
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 cebe\markdown\GithubMarkdown,
7
    Nette\Utils\Finder,
8
    Nette\Neon\Neon,
9
    Nette\Utils\FileSystem,
10
    Symfony\Component\OptionsResolver\OptionsResolver,
11
    Nette\Utils\Validators;
12
13
/**
14
 * Generator
15
 *
16
 * @author Jakub Konečný
17
 * @property string $source
18
 * @property string $output
19
 * @method void onBeforeGenerate()
20
 * @method void onCreatePage(string $html, Generator $generator, string $filename)
21
 * @method void onAfterGenerate()
22
 */
23 1
class Generator {
24 1
  use \Nette\SmartObject;
25
  
26
  /** @var string */
27
  protected $templateFile = __DIR__ . "/template.html";
28
  /** @var string[] */
29
  protected $ignoredFiles = [
30
    "README.md",
31
  ];
32
  /** @var string[] */
33
  protected $ignoredFolders = [
34
    "vendor", ".git", "tests",
35
  ];
36
  /** @var string */
37
  protected $source;
38
  /** @var string */
39
  protected $output;
40
  /** @var string[] */
41
  protected $assets = [];
42
  /** @var callable[] */
43
  protected $metaNormalizers = [];
44
  /** @var callable[] */
45
  public $onBeforeGenerate = [];
46
  /** @var callable[] */
47
  public $onCreatePage = [];
48
  /** @var callable[] */
49
  public $onAfterGenerate = [];
50
  
51
  public function __construct(string $source, string $output) {
52 1
    $this->setSource($source);
53 1
    FileSystem::createDir($output);
54 1
    $this->setOutput($output);
55 1
    $this->onBeforeGenerate[] = [$this, "clearOutputFolder"];
56 1
    $this->onCreatePage[] = [$this, "processImages"];
57 1
    $this->onAfterGenerate[] = [$this, "copyAssets"];
58 1
    $this->addMetaNormalizer([$this, "normalizeTitle"]);
59 1
    $this->addMetaNormalizer([$this, "normalizeStyles"]);
60 1
    $this->addMetaNormalizer([$this, "normalizeScripts"]);
61 1
  }
62
  
63
  public function addMetaNormalizer(callable $callback): void {
64 1
    $this->metaNormalizers[] = $callback;
65 1
  }
66
  
67
  public function getSource(): string {
68 1
    return $this->source;
69
  }
70
  
71
  public function setSource(string $source) {
72 1
    if(is_dir($source)) {
73 1
      $this->source = realpath($source);
74
    }
75 1
  }
76
  
77
  public function getOutput(): string {
78 1
    return $this->output;
79
  }
80
  
81
  public function setOutput(string $output) {
82 1
    $this->output = realpath($output);
83 1
  }
84
  
85
  protected function createMetaResolver(): OptionsResolver {
86 1
    $resolver = new OptionsResolver();
87 1
    $resolver->setDefaults([
88 1
      "title" => "",
89
      "styles" => [],
90
      "scripts" => [],
91
    ]);
92 1
    $isArrayOfStrings = function(array $value) {
93 1
      return Validators::everyIs($value, "string");
94
    };
95 1
    $resolver->setAllowedTypes("title", "string");
96 1
    $resolver->setAllowedTypes("styles", "array");
97 1
    $resolver->setAllowedValues("styles", $isArrayOfStrings);
98 1
    $resolver->setAllowedTypes("scripts", "array");
99 1
    $resolver->setAllowedValues("scripts", $isArrayOfStrings);
100 1
    return $resolver;
101
  }
102
  
103
  protected function getMetafileName(string $filename): string {
104 1
    return str_replace(".md", ".neon", $filename);
105
  }
106
  
107
  protected function getMeta(string $filename, string &$html): array {
108 1
    $resolver = $this->createMetaResolver();
109 1
    $metaFilename = $this->getMetafileName($filename);
110 1
    $meta = [];
111 1
    if(file_exists($metaFilename)) {
112 1
      $meta = Neon::decode(file_get_contents($metaFilename));
113
    }
114 1
    $result = $resolver->resolve($meta);
115 1
    foreach($this->metaNormalizers as $normalizer) {
116 1
      $normalizer($result, $html, $filename);
117
    }
118 1
    return $result;
119
  }
120
  
121
  protected function addAsset(string $asset): void {
122 1
    $asset = realpath($asset);
123 1
    if(!in_array($asset, $this->assets)) {
124 1
      $this->assets[] = $asset;
125
    }
126 1
  }
127
  
128
  protected function normalizeTitle(array &$meta, string &$html, string $filename): void {
129 1
    if(strlen($meta["title"]) === 0) {
130 1
      unset($meta["title"]);
131 1
      $html = str_replace("
132 1
  <title>%%title%%</title>", "", $html);
133
    }
134 1
  }
135
  
136
  protected function removeInvalidFiles(array &$input, string $basePath): void {
137 1
    $input = array_filter($input, function($value) use($basePath) {
138 1
      return file_exists("$basePath/$value");
139 1
    });
140 1
  }
141
  
142 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...
143 1
    $basePath = dirname($filename);
144 1
    $this->removeInvalidFiles($meta["styles"], $basePath);
145 1
    if(!count($meta["styles"])) {
146 1
      unset($meta["styles"]);
147 1
      $html = str_replace("
148 1
  %%styles%%", "", $html);
149 1
      return;
150
    }
151 1
    array_walk($meta["styles"], function(&$value) use($basePath) {
152 1
      $this->addAsset("$basePath/$value");
153 1
      $value = "<link rel=\"stylesheet\" type=\"text/css\" href=\"$value\">";
154 1
    });
155 1
    $meta["styles"] = implode("\n  ", $meta["styles"]);
156 1
  }
157
  
158 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...
159 1
    $basePath = dirname($filename);
160 1
    $this->removeInvalidFiles($meta["scripts"], $basePath);
161 1
    if(!count($meta["scripts"])) {
162 1
      unset($meta["scripts"]);
163 1
      $html = str_replace("
164 1
  %%scripts%%", "", $html);
165 1
      return;
166
    }
167 1
    array_walk($meta["scripts"], function(&$value) use($basePath) {
168 1
      $this->addAsset("$basePath/$value");
169 1
      $value = "<script type=\"text/javascript\" src=\"$value\"></script>";
170 1
    });
171 1
    $meta["scripts"] = implode("\n  ", $meta["scripts"]);
172 1
  }
173
  
174
  protected function createMarkdownParser(): \cebe\markdown\Markdown {
175 1
    $parser = new class extends GithubMarkdown {
176
      public function parse($text): string {
177 1
        $markup = parent::parse($text);
178 1
        if(substr($markup, -1) === PHP_EOL) {
179 1
          $markup = substr($markup, 0, -1);
180
        }
181 1
        return $markup;
182
      }
183
    };
184 1
    $parser->html5 = $parser->keepListStartNumber = $parser->enableNewlines = true;
185 1
    return $parser;
186
  }
187
  
188
  protected function createHtml(string $filename): string {
189 1
    $parser = $this->createMarkdownParser();
190 1
    $source = $parser->parse(file_get_contents($filename));
191 1
    $html = file_get_contents($this->templateFile);
192 1
    $html = str_replace("%%source%%", $source, $html);
193 1
    return $html;
194
  }
195
  
196
  /**
197
   * @internal
198
   */
199
  public function clearOutputFolder(): void {
200 1
    FileSystem::delete($this->output);
201 1
  }
202
  
203
  /**
204
   * @internal
205
   */
206
  public function copyAssets(): void {
207 1
    foreach($this->assets as $asset) {
208 1
      $path = str_replace($this->source, "", $asset);
209 1
      $target = "$this->output$path";
210 1
      FileSystem::copy($asset, $target);
211 1
      echo "Copied $path";
212
    }
213 1
  }
214
  
215
  /**
216
   * @internal
217
   */
218
  public function processImages(string $html, self $generator, string $filename): void {
219 1
    $dom = new \DOMDocument();
220 1
    $dom->loadHTML($html);
221 1
    $images = $dom->getElementsByTagName("img");
222
    /** @var \DOMElement $image */
223 1
    foreach($images as $image) {
224 1
      $path = dirname($filename) . "/" . $image->getAttribute("src");
225 1
      if(file_exists($path)) {
226 1
        $generator->addAsset($path);
227
      }
228
    }
229 1
  }
230
  
231
  /**
232
   * Generate the site
233
   */
234
  public function generate(): void {
235 1
    $this->onBeforeGenerate();
236 1
    $files = Finder::findFiles("*.md")
237 1
      ->exclude($this->ignoredFiles)
238 1
      ->from($this->source)
239 1
      ->exclude($this->ignoredFolders);
240
    /** @var \SplFileInfo $file */
241 1
    foreach($files as $file) {
242 1
      $path = str_replace($this->source, "", dirname($file->getRealPath()));
243 1
      $html = $this->createHtml($file->getRealPath());
244 1
      $meta = $this->getMeta($file->getRealPath(), $html);
245 1
      foreach($meta as $key => $value) {
246 1
        $html = str_replace("%%$key%%", $value, $html);
247
      }
248 1
      $basename = $file->getBasename(".md") . ".html";
249 1
      $filename = "$this->output$path/$basename";
250 1
      FileSystem::write($filename, $html);
251 1
      echo "Created $path/$basename\n";
252 1
      $this->onCreatePage($html, $this, $file->getRealPath());
253
    }
254 1
    $this->onAfterGenerate();
255 1
  }
256
}
257
?>