Aller au contenu principal
ShadowAcademy

Les langages / Lua / Mini-projet final

Projets réels et best practices

Synthèse finale: comment structurer un projet Lua professionnel, conventions de code, documentation, déploiement, et exemples de projets complets. De débutant à expert!

Structure de projet professionnelle

-- Structure recommandée:
--[[
mon_projet/
├── src/              -- Code source
│   ├── init.lua      -- Point d'entrée
│   ├── core/         -- Logique métier
│   ├── utils/        -- Utilitaires
│   └── models/       -- Modèles de données
├── tests/            -- Tests unitaires
│   ├── test_core.lua
│   └── test_utils.lua
├── docs/             -- Documentation
│   └── api.md
├── config/           -- Configuration
│   └── config.lua
├── lib/              -- Dépendances externes
├── .luarc.json       -- Config LSP
└── README.md
]]

-- init.lua - Point d'entrée
local MyApp = {
  _VERSION = "1.0.0",
  _DESCRIPTION = "Mon application Lua",
  _LICENSE = "MIT"
}

-- Charger les modules
MyApp.Core = require("src.core.engine")
MyApp.Utils = require("src.utils.helpers")
MyApp.Config = require("config.config")

-- Initialisation
function MyApp.init(options)
  options = options or {}
  print("Initialisation de " .. MyApp._VERSION)
  -- Configuration...
end

return MyApp

Conventions et documentation

--- Module de gestion des utilisateurs
-- @module UserManager
-- @author Shadow
-- @license MIT
-- @copyright 2024

local UserManager = {}

--- Crée un nouveau gestionnaire d'utilisateurs
-- @return UserManager instance
function UserManager.new()
  local self = setmetatable({}, {__index = UserManager})
  self.users = {}
  return self
end

--- Ajoute un utilisateur
-- @param name string Le nom de l'utilisateur
-- @param email string L'email de l'utilisateur
-- @return table L'utilisateur créé
-- @raise Erreur si l'email est invalide
function UserManager:addUser(name, email)
  assert(type(name) == "string", "name doit être une string")
  assert(type(email) == "string", "email doit être une string")
  
  if not email:match("^[%w%.]+@[%w%.]+%.[%a]+$") then
    error("Email invalide: " .. email)
  end
  
  local user = {
    id = #self.users + 1,
    name = name,
    email = email,
    createdAt = os.time()
  }
  
  table.insert(self.users, user)
  return user
end

--- Trouve un utilisateur par ID
-- @param id number L'ID de l'utilisateur
-- @return table|nil L'utilisateur trouvé ou nil
function UserManager:findById(id)
  for _, user in ipairs(self.users) do
    if user.id == id then
      return user
    end
  end
  return nil
end

return UserManager

Configuration et environnements

-- config/config.lua
local Config = {}

-- Configuration par défaut
local defaults = {
  env = "development",
  debug = true,
  database = {
    host = "localhost",
    port = 5432,
    name = "myapp_dev"
  },
  logging = {
    level = "debug",
    file = "logs/app.log"
  },
  cache = {
    enabled = true,
    ttl = 3600
  }
}

-- Configuration par environnement
local environments = {
  production = {
    debug = false,
    database = {
      host = "prod-db.example.com",
      port = 5432,
      name = "myapp_prod"
    },
    logging = {
      level = "info",
      file = "/var/log/myapp.log"
    }
  },
  test = {
    database = {
      name = "myapp_test"
    },
    logging = {
      level = "warn"
    }
  }
}

-- Charger la config
function Config.load(env)
  env = env or os.getenv("LUA_ENV") or "development"
  
  -- Copier les defaults
  local config = {}
  for k, v in pairs(defaults) do
    if type(v) == "table" then
      config[k] = {}
      for k2, v2 in pairs(v) do
        config[k][k2] = v2
      end
    else
      config[k] = v
    end
  end
  
  -- Merger avec l'environnement
  if environments[env] then
    for k, v in pairs(environments[env]) do
      if type(v) == "table" then
        config[k] = config[k] or {}
        for k2, v2 in pairs(v) do
          config[k][k2] = v2
        end
      else
        config[k] = v
      end
    end
  end
  
  config.env = env
  return config
end

return Config

Projet complet: API REST simple

-- Exemple de mini API REST
local API = {}
local users = {}
local nextId = 1

-- Router simple
local Router = {
  routes = {GET = {}, POST = {}, PUT = {}, DELETE = {}}
}

function Router.add(method, path, handler)
  Router.routes[method][path] = handler
end

function Router.handle(method, path, params)
  local handler = Router.routes[method][path]
  if handler then
    return handler(params)
  else
    return {error = "Route not found", code = 404}
  end
end

-- Définir les routes
Router.add("GET", "/users", function(params)
  return {data = users, code = 200}
end)

Router.add("GET", "/users/:id", function(params)
  local user = users[tonumber(params.id)]
  if user then
    return {data = user, code = 200}
  else
    return {error = "User not found", code = 404}
  end
end)

Router.add("POST", "/users", function(params)
  local user = {
    id = nextId,
    name = params.name,
    email = params.email,
    createdAt = os.time()
  }
  users[nextId] = user
  nextId = nextId + 1
  return {data = user, code = 201}
end)

Router.add("DELETE", "/users/:id", function(params)
  local id = tonumber(params.id)
  if users[id] then
    users[id] = nil
    return {message = "User deleted", code = 200}
  else
    return {error = "User not found", code = 404}
  end
end)

-- Utilisation
print("=== API REST Demo ===")

-- POST /users
local response = Router.handle("POST", "/users", 
  {name = "Shadow", email = "shadow@example.com"})
print("Créé:", response.data.id, response.data.name)

-- GET /users
response = Router.handle("GET", "/users", {})
print("Total users:", #response.data)

-- GET /users/1
response = Router.handle("GET", "/users/:id", {id = 1})
print("User:", response.data.name)

1 exercice pour cette lecon

Les exercices demandent un compte : il faut bien enregistrer votre progression quelque part. La lecture, elle, reste libre. Creer un compte

  • Générateur de nombres premiers difficile 35 XP