diff --git a/.gitignore b/.gitignore
index 241e833..a1a2711 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
.idea/
.vscode/
-venv/
-
-.DS_Store
+build/
+dist/
+node_modules/
\ No newline at end of file
diff --git a/README.md b/README.md
deleted file mode 100644
index 5ca31c5..0000000
--- a/README.md
+++ /dev/null
@@ -1,68 +0,0 @@
-## Hi there!
-
-I'm **Lucàs**, a passionate developer based in 🇫🇷 **Pau, France**.
-My journey in the world of programming started 7 years ago when I fell in love with   .
-
-###
What I do
-
-- 💻 Currently, I'm studying at **UPPA** for my Master's degree in Informatics.
-- 🌐 I specialize in Web Development, and I'm always eager to explore new technologies and frameworks.
-- 🌱 I'm constantly learning and expanding my skill set to stay up-to-date with the ever-evolving tech landscape.
-
-###
Connect with me
-
-[](https://www.lucasvbr.dev)
-[](https://www.linkedin.com/in/lucasvbr)
-[](https://www.freecodecamp.org/LucasVbr)
-[](https://openclassrooms.com/fr/members/97j9zltv6225)
-[](https://exercism.org/profiles/LucasVbr)
-
-###
Tech Stack
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-###
Let's collaborate
-
-👀 I'm always open to collaboration and exciting projects. If you have something in mind, feel free to reach out!
-
----
-
-
diff --git a/bun.lock b/bun.lock
new file mode 100644
index 0000000..512f85a
--- /dev/null
+++ b/bun.lock
@@ -0,0 +1,50 @@
+{
+ "lockfileVersion": 1,
+ "workspaces": {
+ "": {
+ "name": "lucasvbr",
+ "dependencies": {
+ "handlebars": "^4.7.8",
+ "yaml": "^2.8.0",
+ "zod": "^4.0.5",
+ },
+ "devDependencies": {
+ "@types/bun": "latest",
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0",
+ },
+ },
+ },
+ "packages": {
+ "@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
+
+ "@types/node": ["@types/node@24.0.13", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ=="],
+
+ "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
+
+ "bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
+
+ "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
+
+ "handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="],
+
+ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
+
+ "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
+
+ "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
+ "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
+
+ "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
+
+ "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
+
+ "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
+
+ "yaml": ["yaml@2.8.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ=="],
+
+ "zod": ["zod@4.0.5", "", {}, "sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA=="],
+ }
+}
diff --git a/config.json b/config.json
new file mode 100644
index 0000000..98d65ad
--- /dev/null
+++ b/config.json
@@ -0,0 +1,7 @@
+{
+ "parser_type": "YAML",
+ "compiler_type": "HANDLEBARS",
+ "input_file": "src/templates/README.md.hbs",
+ "data_file": "static/README.yaml",
+ "output_file": "README.md"
+}
\ No newline at end of file
diff --git a/config.yaml b/config.yaml
deleted file mode 100644
index b38fab3..0000000
--- a/config.yaml
+++ /dev/null
@@ -1,48 +0,0 @@
-user: "LucasVbr"
-
-socials:
- - name: "Portfolio"
- url: "https://www.lucasvbr.dev"
-
- - name: "Linkedin"
- url: "https://www.linkedin.com/in/lucasvbr"
-
- - name: "FreeCodeCamp"
- url: "https://www.freecodecamp.org/LucasVbr"
-
- - name: "OpenClassRooms"
- url: "https://openclassrooms.com/fr/members/97j9zltv6225"
-
- - name: "Exercism"
- url: "https://exercism.org/profiles/LucasVbr"
-
-skills:
- - Android
- - Angular
- - Astro
- - Bun
- - Bootstrap
- - Bulma
- - C
- - CSS3
- - Deno
- - Docker
- - Express
- - Figma
- - Git
- - GNU Bash
- - HTML5
- - JavaScript
- - MariaDB
- - MongoDB
- - MySQL
- - Node.js
- - OCaml
- - PHP
- - PostgreSQL
- - Pug
- - Python
- - React
- - SQLite
- - Symfony
- - TypeScript
\ No newline at end of file
diff --git a/github/workflows/deploy.yaml b/github/workflows/deploy.yaml
new file mode 100644
index 0000000..58a8e66
--- /dev/null
+++ b/github/workflows/deploy.yaml
@@ -0,0 +1,43 @@
+name: Build and Commit README
+
+on:
+ push:
+ branches: [dev]
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Install Bun
+ uses: oven-sh/setup-bun@v1
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Run build
+ run: bun run build
+
+ - name: Configure Git
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+
+ - name: Commit README.md to main
+ run: |
+ git checkout main
+ git pull origin main
+ git checkout dev -- README.md
+ if git diff --quiet README.md; then
+ echo "No changes to README.md"
+ else
+ git add README.md
+ git commit -m "chore(readme): update after build [CI]"
+ git push origin main
+ fi
diff --git a/main.py b/main.py
deleted file mode 100644
index c878ea5..0000000
--- a/main.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from src.config import Config
-from src.template import Template
-
-CONFIG_FILE = 'config.yaml'
-TEMPLATE_FILE = 'template.md'
-OUTPUT_FILE = 'README.md'
-
-if __name__ == '__main__':
- template = Template(TEMPLATE_FILE)
- data = Config(CONFIG_FILE).get_data()
-
- # Generate README file
- render = template.render(**data)
- with open(OUTPUT_FILE, 'w') as f:
- f.write(render)
-
- print(f"{OUTPUT_FILE} generated successfully! 🎉")
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6663d8b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "lucasvbr",
+ "module": "src/index.ts",
+ "type": "module",
+ "scripts": {
+ "build": "bun run src/index.ts"
+ },
+ "devDependencies": {
+ "@types/bun": "latest"
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0"
+ },
+ "dependencies": {
+ "handlebars": "^4.7.8",
+ "yaml": "^2.8.0",
+ "zod": "^4.0.5"
+ }
+}
\ No newline at end of file
diff --git a/requirements b/requirements
deleted file mode 100644
index 98eb82d..0000000
--- a/requirements
+++ /dev/null
@@ -1,5 +0,0 @@
-pyyaml
-json
-requests
-simpleicons
-urllib3
\ No newline at end of file
diff --git a/src/compilers/Compiler.ts b/src/compilers/Compiler.ts
new file mode 100644
index 0000000..8618c9d
--- /dev/null
+++ b/src/compilers/Compiler.ts
@@ -0,0 +1,12 @@
+/**
+ * Compiler interface for compiling templates with data.
+ */
+export interface Compiler {
+
+ /**
+ * Compiles the given template with the provided data.
+ * @param template - The template string to compile.
+ * @param data - The data object to use for compilation.
+ */
+ compile(template: string, data: object): string;
+}
\ No newline at end of file
diff --git a/src/compilers/CompilerFactory.ts b/src/compilers/CompilerFactory.ts
new file mode 100644
index 0000000..7c5d154
--- /dev/null
+++ b/src/compilers/CompilerFactory.ts
@@ -0,0 +1,22 @@
+import {type Compiler, CompilerType, HandlebarsCompiler} from '.';
+
+/**
+ * Factory class for creating compiler instances.
+ * This class provides a method to get a compiler based on the specified type.
+ */
+export class CompilerFactory {
+
+ /**
+ * Creates a compiler instance based on the specified type.
+ * @param type - The type of compiler to create.
+ * @returns An instance of the specified compiler.
+ */
+ public static getCompiler(type: CompilerType): Compiler {
+ switch (type) {
+ case CompilerType.HANDLEBARS:
+ return HandlebarsCompiler.getInstance();
+ default:
+ throw new Error(`Unsupported compiler type: ${type}`);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/compilers/CompilerType.ts b/src/compilers/CompilerType.ts
new file mode 100644
index 0000000..4d2d0a6
--- /dev/null
+++ b/src/compilers/CompilerType.ts
@@ -0,0 +1,6 @@
+/**
+ * Enum representing different types of compilers.
+ */
+export enum CompilerType {
+ HANDLEBARS = 'handlebars',
+}
\ No newline at end of file
diff --git a/src/compilers/HandlebarsCompiler.ts b/src/compilers/HandlebarsCompiler.ts
new file mode 100644
index 0000000..5a615f9
--- /dev/null
+++ b/src/compilers/HandlebarsCompiler.ts
@@ -0,0 +1,25 @@
+import {type Compiler, CompilerType} from '.';
+import {compile} from 'handlebars';
+
+export class HandlebarsCompiler implements Compiler {
+
+ static readonly TYPE: CompilerType = CompilerType.HANDLEBARS;
+
+ private static instance: HandlebarsCompiler;
+ private constructor() {}
+
+ public static getInstance(): HandlebarsCompiler {
+ if (!HandlebarsCompiler.instance) {
+ HandlebarsCompiler.instance = new HandlebarsCompiler();
+ }
+ return HandlebarsCompiler.instance;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public compile(template: string, data: object = {}): string {
+ const compiledTemplate = compile(template);
+ return compiledTemplate(data);
+ }
+}
\ No newline at end of file
diff --git a/src/compilers/index.ts b/src/compilers/index.ts
new file mode 100644
index 0000000..17e20f0
--- /dev/null
+++ b/src/compilers/index.ts
@@ -0,0 +1,4 @@
+export * from './Compiler';
+export * from './CompilerType';
+export * from './CompilerFactory';
+export * from './HandlebarsCompiler';
diff --git a/src/config.py b/src/config.py
deleted file mode 100644
index 71a72e1..0000000
--- a/src/config.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import yaml
-from src.model.skill_list import skill_list
-import requests
-from src.model.social_list import social_list
-
-
-class Config:
- config_file_path: str
- config_data: dict[str, any] = None
-
- def __init__(self, config_file_path: str):
- self.config_file_path = config_file_path
-
- def load_config_file(self):
- with open(self.config_file_path, 'r') as config_file:
- self.config_data = yaml.safe_load(config_file)
-
- def handle_user_info(self):
- user = self.config_data["user"]
- response = requests.get(f"https://api.github.com/users/{user}")
- if response.status_code != 200:
- raise Exception("User not found")
- self.config_data["user"] = response.json()
-
- def get_data(self):
- self.load_config_file()
- self.handle_user_info()
- self.config_data["skills"] = skill_list(self.config_data["skills"])
- self.config_data["socials"] = social_list(self.config_data["socials"])
-
- return self.config_data
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..17cab9e
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,75 @@
+import config from '../config.json';
+import {CompilerFactory, CompilerType} from './compilers';
+import {ParserFactory, ParserType} from './parsers';
+import {Validator} from './validators/Validator.ts';
+import {FileManager} from './io/FileManager.ts';
+import {ReadmeSchema} from './validators/schemas';
+
+/**
+ * Point d'entrée du script
+ */
+async function main(): Promise {
+ const compiler = createCompiler(config.compiler_type);
+ const parser = createParser(config.parser_type);
+ const validator = new Validator(ReadmeSchema);
+
+ const data = await loadData(config.data_file, parser, validator);
+ const template = await loadTemplate(config.input_file);
+ const result = compiler.compile(template, data);
+
+ await saveOutput(config.output_file, result);
+}
+
+/**
+ * Crée un compilateur en fonction du type spécifié dans la configuration
+ * @param type - Le type de compilateur à créer
+ */
+function createCompiler(type: string) {
+ const compilerType = CompilerType[type as keyof typeof CompilerType];
+ return CompilerFactory.getCompiler(compilerType);
+}
+
+/**
+ * Crée un parseur en fonction du type spécifié dans la configuration
+ * @param type - Le type de parseur à créer
+ */
+function createParser(type: string) {
+ const parserType = ParserType[type as keyof typeof ParserType];
+ return ParserFactory.getParser(parserType);
+}
+
+/**
+ * Charge les données depuis le fichier spécifié, les parse et les valide
+ * @param path - Le chemin du fichier de données
+ * @param parser - Le parseur à utiliser pour analyser les données
+ * @param validator - Le validateur à utiliser pour valider les données
+ */
+async function loadData(
+ path: string, parser: ReturnType,
+ validator: Validator) {
+ const raw = await FileManager.read(path);
+ const parsed = parser.parse(raw);
+ return validator.validate(parsed);
+}
+
+/**
+ * Charge le modèle de template depuis le fichier spécifié
+ * @param path - Le chemin du fichier de template
+ */
+async function loadTemplate(path: string): Promise {
+ return FileManager.read(path);
+}
+
+/**
+ * Enregistre le résultat compilé dans le fichier de sortie spécifié
+ * @param path - Le chemin du fichier de sortie
+ * @param content - Le contenu à écrire dans le fichier
+ */
+async function saveOutput(path: string, content: string): Promise {
+ await FileManager.write(path, content);
+}
+
+// Exécute le script principal
+main()
+ .then(() => console.log('🎉 Exécution terminée avec succès.'))
+ .catch(err => console.error('❌ Erreur pendant l’exécution:', err));
diff --git a/src/io/FileManager.ts b/src/io/FileManager.ts
new file mode 100644
index 0000000..0642542
--- /dev/null
+++ b/src/io/FileManager.ts
@@ -0,0 +1,33 @@
+import {readFileSync, writeFileSync} from 'node:fs';
+
+/**
+ * FileManager is a singleton class that provides methods to read and write files.
+ * It ensures that only one instance of the FileManager exists throughout the application.
+ */
+export class FileManager {
+
+ /**
+ * Reads the content of a file at the specified path.
+ * @param {string} filePath - The path to the file to read.
+ * @returns {Promise} A promise that resolves with the file content.
+ */
+ public static async read(filePath: string): Promise {
+ return readFileSync(filePath, {
+ encoding: 'utf-8',
+ });
+ }
+
+ /**
+ * Writes content to a file at the specified path.
+ * If the file does not exist, it will be created.
+ * @param {string} filePath - The path to the file to write.
+ * @param {string} content - The content to write to the file.
+ * @returns {Promise} A promise that resolves when the write operation is complete.
+ */
+ public static async write(filePath: string, content: string): Promise {
+ return writeFileSync(filePath, content, {
+ encoding: 'utf-8',
+ flag: 'w',
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/model/skill.py b/src/model/skill.py
deleted file mode 100644
index 093024b..0000000
--- a/src/model/skill.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from src.shield.skill_shield import SkillShield
-
-
-class Skill:
- alt: str
- src: str
-
- def __init__(self, name: str):
- self.alt = name
- self.src = SkillShield(name).__str__()
-
- def __str__(self) -> str:
- return f""
diff --git a/src/model/skill_list.py b/src/model/skill_list.py
deleted file mode 100644
index ee746ad..0000000
--- a/src/model/skill_list.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from src.model.skill import Skill
-
-
-def skill_list(skills: list[str]) -> dict[str, str]:
- # Sort and remove duplicates
- skills = list(set(skills))
- skills.sort()
-
- skills = {skill: Skill(skill).__str__() for skill in skills}
- skills["all"] = "\n".join([str(skill) for skill in skills.values()])
- return skills
diff --git a/src/model/social.py b/src/model/social.py
deleted file mode 100644
index a96ad54..0000000
--- a/src/model/social.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from src.shield.social_shield import SocialShield
-
-
-class Social:
- name: str
- img: str
-
- def __init__(self, name: str, url: str):
- self.name = name
- self.img = SocialShield(name, url).__str__()
- self.url = url
-
- def __str__(self) -> str:
- return f"[]({self.url})"
diff --git a/src/model/social_list.py b/src/model/social_list.py
deleted file mode 100644
index f3106ca..0000000
--- a/src/model/social_list.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from src.model.social import Social
-
-
-def social_list(socials: list) -> dict[str, str]:
- socials: dict[str, str] = {social.get("name"): str(Social(social.get("name"), social.get("url"))) for social in
- socials}
- socials["all"] = "\n".join([str(social) for social in socials.values()])
- return socials
diff --git a/src/parsers/Parser.ts b/src/parsers/Parser.ts
new file mode 100644
index 0000000..51dcf6b
--- /dev/null
+++ b/src/parsers/Parser.ts
@@ -0,0 +1,17 @@
+/**
+ * Parser interface for parsing data strings into object representations.
+ */
+export interface Parser {
+ /**
+ * Parses the given data string and returns an object representation.
+ * @param data - The data string to parse.
+ * @returns An object representation of the parsed data.
+ */
+ parse(data: string): object;
+
+ /**
+ * Returns an instance of the parser.
+ * @returns An instance of the parser.
+ */
+ getInstance(): Parser;
+}
\ No newline at end of file
diff --git a/src/parsers/ParserFactory.ts b/src/parsers/ParserFactory.ts
new file mode 100644
index 0000000..3bf5f58
--- /dev/null
+++ b/src/parsers/ParserFactory.ts
@@ -0,0 +1,20 @@
+import {ParserType, type Parser, YamlParser} from "."
+
+/**
+ * Factory class for creating parser instances.
+ */
+export class ParserFactory {
+
+ /**
+ * Creates and returns a parser instance based on the specified type.
+ * @param type
+ */
+ static getParser(type: ParserType): Parser {
+ switch (type) {
+ case ParserType.YAML:
+ return YamlParser.getInstance();
+ default:
+ throw new Error(`Unsupported parser type: ${type}`);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/parsers/ParserType.ts b/src/parsers/ParserType.ts
new file mode 100644
index 0000000..cc39a8a
--- /dev/null
+++ b/src/parsers/ParserType.ts
@@ -0,0 +1,6 @@
+/**
+ * Enum representing different parser types.
+ */
+export enum ParserType {
+ YAML = 'yaml',
+}
\ No newline at end of file
diff --git a/src/parsers/YamlParser.ts b/src/parsers/YamlParser.ts
new file mode 100644
index 0000000..81de66e
--- /dev/null
+++ b/src/parsers/YamlParser.ts
@@ -0,0 +1,49 @@
+import {type Parser, ParserType} from '.';
+import {parse} from 'yaml';
+
+/**
+ * YamlParser class implements the Parser interface for parsing YAML data.
+ */
+export class YamlParser implements Parser {
+
+ /**
+ * The type of the parser.
+ * This is used to identify the parser type in the system.
+ */
+ static readonly TYPE: ParserType = ParserType.YAML;
+
+ /**
+ * Singleton instance of the parser.
+ * This ensures that only one instance of the parser exists.
+ */
+ static instance?: YamlParser;
+
+ private constructor() {
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static getInstance(): YamlParser {
+ if (!this.instance) {
+ this.instance = new YamlParser();
+ }
+ return this.instance;
+ }
+
+ getInstance(): Parser {
+ throw new Error('Method not implemented.');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public parse(data: string): object {
+ try {
+ return parse(data);
+ } catch (error) {
+ console.error('Error parsing YAML data:', error);
+ throw new Error('Failed to parse YAML data');
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/parsers/index.ts b/src/parsers/index.ts
new file mode 100644
index 0000000..af983d5
--- /dev/null
+++ b/src/parsers/index.ts
@@ -0,0 +1,4 @@
+export * from "./Parser";
+export * from "./ParserType";
+export * from "./ParserFactory";
+export * from "./YamlParser";
diff --git a/src/shield/shield_builder.py b/src/shield/shield_builder.py
deleted file mode 100644
index b9a2977..0000000
--- a/src/shield/shield_builder.py
+++ /dev/null
@@ -1,94 +0,0 @@
-from urllib.parse import urlunsplit, urlencode
-from simpleicons.all import icons
-
-class ShieldBuilder:
- BASE_URL = "https://img.shields.io/static/v1"
-
- message: str = None
- style: str = None
- logo: str = None
- logo_color: str = None
- label: str = None
- label_color: str = None
- color: str = None
- cache_seconds: int = None
- link: str = None
-
- def __init__(self):
- self.logo_color = "white"
- self.label = " "
- self.color = "black"
-
- def set_message(self, message: str):
- self.message = (
- message
- .replace("_", "__")
- .replace("-", "--")
- .replace(" ", "_")
- )
- return self
-
- def set_style(self, style: str):
- if not style in ["flat", "flat-square", "plastic", "for-the-badge", "social"]:
- raise Exception("Invalid style")
-
- self.style = style
- return self
-
- def set_logo(self, logo: str):
- self.logo = icons.get(logo).slug if logo in icons else None
- return self
-
- def set_logo_color(self, logo_color: str):
- self.logo_color = logo_color
- return self
-
- def set_label(self, label: str):
- self.label = label
- return self
-
- def set_label_color(self, label_color: str):
- self.label_color = label_color
- return self
-
- def set_color(self, color: str):
- self.color = color
- return self
-
- def set_cache_seconds(self, cache_seconds: int):
- self.cache_seconds = cache_seconds
- return self
-
- def set_link(self, link: str):
- self.link = link
- return self
-
- def get_query(self):
- query = {
- "message": self.message,
- "style": self.style,
- "logo": self.logo,
- "logoColor": self.logo_color,
- "label": self.label,
- "labelColor": self.label_color,
- "color": self.color,
- "cacheSeconds": self.cache_seconds,
- "link": self.link
- }
-
- # Remove None values
- return {k: v for k, v in query.items() if v is not None}
-
- def build(self):
- query = urlencode(self.get_query())
- return urlunsplit(("", "", self.BASE_URL, query, ""))
-
-
-if "__main__" == __name__:
- shield_builder = (
- ShieldBuilder()
- .set_logo("HTML5")
- .set_message("HTML5")
- .build()
- )
- print(shield_builder)
diff --git a/src/shield/skill_shield.py b/src/shield/skill_shield.py
deleted file mode 100644
index 3853513..0000000
--- a/src/shield/skill_shield.py
+++ /dev/null
@@ -1,33 +0,0 @@
-from simpleicons.all import icons
-
-from src.shield.shield_builder import ShieldBuilder
-
-
-class SkillShield:
-
- def __init__(self, name: str = None):
- self.builder = ShieldBuilder()
- self.skill = self.set_skill(name) if name is not None else None
-
- def get_skill(self):
- return self.skill
-
- def set_skill(self, name: str):
- self.skill = icons.get(name)
-
- if self.skill is not None:
- (self.builder.set_message(self.skill.title)
- .set_logo(self.skill.slug)
- .set_color(self.skill.hex)
- )
- else:
- self.builder.set_message(name)
-
- def get_builder(self):
- return self.builder
-
- def __repr__(self):
- self.__str__()
-
- def __str__(self):
- return self.builder.build()
diff --git a/src/shield/social_shield.py b/src/shield/social_shield.py
deleted file mode 100644
index d164572..0000000
--- a/src/shield/social_shield.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from simpleicons.all import icons
-
-from src.shield.shield_builder import ShieldBuilder
-
-
-class SocialShield:
-
- def __init__(self, name: str = None, url: str = None):
- self.builder = ShieldBuilder().set_style("for-the-badge")
- self.name = name
- self.url = url
- self.social = self.set_social(name, url) if name is not None and url is not None else None
-
- def get_social(self):
- return self.social
-
- def set_social(self, name: str, url: str):
- self.social = icons.get(name)
-
- if self.social is not None:
- (self.builder.set_message(self.social.title)
- .set_logo(self.social.slug)
- .set_color(self.social.hex)
- )
- else:
- self.builder.set_message(name)
-
- self.builder.set_link(url)
-
- def get_builder(self):
- return self.builder
-
- def __repr__(self):
- self.__str__()
-
- def __str__(self):
- return self.builder.build()
diff --git a/src/template.py b/src/template.py
deleted file mode 100644
index b5a91f9..0000000
--- a/src/template.py
+++ /dev/null
@@ -1,10 +0,0 @@
-class Template:
-
- def __init__(self, template_path: str):
- self.template_path = template_path
-
- def render(self, **kwargs):
- with open(self.template_path, 'r') as f:
- template = f.read()
-
- return template.format(**kwargs)
diff --git a/src/templates/README.md.hbs b/src/templates/README.md.hbs
new file mode 100644
index 0000000..2c4c2af
--- /dev/null
+++ b/src/templates/README.md.hbs
@@ -0,0 +1,37 @@
+## Hi there! 👋
+
+I'm **{{author.first_name}}**, a passionate developer based in **{{author.location}}**.
+
+### 🚀 What I do
+
+- 💻 Currently, I'm studying at **{{author.company}}** for my Master's degree in Informatics.
+- 🌐 I specialize in Web Development, and I'm always eager to explore new technologies and frameworks.
+- 🌱 I'm constantly learning and expanding my skill set to stay up-to-date with the ever-evolving tech landscape.
+
+### 🌍 Connect with me
+
+{{#each socials as | social |}}
+[]({{social.url}})
+{{/each}}
+
+### 🛠️ Tech Stack
+
+{{#each skills as | skill |}}
+
+{{/each}}
+
+### 🤝 Let's collaborate
+
+👀 I'm always open to collaboration and exciting projects. If you have something in mind, feel free to reach out!
+
+---
+
+
\ No newline at end of file
diff --git a/src/validators/Validator.ts b/src/validators/Validator.ts
new file mode 100644
index 0000000..c355814
--- /dev/null
+++ b/src/validators/Validator.ts
@@ -0,0 +1,43 @@
+import type {ZodObject} from 'zod';
+
+/**
+ * Validator class for validating and parsing objects against a Zod schema.
+ */
+export class Validator {
+
+ /**
+ * Creates an instance of the Validator.
+ * @param schema - The Zod schema to validate against.
+ */
+ constructor(private schema: ZodObject) {
+ }
+
+ /**
+ * Validates the given value against the schema.
+ * @param value - The object to validate.
+ * @returns true if the value is valid, false otherwise.
+ */
+ public isValid(value: object): boolean {
+ try {
+ this.schema.parse(value);
+ return true;
+ } catch (error) {
+ console.error('Validation error:', error);
+ return false;
+ }
+ }
+
+ /**
+ * Parses the given value against the schema.
+ * @param value - The object to parse.
+ * @returns The parsed object if valid, throws an error otherwise.
+ */
+ public validate(value: object): object {
+ try {
+ return this.schema.parse(value);
+ } catch (error) {
+ console.error('Parsing error:', error);
+ throw new Error('Failed to parse value');
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/validators/schemas/AuthorSchema.ts b/src/validators/schemas/AuthorSchema.ts
new file mode 100644
index 0000000..35252bd
--- /dev/null
+++ b/src/validators/schemas/AuthorSchema.ts
@@ -0,0 +1,7 @@
+import {z} from 'zod';
+
+export const AuthorSchema = z.object({
+ first_name: z.string(),
+ location: z.string(),
+ company: z.string(),
+});
\ No newline at end of file
diff --git a/src/validators/schemas/ReadmeSchema.ts b/src/validators/schemas/ReadmeSchema.ts
new file mode 100644
index 0000000..10ca244
--- /dev/null
+++ b/src/validators/schemas/ReadmeSchema.ts
@@ -0,0 +1,8 @@
+import {z} from 'zod';
+import {AuthorSchema, SkillSchema, SocialSchema} from '.';
+
+export const ReadmeSchema = z.object({
+ author: AuthorSchema,
+ socials: z.array(SocialSchema),
+ skills: z.array(SkillSchema),
+});
\ No newline at end of file
diff --git a/src/validators/schemas/SkillSchema.ts b/src/validators/schemas/SkillSchema.ts
new file mode 100644
index 0000000..c8c258c
--- /dev/null
+++ b/src/validators/schemas/SkillSchema.ts
@@ -0,0 +1,6 @@
+import {z} from 'zod';
+
+export const SkillSchema = z.object({
+ name: z.string(),
+ icon: z.string(),
+});
\ No newline at end of file
diff --git a/src/validators/schemas/SocialSchema.ts b/src/validators/schemas/SocialSchema.ts
new file mode 100644
index 0000000..2a141d8
--- /dev/null
+++ b/src/validators/schemas/SocialSchema.ts
@@ -0,0 +1,7 @@
+import {z} from 'zod';
+
+export const SocialSchema = z.object({
+ name: z.string(),
+ icon: z.string(),
+ url: z.string(),
+});
\ No newline at end of file
diff --git a/src/validators/schemas/index.ts b/src/validators/schemas/index.ts
new file mode 100644
index 0000000..31d0df5
--- /dev/null
+++ b/src/validators/schemas/index.ts
@@ -0,0 +1,4 @@
+export * from './AuthorSchema';
+export * from './SocialSchema';
+export * from './SkillSchema';
+export * from './ReadmeSchema';
\ No newline at end of file
diff --git a/static/README.yaml b/static/README.yaml
new file mode 100644
index 0000000..1b52f09
--- /dev/null
+++ b/static/README.yaml
@@ -0,0 +1,70 @@
+author:
+ first_name: Lucàs
+ location: 🇫🇷 Toulouse, France
+ company: UPPA
+socials:
+ - name: Portfolio
+ icon: ""
+ url: https://www.lucasvbr.dev
+ - name: LinkedIn
+ icon: ""
+ url: https://www.linkedin.com/in/lucasvbr
+skills:
+ - name: Android
+ icon: ""
+ - name: Angular
+ icon: ""
+ - name: Astro
+ icon: ""
+ - name: Bun
+ icon: ""
+ - name: Bootstrap
+ icon: ""
+ - name: Bulma
+ icon: ""
+ - name: C
+ icon: ""
+ - name: CSS3
+ icon: ""
+ - name: Deno
+ icon: ""
+ - name: Docker
+ icon: ""
+ - name: Express
+ icon: ""
+ - name: Figma
+ icon: ""
+ - name: Git
+ icon: ""
+ - name: GNU Bash
+ icon: ""
+ - name: HTML5
+ icon: ""
+ - name: JavaScript
+ icon: ""
+ - name: MariaDB
+ icon: ""
+ - name: MongoDB
+ icon: ""
+ - name: MySQL
+ icon: ""
+ - name: Node.js
+ icon: ""
+ - name: OCaml
+ icon: ""
+ - name: PHP
+ icon: ""
+ - name: PostgreSQL
+ icon: ""
+ - name: Pug
+ icon: ""
+ - name: Python
+ icon: ""
+ - name: React
+ icon: ""
+ - name: SQLite
+ icon: ""
+ - name: Symfony
+ icon: ""
+ - name: TypeScript
+ icon: ""
\ No newline at end of file
diff --git a/template.md b/template.md
deleted file mode 100644
index 181c03b..0000000
--- a/template.md
+++ /dev/null
@@ -1,36 +0,0 @@
-## Hi there!
-
-I'm **{user[name]}**, a passionate developer based in 🇫🇷 **{user[location]}**.
-My journey in the world of programming started 7 years ago when I fell in love with {skills[HTML5]} {skills[CSS3]} {skills[JavaScript]}.
-
-###
What I do
-
-- 💻 Currently, I'm studying at **{user[company]}** for my Master's degree in Informatics.
-- 🌐 I specialize in Web Development, and I'm always eager to explore new technologies and frameworks.
-- 🌱 I'm constantly learning and expanding my skill set to stay up-to-date with the ever-evolving tech landscape.
-
-###
Connect with me
-
-{socials[all]}
-
-###
Tech Stack
-
-{skills[all]}
-
-###
Let's collaborate
-
-👀 I'm always open to collaboration and exciting projects. If you have something in mind, feel free to reach out!
-
----
-
-
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..238655f
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ // Enable latest features
+ "lib": ["ESNext", "DOM"],
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleDetection": "force",
+ "jsx": "react-jsx",
+ "allowJs": true,
+
+ // Bundler mode
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "noEmit": true,
+
+ // Best practices
+ "strict": true,
+ "skipLibCheck": true,
+ "noFallthroughCasesInSwitch": true,
+
+ // Some stricter flags (disabled by default)
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noPropertyAccessFromIndexSignature": false
+ }
+}