diff --git a/README.md b/README.md
index 2ede294..3e218ce 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
# fibable
-A little PHP file-based blog engine.
\ No newline at end of file
+A little PHP file-based blog engine.
+
+This is a lightweight and not yet finished blog engine, written in PHP, working without database, only with directory structure and files and a sort of Markdown format.
diff --git a/config.php b/config.php
new file mode 100644
index 0000000..df4a2e0
--- /dev/null
+++ b/config.php
@@ -0,0 +1,14 @@
+ "https://example.com"
+];
diff --git a/css/blog.css b/css/blog.css
new file mode 100644
index 0000000..87925bd
--- /dev/null
+++ b/css/blog.css
@@ -0,0 +1,5 @@
+@charset "utf-8";
+
+* { box-sizing: border-box; }
+
+body { font-family: sans-serif; }
diff --git a/data/cache/default.phtml b/data/cache/default.phtml
new file mode 100644
index 0000000..7947b73
--- /dev/null
+++ b/data/cache/default.phtml
@@ -0,0 +1,8 @@
+
+ L'article que vous cherchez n'existe pas
+
+
+
+ Il n'y a rien à voir ici ! Vous avez du vous tromper de chemin !
+
+
diff --git a/data/serial/default.pobj b/data/serial/default.pobj
new file mode 100644
index 0000000..d1efe76
--- /dev/null
+++ b/data/serial/default.pobj
@@ -0,0 +1 @@
+O:7:"Article":5:{s:5:"title";s:40:"L'article que vous cherchez n'existe pas";s:4:"date";s:10:"2023-08-01";s:3:"ref";s:2:"01";s:4:"tags";a:2:{i:0;s:4:"blog";i:1;s:5:"essai";}s:4:"view";s:26:"./data/cache/default.phtml";}
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..59f28e7
--- /dev/null
+++ b/index.php
@@ -0,0 +1,10 @@
+run();
diff --git a/lib/Article.php b/lib/Article.php
new file mode 100644
index 0000000..8b3e3de
--- /dev/null
+++ b/lib/Article.php
@@ -0,0 +1,17 @@
+view;
+ }
+
+}
diff --git a/lib/ArticleDate.php b/lib/ArticleDate.php
new file mode 100644
index 0000000..e9aecff
--- /dev/null
+++ b/lib/ArticleDate.php
@@ -0,0 +1,14 @@
+str = $str;
+ }
+
+}
diff --git a/lib/ArticleManager.php b/lib/ArticleManager.php
new file mode 100644
index 0000000..39e8d7d
--- /dev/null
+++ b/lib/ArticleManager.php
@@ -0,0 +1,118 @@
+articles[] = self::getArticle($file_path);
+ }
+ }
+ }
+ }
+ return $dates;
+ }
+
+ private static function getArticle(string $file_path): Article
+ {
+ $pattern = "|(./data)/articles/([0-9]+)/([0-9]+)/([0-9]+)/([0-9]+)\.txt|";
+ $replacement = "$1/serial/$2$3$4$5.pobj";
+ $serial_path = preg_replace($pattern, $replacement, $file_path);
+ return file_exists($serial_path) ?
+ unserialize(file_get_contents($serial_path)) :
+ self::createArticle($file_path, $serial_path);
+ }
+
+ private static function createArticle(string $file_path, string $serial_path): Article
+ {
+ $parser = new ArticleParser();
+ $article = $parser->parse($file_path);
+ file_put_contents($serial_path, serialize($article));
+ TagManager::add($article->tags, substr(basename($serial_path), 0, -5));
+ return $article;
+ }
+}
diff --git a/lib/ArticleParser.php b/lib/ArticleParser.php
new file mode 100644
index 0000000..45a1a20
--- /dev/null
+++ b/lib/ArticleParser.php
@@ -0,0 +1,122 @@
+$1";
+ const REPLACE_ITALIC = "$1 ";
+ const REPLACE_UNDERLINE = "$1 ";
+
+ private bool $in_p = false;
+ private array $content = [];
+ private array $p = [];
+
+ public function parse(string $file_path): Article
+ {
+ $article = new Article();
+ $article->date = preg_replace(self::PATTERN_DATE, self::REPLACE_DATE, $file_path);
+ $article->ref = preg_replace(self::PATTERN_DATE, self::REPLACE_REF, $file_path);
+ $lines = file($file_path, FILE_IGNORE_NEW_LINES);
+ foreach ($lines as $line) {
+ if (substr($line, 0, 2) === "# ") {
+ $article->title = substr($line, 2);
+ continue;
+ }
+ if (substr($line, 0, 3) === "## ") {
+ $this->closeParagraph();
+ $this->content[] = "" . substr($line, 3) . " ";
+ continue;
+ }
+ if (substr($line, 0, 4) === "### ") {
+ $this->closeParagraph();
+ $this->content[] = "" . substr($line, 4) . " ";
+ continue;
+ }
+ if (substr($line, 0, 5) === "#### ") {
+ $this->closeParagraph();
+ $this->content[] = "" . substr($line, 5) . " ";
+ continue;
+ }
+ if (preg_match(self::PATTERN_TAGS, $line, $matches)) {
+ $article->tags = explode(",", $matches[1]);
+ continue;
+ }
+ if (preg_match(self::PATTERN_IMG, $line, $matches)) {
+ $this->closeParagraph();
+ $this->content[] = "";
+ $this->content[] = " ";
+ $this->content[] = " $matches[3] ";
+ $this->content[] = " ";
+ continue;
+ }
+ if ($line === "") {
+ $this->closeParagraph();
+ continue;
+ } else {
+ if (!$this->in_p) {
+ $this->content[] = "";
+ $this->in_p = true;
+ }
+ $this->p[] = $this->parseLine($line);
+ }
+ }
+ $this->closeParagraph();
+ array_unshift($this->content,
+ "",
+ " " . $article->title . " ",
+ " " . $this->getHtmlTags($article->tags) . " ",
+ " "
+ );
+ $this->content[] = "" .
+ preg_replace("/([0-9]+)\-([0-9]+)-([0-9]+)/", "$3/$2/$1", $article->date) .
+ " ";
+ $view_path = "./data/cache/{$article->date}_{$article->ref}.phtml";
+ file_put_contents($view_path, " " . implode("\n ", $this->content) . "\n");
+ $article->view = $view_path;
+ $this->content = [];
+ return $article;
+ }
+
+ private function closeParagraph()
+ {
+ if ($this->in_p) {
+ $this->content[] = " " . implode(" ", $this->p);
+ $this->content[] = "
";
+ $this->in_p = false;
+ $this->p = [];
+ }
+ }
+
+ private function parseLine(string $line): string
+ {
+ $patterns = [
+ self::PATTERN_BOLD,
+ self::PATTERN_ITALIC,
+ self::PATTERN_UNDERLINE
+ ];
+ $replacements = [
+ self::REPLACE_BOLD,
+ self::REPLACE_ITALIC,
+ self::REPLACE_UNDERLINE
+ ];
+ return preg_replace($patterns, $replacements, htmlspecialchars($line, ENT_HTML5, UTF-8));
+ }
+
+ private function getHtmlTags(array $tags): string
+ {
+ $html = "";
+ foreach ($tags as $tag) {
+ $html .= "$tag ";
+ }
+ return $html;
+ }
+
+}
diff --git a/lib/Blog.php b/lib/Blog.php
new file mode 100644
index 0000000..f4a2df8
--- /dev/null
+++ b/lib/Blog.php
@@ -0,0 +1,113 @@
+loadConfig();
+ $this->render();
+ }
+
+ private function loadConfig()
+ {
+ require_once "config.php";
+ foreach ($links as $title => $url) {
+ $this->links[] = new Link($title, $url);
+ }
+ }
+
+ private function render()
+ {
+ extract($this->populateViews());
+ include "views/index.phtml";
+ }
+
+ private function populateViews(): array
+ {
+ return array_merge(
+ ["lang" => BLOG_LANGUAGE],
+ $this->populateHeader(),
+ $this->populateMainView(),
+ $this->populateBio(),
+ $this->populateNavByTag(),
+ $this->populateNavByLatest(),
+ $this->populateFooter()
+ );
+ }
+
+ private function populateHeader(): array
+ {
+ return [
+ "title" => BLOG_TITLE,
+ "has_sub_title" => BLOG_SUB_TITLE !== "",
+ "sub_title" => BLOG_SUB_TITLE
+ ];
+ }
+
+ private function populateMainView(): array
+ {
+ $variables = [];
+ $view = filter_input(INPUT_GET, "view", FILTER_SANITIZE_STRING) ?: "single";
+ $variables["view"] = $view;
+ switch ($view) {
+ case "single":
+ $date = filter_input(INPUT_GET, "date", FILTER_SANITIZE_STRING);
+ $ref = filter_input(INPUT_GET, "ref", FILTER_SANITIZE_STRING);
+ $variables["article"] = ($date !== null and $ref !== null) ?
+ ArticleManager::get(str_replace("-", "", $date) . $ref) :
+ ArticleManager::getNewest();
+ break;
+ case "tags":
+ $tag = filter_input(INPUT_GET, "tag", FILTER_SANITIZE_STRING) ?: "";
+ $variables["articles"] = ArticleManager::getMultiple(TagManager::getFromTag($tag));
+ break;
+ case "archives":
+ $variables["dates"] = ArticleManager::getAll();;
+ break;
+ }
+ return $variables;
+ }
+
+ private function populateBio(): array
+ {
+ return [
+ "author" => AUTHOR_NICKNAME,
+ "bio" => AUTHOR_BIOGRAPHY,
+ ];
+ }
+
+ private function populateNavByTag(): array
+ {
+ return ["tags" => TagManager::getTags()];
+ }
+
+ private function populateNavByLatest(): array
+ {
+ return [
+ "latest" => ArticleManager::getLatestArticles(ARTICLE_COUNT_IN_NAV)
+ ];
+ }
+
+ private function populateFooter(): array
+ {
+ return [
+ "has_links" => count($this->links) > 0,
+ "links" => $this->links,
+ "footer" => $this->getFooterString()
+ ];
+ }
+
+ private function getFooterString(): string
+ {
+ $currentYear = getdate()["year"];
+ $footerYear = BLOG_CREATION_YEAR .
+ (BLOG_CREATION_YEAR !== $currentYear ? " - $currentYear" : "");
+ $footerAuthor = AUTHOR_NICKNAME .
+ (AUTHOR_REALNAME !== "" ? " (" . AUTHOR_REALNAME . ")" : "");
+ return "$footerYear. $footerAuthor";
+ }
+
+}
diff --git a/lib/Link.php b/lib/Link.php
new file mode 100644
index 0000000..ce914c2
--- /dev/null
+++ b/lib/Link.php
@@ -0,0 +1,15 @@
+title = $title;
+ $this->url = $url;
+ }
+
+}
diff --git a/lib/TagManager.php b/lib/TagManager.php
new file mode 100644
index 0000000..96cf169
--- /dev/null
+++ b/lib/TagManager.php
@@ -0,0 +1,29 @@
+
+
+
+ =$date->str?>
+
+articles as $article): ?>
+ =$article->title?>
+
+
+
+
+
diff --git a/views/aside.phtml b/views/aside.phtml
new file mode 100644
index 0000000..90f8d98
--- /dev/null
+++ b/views/aside.phtml
@@ -0,0 +1,7 @@
+
diff --git a/views/bio.phtml b/views/bio.phtml
new file mode 100644
index 0000000..3117918
--- /dev/null
+++ b/views/bio.phtml
@@ -0,0 +1,7 @@
+
+
+
+ =$author?>
+
+
=$bio?>
+
diff --git a/views/footer.phtml b/views/footer.phtml
new file mode 100644
index 0000000..bb0b7a7
--- /dev/null
+++ b/views/footer.phtml
@@ -0,0 +1,10 @@
+
diff --git a/views/header.phtml b/views/header.phtml
new file mode 100644
index 0000000..1f9cc0e
--- /dev/null
+++ b/views/header.phtml
@@ -0,0 +1,6 @@
+
+ =$title?>
+
+ =$sub_title?>
+
+
diff --git a/views/index.phtml b/views/index.phtml
new file mode 100644
index 0000000..d46d673
--- /dev/null
+++ b/views/index.phtml
@@ -0,0 +1,16 @@
+
+
+
+
+ =$title?>
+
+
+
+
+
+
diff --git a/views/main.phtml b/views/main.phtml
new file mode 100644
index 0000000..27b8e9e
--- /dev/null
+++ b/views/main.phtml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/views/nav-by-latest.phtml b/views/nav-by-latest.phtml
new file mode 100644
index 0000000..8ea3d9e
--- /dev/null
+++ b/views/nav-by-latest.phtml
@@ -0,0 +1,6 @@
+
+
+ =$prev->title?>
+
+ Archives
+
diff --git a/views/nav-by-tag.phtml b/views/nav-by-tag.phtml
new file mode 100644
index 0000000..d4ac6ac
--- /dev/null
+++ b/views/nav-by-tag.phtml
@@ -0,0 +1,5 @@
+
+
+ =$tag?>
+
+
diff --git a/views/single.phtml b/views/single.phtml
new file mode 100644
index 0000000..f5c588f
--- /dev/null
+++ b/views/single.phtml
@@ -0,0 +1,3 @@
+
+render(); ?>
+
diff --git a/views/tags.phtml b/views/tags.phtml
new file mode 100644
index 0000000..67f02b1
--- /dev/null
+++ b/views/tags.phtml
@@ -0,0 +1,5 @@
+