static generator
[mdref/mdref] / mdref / Generator.php
1 <?php
2
3 namespace mdref;
4
5 /**
6 * Static mdref generator
7 */
8 class Generator
9 {
10 /**
11 * @var \mdref\Reference
12 */
13 private $reference;
14
15 /**
16 * @var \mdref\Generator\Renderer
17 */
18 private $renderer;
19
20 /**
21 * Create a new generator
22 * @param string $refs list of reference paths
23 * @param string $dir output directory
24 */
25 public function __construct($refs, $dir = null) {
26 $this->reference = new Reference(explode(PATH_SEPARATOR, $refs));
27 $this->renderer = new Generator\Renderer($dir ?: "public/static");
28 }
29
30 /**
31 * Run the generator
32 */
33 public function run() {
34 $this->generateRoot();
35 foreach ($this->reference as $repo) {
36 $iter = new \RecursiveIteratorIterator($repo,
37 \RecursiveIteratorIterator::SELF_FIRST);
38 foreach ($iter as $ref) {
39 $this->generateEntry($ref);
40 }
41 }
42 }
43
44 /**
45 * Generate index.html and LICENSE.html
46 */
47 private function generateRoot() {
48 printf("Generating index ...\n");
49 $data = $this->createPayload(null);
50 $data->ref = "index";
51 $this->renderer->persist($data);
52
53 printf("Generating LICENSE ...\n");
54 $data->text = file_get_contents(__DIR__."/../LICENSE");
55 $data->ref = "LICENSE";
56 $this->renderer->persist($data);
57 }
58
59 /**
60 * Generate HTML for an entry
61 * @param \mdref\Entry $ref
62 */
63 private function generateEntry(Entry $ref) {
64 printf("Generating %s ...\n", $ref->getName());
65 $data = $this->createPayload($ref);
66 $this->renderer->persist($data);
67 }
68
69 /**
70 * Create the view payload
71 * @param \mdref\Entry $ref
72 * @param \mdref\Generator\Renderer $view
73 * @return \stdClass
74 */
75 private function createPayload(Entry $ref = null) {
76 $pld = new \stdClass;
77
78 $pld->quick = [$this->reference, "formatString"];
79 $pld->file = [$this->reference, "formatFile"];
80 $pld->refs = $this->reference;
81 $pld->view = $this->renderer;
82 if ($ref) {
83 $pld->entry = $ref;
84 $pld->ref = $ref->getName();
85 }
86
87 return $pld;
88 }
89 }
90
91 namespace mdref\Generator;
92
93 class Renderer
94 {
95 /**
96 * @var string
97 */
98 private $dir;
99
100 /**
101 * @param string $dir output directory
102 */
103 public function __construct($dir = "public/static") {
104 $this->dir = $dir;
105 }
106
107 /**
108 * HTML entity encode special characters
109 * @param string $string
110 * @return string
111 */
112 public function esc($string) {
113 return htmlspecialchars($string);
114 }
115
116 /**
117 * Render mdref page
118 * @param \stdClass $pld
119 * @return string
120 */
121 public function render(\stdClass $pld) {
122 $content = "";
123 ob_start(function($data) use(&$content) {
124 $content .= $data;
125 return true;
126 });
127 static::renderFile("views/layout.phtml", (array) $pld);
128 ob_end_flush();
129 return $content;
130 }
131
132 /**
133 * Persist mdref page to output directory
134 * @param \stdClass $data
135 */
136 public function persist(\stdClass $data) {
137 $html = $this->render($data);
138 $file = sprintf("%s/%s.html", $this->dir, $data->ref);
139 $this->saveFile($file, $html);
140 $this->linkIndex(dirname($file));
141 }
142
143 /**
144 * Save data to file (write to $file.tmp and rename to $file)
145 * @param string $file
146 * @param string $data
147 * @throws \Exception
148 */
149 private function saveFile($file, $data) {
150 $dir = dirname($file);
151 if (!is_dir($dir) && !mkdir($dir, 0755, true)) {
152 throw new \Exception("Failed to create directory '$dir'");
153 }
154 if (!file_put_contents("$file.tmp", $data)) {
155 throw new \Exception("Failed to save file '$file.tmp'");
156 }
157 if (!rename("$file.tmp", $file)) {
158 throw new \Exception("Failed to rename to '$file'");
159 }
160 }
161
162 private function linkIndex($dir) {
163 $index = "$dir.html";
164 $link = "$dir/index.html";
165 if (is_file($index) && !is_file($link)) {
166 printf("Generating index for '%s'\n", substr($dir, strlen($this->dir)));
167 link($index, $link);
168 }
169 }
170
171 /**
172 * Render file
173 */
174 static private function renderFile() {
175 if (func_num_args() > 1) {
176 extract(func_get_arg(1));
177 }
178 include func_get_arg(0);
179 }
180 }