#!/bin/bash # Script automatique : Markdown → PDF → Prévisualisation → Impression # Usage: ./md_to_print.sh fichier.md [options] set -euo pipefail # Version VERSION="1.0.0" # Codes de retour EXIT_SUCCESS=0 EXIT_GENERAL=1 EXIT_FILE_NOT_FOUND=2 EXIT_DEPS_MISSING=3 EXIT_PDF_CONVERSION=4 EXIT_PRINT_ERROR=5 EXIT_CONFIG_ERROR=6 EXIT_DOCKER_ERROR=7 EXIT_PERMISSIONS=8 EXIT_INVALID_ARG=9 EXIT_PRINTER_UNAVAILABLE=10 # Couleurs pour les messages RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # Variables par défaut PREVIEW=false PRINTER="" COPIES=1 PAGES="" ORIENTATION="portrait" SIZE="A4" QUALITY="normal" COLOR=false DUPLEX="none" INSTALL_DEPS=false KEEP_PDF=false CONVERT_ONLY=false CONFIG_FILE="" VERBOSE=false LOG_FILE="" LOG_LEVEL="INFO" # Répertoires OUTPUT_DIR="./output" LOG_DIR="./logs" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Fonction de logging log() { local level="$1" shift local message="$*" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') local log_entry="[${timestamp}] [${level}] ${message}" case "$level" in DEBUG) if [ "$VERBOSE" = true ] || [ "$LOG_LEVEL" = "DEBUG" ]; then echo -e "${BLUE}${log_entry}${NC}" >&2 fi ;; INFO) echo -e "${GREEN}${log_entry}${NC}" >&2 ;; WARN) echo -e "${YELLOW}${log_entry}${NC}" >&2 ;; ERROR) echo -e "${RED}${log_entry}${NC}" >&2 ;; esac if [ -n "$LOG_FILE" ] && [ -f "$LOG_FILE" ]; then echo "${log_entry}" >> "$LOG_FILE" fi } # Fonction d'aide show_help() { cat << EOF MD_to_Print v${VERSION} - Conversion Markdown → PDF → Impression Usage: $0 fichier.md [OPTIONS] OPTIONS: -p, --preview Prévisualiser le PDF avant impression -P, --printer NOM Spécifier l'imprimante -c, --copies N Nombre de copies (défaut: 1) -r, --pages RANGE Pages à imprimer (ex: 1-3,5,7-9) -o, --orientation DIR Orientation: portrait ou landscape (défaut: portrait) -s, --size SIZE Taille: A4, A3, Letter, etc. (défaut: A4) -q, --quality QUALITY Qualité: draft, normal, high (défaut: normal) -C, --color Impression en couleur -d, --duplex MODE Mode recto-verso: none, simplex, duplex (défaut: none) -i, --install-deps Installer les dépendances automatiquement -k, --keep-pdf Conserver le fichier PDF après impression --convert-only, --no-print Conversion uniquement (sans impression) --config FILE Fichier de configuration -v, --verbose Mode verbeux (logging DEBUG) -l, --log FILE Fichier de log -h, --help Afficher cette aide --version Afficher la version EXEMPLES: $0 document.md --preview $0 document.md --printer HP_LaserJet --copies 2 --color $0 document.md --pages 1-5 --orientation landscape $0 document.md --duplex duplex --quality high --config myconfig.conf CONFIGURATION: Le fichier de configuration utilise le format INI avec les sections: [default], [paths], [logging] Voir config.example.conf pour un exemple. CODES DE RETOUR: 0 : Succès 1 : Erreur générale 2 : Fichier introuvable 3 : Dépendances manquantes 4 : Erreur conversion PDF 5 : Erreur impression 6 : Erreur configuration 7 : Erreur Docker 8 : Permissions insuffisantes 9 : Argument invalide 10 : Imprimante non disponible EOF } # Fonction de chargement de configuration load_config() { local config_path="$1" if [ ! -f "$config_path" ]; then log ERROR "Fichier de configuration introuvable: $config_path" return $EXIT_CONFIG_ERROR fi log INFO "Chargement de la configuration depuis: $config_path" while IFS='=' read -r key value || [ -n "$key" ]; do # Ignorer les commentaires et lignes vides [[ "$key" =~ ^[[:space:]]*# ]] && continue [[ -z "${key// }" ]] && continue # Supprimer les espaces key=$(echo "$key" | tr -d '[:space:]') value=$(echo "$value" | tr -d '[:space:]') case "$key" in printer) [ -z "$PRINTER" ] && PRINTER="$value" ;; copies) [ "$COPIES" = 1 ] && COPIES="$value" ;; orientation) [ "$ORIENTATION" = "portrait" ] && ORIENTATION="$value" ;; size) [ "$SIZE" = "A4" ] && SIZE="$value" ;; quality) [ "$QUALITY" = "normal" ] && QUALITY="$value" ;; color) [ "$COLOR" = false ] && COLOR=$( [ "$value" = "true" ] && echo true || echo false ) ;; duplex) [ "$DUPLEX" = "none" ] && DUPLEX="$value" ;; preview) [ "$PREVIEW" = false ] && PREVIEW=$( [ "$value" = "true" ] && echo true || echo false ) ;; keep_pdf) [ "$KEEP_PDF" = false ] && KEEP_PDF=$( [ "$value" = "true" ] && echo true || echo false ) ;; output_dir) OUTPUT_DIR="$value" ;; log_dir) LOG_DIR="$value" ;; level) LOG_LEVEL="$value" ;; file) [ -z "$LOG_FILE" ] && LOG_FILE="$LOG_DIR/$value" ;; verbose) [ "$VERBOSE" = false ] && VERBOSE=$( [ "$value" = "true" ] && echo true || echo false ) ;; esac done < <(grep -v '^\[' "$config_path" | grep -v '^[[:space:]]*$') log INFO "Configuration chargée avec succès" return $EXIT_SUCCESS } # Fonction d'installation des dépendances install_dependencies() { log INFO "Vérification et installation des dépendances..." if ! command -v apt-get &> /dev/null; then log ERROR "apt-get non disponible. Installation manuelle requise." return $EXIT_DEPS_MISSING fi log INFO "Mise à jour de la liste des paquets..." if ! sudo apt-get update -qq; then log ERROR "Échec de la mise à jour des paquets" return $EXIT_DEPS_MISSING fi local deps_to_install=() if ! command -v pandoc &> /dev/null; then deps_to_install+=("pandoc") fi if ! command -v pdflatex &> /dev/null; then deps_to_install+=("texlive-latex-base" "texlive-fonts-recommended" "texlive-latex-extra") fi if ! command -v lp &> /dev/null; then deps_to_install+=("cups") fi if [ ${#deps_to_install[@]} -gt 0 ]; then log INFO "Installation: ${deps_to_install[*]}" if ! sudo apt-get install -y "${deps_to_install[@]}"; then log ERROR "Échec de l'installation des dépendances" return $EXIT_DEPS_MISSING fi log INFO "Dépendances installées avec succès" else log INFO "Toutes les dépendances sont déjà installées" fi return $EXIT_SUCCESS } # Fonction de vérification des dépendances check_dependencies() { local missing_deps=() if ! command -v pandoc &> /dev/null; then missing_deps+=("pandoc") fi if ! command -v pdflatex &> /dev/null; then missing_deps+=("texlive-latex-base") fi if ! command -v lp &> /dev/null; then missing_deps+=("cups") fi if [ ${#missing_deps[@]} -gt 0 ]; then log ERROR "Dépendances manquantes: ${missing_deps[*]}" log INFO "Utilisez l'option --install-deps pour installer automatiquement" return $EXIT_DEPS_MISSING fi return $EXIT_SUCCESS } # Fonction de prévisualisation preview_pdf() { local pdf_file="$1" if [ ! -f "$pdf_file" ]; then log ERROR "Fichier PDF introuvable pour prévisualisation: $pdf_file" return $EXIT_FILE_NOT_FOUND fi log INFO "Ouverture de la prévisualisation..." if command -v evince &> /dev/null; then evince "$pdf_file" 2>/dev/null & log DEBUG "Utilisation de evince pour la prévisualisation" elif command -v okular &> /dev/null; then okular "$pdf_file" 2>/dev/null & log DEBUG "Utilisation de okular pour la prévisualisation" elif command -v xdg-open &> /dev/null; then xdg-open "$pdf_file" 2>/dev/null & log DEBUG "Utilisation de xdg-open pour la prévisualisation" else log WARN "Aucun visualiseur PDF trouvé. Installation recommandée: evince ou okular" return $EXIT_DEPS_MISSING fi log INFO "Appuyez sur Entrée pour continuer avec l'impression..." read -r return $EXIT_SUCCESS } # Fonction de liste des imprimantes list_printers() { log INFO "Imprimantes disponibles:" if command -v lpstat &> /dev/null; then lpstat -p 2>/dev/null | grep -E "^printer" | awk '{print " - " $2}' || log WARN "Aucune imprimante trouvée" else log WARN "lpstat non disponible, impossible de lister les imprimantes" fi } # Fonction de validation de l'imprimante check_printer() { local printer_name="$1" if [ -z "$printer_name" ]; then return $EXIT_SUCCESS fi if ! command -v lpstat &> /dev/null; then log WARN "lpstat non disponible, impossible de vérifier l'imprimante" return $EXIT_SUCCESS fi if lpstat -p "$printer_name" &>/dev/null; then log DEBUG "Imprimante '$printer_name' disponible" return $EXIT_SUCCESS else log WARN "Imprimante '$printer_name' non disponible" list_printers return $EXIT_PRINTER_UNAVAILABLE fi } # Parsing des arguments MD_FILE="" ARGS=("$@") while [[ $# -gt 0 ]]; do case $1 in -p|--preview) PREVIEW=true shift ;; -P|--printer) if [ -z "${2:-}" ]; then log ERROR "Option --printer nécessite un argument" exit $EXIT_INVALID_ARG fi PRINTER="$2" shift 2 ;; -c|--copies) if [ -z "${2:-}" ]; then log ERROR "Option --copies nécessite un argument" exit $EXIT_INVALID_ARG fi COPIES="$2" shift 2 ;; -r|--pages) if [ -z "${2:-}" ]; then log ERROR "Option --pages nécessite un argument" exit $EXIT_INVALID_ARG fi PAGES="$2" shift 2 ;; -o|--orientation) if [ -z "${2:-}" ]; then log ERROR "Option --orientation nécessite un argument" exit $EXIT_INVALID_ARG fi ORIENTATION="$2" shift 2 ;; -s|--size) if [ -z "${2:-}" ]; then log ERROR "Option --size nécessite un argument" exit $EXIT_INVALID_ARG fi SIZE="$2" shift 2 ;; -q|--quality) if [ -z "${2:-}" ]; then log ERROR "Option --quality nécessite un argument" exit $EXIT_INVALID_ARG fi QUALITY="$2" shift 2 ;; -C|--color) COLOR=true shift ;; -d|--duplex) if [ -z "${2:-}" ]; then log ERROR "Option --duplex nécessite un argument" exit $EXIT_INVALID_ARG fi DUPLEX="$2" shift 2 ;; -i|--install-deps) INSTALL_DEPS=true shift ;; -k|--keep-pdf) KEEP_PDF=true shift ;; --convert-only|--no-print) CONVERT_ONLY=true KEEP_PDF=true shift ;; --config) if [ -z "${2:-}" ]; then log ERROR "Option --config nécessite un argument" exit $EXIT_INVALID_ARG fi CONFIG_FILE="$2" shift 2 ;; -v|--verbose) VERBOSE=true LOG_LEVEL="DEBUG" shift ;; -l|--log) if [ -z "${2:-}" ]; then log ERROR "Option --log nécessite un argument" exit $EXIT_INVALID_ARG fi LOG_FILE="$2" shift 2 ;; -h|--help) show_help exit $EXIT_SUCCESS ;; --version) echo "MD_to_Print v${VERSION}" exit $EXIT_SUCCESS ;; -*) log ERROR "Option inconnue: $1" show_help exit $EXIT_INVALID_ARG ;; *) if [ -z "$MD_FILE" ]; then MD_FILE="$1" else log ERROR "Trop de fichiers spécifiés: $MD_FILE et $1" exit $EXIT_INVALID_ARG fi shift ;; esac done # Vérification du fichier markdown if [ -z "$MD_FILE" ]; then log ERROR "Aucun fichier markdown spécifié" show_help exit $EXIT_INVALID_ARG fi # Résolution du chemin absolu if [[ "$MD_FILE" != /* ]]; then MD_FILE="$(realpath "$MD_FILE" 2>/dev/null || echo "$MD_FILE")" fi if [ ! -f "$MD_FILE" ]; then log ERROR "Le fichier '$MD_FILE' n'existe pas" exit $EXIT_FILE_NOT_FOUND fi # Vérification des permissions de lecture if [ ! -r "$MD_FILE" ]; then log ERROR "Permission de lecture refusée pour '$MD_FILE'" exit $EXIT_PERMISSIONS fi # Création des répertoires nécessaires mkdir -p "$OUTPUT_DIR" "$LOG_DIR" # Initialisation du fichier de log si spécifié if [ -n "$LOG_FILE" ]; then mkdir -p "$(dirname "$LOG_FILE")" touch "$LOG_FILE" fi # Chargement de la configuration si spécifiée if [ -n "$CONFIG_FILE" ]; then if ! load_config "$CONFIG_FILE"; then exit $EXIT_CONFIG_ERROR fi elif [ -f "$SCRIPT_DIR/config.conf" ]; then log DEBUG "Chargement de la configuration par défaut: $SCRIPT_DIR/config.conf" load_config "$SCRIPT_DIR/config.conf" || true fi # Installation des dépendances si demandée if [ "$INSTALL_DEPS" = true ]; then if ! install_dependencies; then exit $EXIT_DEPS_MISSING fi fi # Vérification des dépendances if ! check_dependencies; then exit $EXIT_DEPS_MISSING fi # Génération du nom du fichier PDF MD_BASENAME=$(basename "$MD_FILE" .md) PDF_FILE="$OUTPUT_DIR/${MD_BASENAME}.pdf" log INFO "=== Conversion Markdown → PDF ===" log INFO "Fichier source: $MD_FILE" log INFO "Fichier PDF: $PDF_FILE" # Conversion Markdown → PDF avec options # Détection du moteur PDF disponible (xelatex gère mieux Unicode) PDF_ENGINE="pdflatex" if command -v xelatex &> /dev/null; then PDF_ENGINE="xelatex" log DEBUG "Utilisation de xelatex pour meilleure gestion Unicode" fi PANDOC_OPTS=( "$MD_FILE" --from=markdown # Format d'entrée explicite (Markdown) --to=pdf # Format de sortie explicite (PDF) -o "$PDF_FILE" --pdf-engine="$PDF_ENGINE" --standalone # Document complet avec métadonnées -V geometry:margin=2cm -V fontsize=11pt -V documentclass=article -V papersize="$SIZE" ) # Options spécifiques pour xelatex (meilleure gestion Unicode) if [ "$PDF_ENGINE" = "xelatex" ]; then PANDOC_OPTS+=( -V mainfont="DejaVu Sans" -V monofont="DejaVu Sans Mono" ) fi # Ne pas forcer l'orientation dans le PDF - laisser CUPS gérer l'orientation lors de l'impression # Cela évite les conflits de mise en page # if [ "$ORIENTATION" = "landscape" ]; then # PANDOC_OPTS+=(-V geometry:landscape) # fi log DEBUG "Exécution: pandoc ${PANDOC_OPTS[*]}" if ! pandoc "${PANDOC_OPTS[@]}" 2>&1 | while IFS= read -r line; do log DEBUG "$line"; done; then log ERROR "Erreur lors de la conversion en PDF" exit $EXIT_PDF_CONVERSION fi if [ ! -f "$PDF_FILE" ]; then log ERROR "Le fichier PDF n'a pas été créé" exit $EXIT_PDF_CONVERSION fi log INFO "PDF créé avec succès: $PDF_FILE" # Vérification de l'imprimante si spécifiée if [ -n "$PRINTER" ]; then if ! check_printer "$PRINTER"; then log WARN "Tentative d'utilisation de l'imprimante par défaut" PRINTER="" fi fi # Prévisualisation si demandée if [ "$PREVIEW" = true ]; then if ! preview_pdf "$PDF_FILE"; then log WARN "Échec de la prévisualisation, continuation avec l'impression" fi fi # Préparation des options d'impression LP_OPTS=() if [ -n "$PRINTER" ]; then LP_OPTS+=(-d "$PRINTER") else log INFO "Utilisation de l'imprimante par défaut" list_printers fi LP_OPTS+=(-n "$COPIES") if [ -n "$PAGES" ]; then LP_OPTS+=(-o page-ranges="$PAGES") fi # Appliquer l'orientation via CUPS (le PDF reste en portrait) # Utiliser les valeurs standard IPP pour l'orientation if [ "$ORIENTATION" = "landscape" ]; then LP_OPTS+=(-o orientation-requested=4) fi LP_OPTS+=(-o media="$SIZE") # Ne pas utiliser fit-to-page car cela peut causer des problèmes de mise en page # Le PDF est déjà correctement dimensionné case "$QUALITY" in draft) LP_OPTS+=(-o print-quality=3) ;; high) LP_OPTS+=(-o print-quality=5) ;; *) LP_OPTS+=(-o print-quality=4) ;; esac if [ "$COLOR" = true ]; then LP_OPTS+=(-o ColorMode=Color) else LP_OPTS+=(-o ColorMode=Monochrome) fi case "$DUPLEX" in simplex) LP_OPTS+=(-o sides=one-sided) ;; duplex) LP_OPTS+=(-o sides=two-sided-long-edge) ;; *) LP_OPTS+=(-o sides=one-sided) ;; esac # Impression (sauf si --convert-only) if [ "$CONVERT_ONLY" = false ]; then log INFO "=== Impression ===" log INFO "Options:" log INFO " - Copies: $COPIES" log INFO " - Taille: $SIZE" log INFO " - Orientation: $ORIENTATION" log INFO " - Qualité: $QUALITY" log INFO " - Couleur: $([ "$COLOR" = true ] && echo "Oui" || echo "Non")" log INFO " - Recto-verso: $DUPLEX" [ -n "$PAGES" ] && log INFO " - Pages: $PAGES" [ -n "$PRINTER" ] && log INFO " - Imprimante: $PRINTER" log DEBUG "Exécution: lp ${LP_OPTS[*]} $PDF_FILE" if ! lp "${LP_OPTS[@]}" "$PDF_FILE" 2>&1 | while IFS= read -r line; do log DEBUG "$line"; done; then log ERROR "Erreur lors de l'impression" log INFO "Le fichier PDF a été conservé: $PDF_FILE" exit $EXIT_PRINT_ERROR fi log INFO "Impression lancée avec succès!" else log INFO "Mode conversion uniquement (pas d'impression)" fi # Gestion du fichier PDF if [ "$KEEP_PDF" = false ]; then read -p "Supprimer le fichier PDF temporaire? (o/N): " -n 1 -r echo if [[ $REPLY =~ ^[Oo]$ ]]; then rm "$PDF_FILE" log INFO "Fichier PDF supprimé" else log INFO "Fichier PDF conservé: $PDF_FILE" fi else log INFO "Fichier PDF conservé: $PDF_FILE" fi log INFO "=== Opération terminée avec succès ===" exit $EXIT_SUCCESS