#!/bin/bash # Script: md_table_to_ascii.sh # Convierte tablas Markdown a formato ASCII con mejor ajuste automático # Definir estilos de bordes declare -A border_styles border_styles["box"]="┌ ─ ┐ └ ┘ │ ├ ┤ ┬ ┴ ┼" border_styles["rounded"]="╭ ─ ╮ ╰ ╯ │ ├ ┤ ┬ ┴ ┼" border_styles["double"]="╔ ═ ╗ ╚ ╝ ║ ╠ ╣ ╦ ╩ ╬" border_styles["bold"]="┏ ━ ┓ ┗ ┛ ┃ ┣ ┫ ┳ ┻ ╋" # Estilo por defecto DEFAULT_STYLE="box" # Función para obtener caracteres de borde según el estilo get_border_chars() { local style="$1" case "$style" in "box") echo "┌ ─ ┐ └ ┘ │ ├ ┤ ┬ ┴ ┼" ;; "rounded") echo "╭ ─ ╮ ╰ ╯ │ ├ ┤ ┬ ┴ ┼" ;; "double") echo "╔ ═ ╗ ╚ ╝ ║ ╠ ╣ ╦ ╩ ╬" ;; "bold") echo "┏ ━ ┓ ┗ ┛ ┃ ┣ ┫ ┳ ┻ ╋" ;; *) echo "┌ ─ ┐ └ ┘ │ ├ ┤ ┬ ┴ ┼" ;; esac } convert_md_table_to_ascii() { local input_file="$1" local style="${2:-$DEFAULT_STYLE}" if [[ ! -f "$input_file" ]]; then echo "Error: Archivo '$input_file' no encontrado" return 1 fi # Obtener caracteres de borde según el estilo local border_chars=($(get_border_chars "$style")) awk -v style="$style" \ -v top_left="${border_chars[0]}" \ -v horizontal="${border_chars[1]}" \ -v top_right="${border_chars[2]}" \ -v bottom_left="${border_chars[3]}" \ -v bottom_right="${border_chars[4]}" \ -v vertical="${border_chars[5]}" \ -v t_left="${border_chars[6]}" \ -v t_right="${border_chars[7]}" \ -v t_top="${border_chars[8]}" \ -v t_bottom="${border_chars[9]}" \ -v cross="${border_chars[10]}" ' function trim(str) { gsub(/^[ \t]+|[ \t]+$/, "", str) return str } function calculate_column_widths() { for (i = 1; i <= max_cols; i++) { # Ancho mínimo de 3 caracteres para columnas col_width[i] = 3 } # Calcular para encabezados for (i = 1; i <= max_cols; i++) { len = length(trim(headers[i])) if (len > col_width[i]) col_width[i] = len } # Calcular para todas las filas de datos for (r = 1; r <= row_count; r++) { split(rows[r], row_fields, "|") for (i = 1; i <= max_cols; i++) { if (i in row_fields) { len = length(trim(row_fields[i])) if (len > col_width[i]) col_width[i] = len } } } # Verificar que no haya columnas vacías o muy pequeñas for (i = 1; i <= max_cols; i++) { if (col_width[i] < 3) col_width[i] = 3 } } function print_table_border(type) { line = "" if (type == "top") { line = top_left end_char = top_right join_char = t_top } else if (type == "middle") { line = t_left end_char = t_right join_char = cross } else if (type == "bottom") { line = bottom_left end_char = bottom_right join_char = t_bottom } else if (type == "internal") { line = t_left end_char = t_right join_char = cross } for (i = 1; i <= max_cols; i++) { # +2 por los espacios a cada lado del contenido for (j = 0; j < col_width[i] + 2; j++) { line = line horizontal } if (i < max_cols) line = line join_char } line = line end_char print line } function print_table_row(values, is_header) { line = vertical for (i = 1; i <= max_cols; i++) { value = trim(values[i]) # Ajustar el valor al ancho de la columna line = line " " sprintf("%-*s", col_width[i], value) " " vertical } print line } BEGIN { max_cols = 0 row_count = 0 separator_count = 0 is_table = 0 has_data = 0 } /^[|].*[|]$/ { if (!is_table) is_table = 1 # Eliminar pipes iniciales y finales gsub(/^\||\|$/, "", $0) # Dividir en campos n = split($0, fields, "|") if (n > max_cols) max_cols = n if (NR == 1) { # Primera fila (encabezados) for (i = 1; i <= n; i++) { headers[i] = fields[i] } } else if (NR == 2 && /^[\|\-[:space:]]+$/) { # Segunda fila (separador markdown) - la ignoramos para cálicos separator_found = 1 } else { # Filas de datos row_data = "" for (i = 1; i <= n; i++) { if (i > 1) row_data = row_data "|" row_data = row_data fields[i] } rows[++row_count] = row_data row_separator[row_count] = 0 # Por defecto, no hay separador después has_data = 1 } next } /^---+$/ { # Línea con --- (separador interno) if (is_table && has_data) { # Marcar que después de la última fila añadida hay un separador row_separator[row_count] = 1 separator_count++ } next } { # Si encontramos una línea que no es tabla después de empezar una tabla, terminamos if (is_table && !/^[|].*[|]$/ && !/^---+$/) { is_table = 0 } } END { if (!is_table || max_cols == 0) { print "No se encontró una tabla Markdown válida" print "Formato esperado:" print "| Header 1 | Header 2 | Header 3 |" print "|----------|----------|----------|" print "| Data 1 | Data 2 | Data 3 |" exit 0 } # Calcular anchos de columna calculate_column_widths() # Imprimir tabla print_table_border("top") print_table_row(headers, 1) if (row_count > 0) { # Imprimir separador después del header print_table_border("middle") # Imprimir filas de datos con separadores internos for (r = 1; r <= row_count; r++) { split(rows[r], row_fields, "|") print_table_row(row_fields, 0) # Imprimir separador interno si está marcado (y no es la última fila) if (row_separator[r] == 1 && r < row_count) { print_table_border("internal") } } } print_table_border("bottom") # Información de depuración (opcional) if (DEBUG == "1") { print "\n--- DEBUG INFO ---" print "Estilo: " style print "Columnas: " max_cols print "Filas de datos: " row_count print "Separadores internos: " separator_count print "Anchos de columna:" for (i = 1; i <= max_cols; i++) { print " Col " i ": " col_width[i] " chars" } print "Separadores por fila:" for (r = 1; r <= row_count; r++) { print " Fila " r ": " row_separator[r] } } } ' DEBUG="${DEBUG:-0}" "$input_file" } # Versión mejorada para entrada directa convert_md_table_direct() { local input="$1" local style="${2:-$DEFAULT_STYLE}" # Obtener caracteres de borde según el estilo local border_chars=($(get_border_chars "$style")) echo "$input" | awk -v style="$style" \ -v top_left="${border_chars[0]}" \ -v horizontal="${border_chars[1]}" \ -v top_right="${border_chars[2]}" \ -v bottom_left="${border_chars[3]}" \ -v bottom_right="${border_chars[4]}" \ -v vertical="${border_chars[5]}" \ -v t_left="${border_chars[6]}" \ -v t_right="${border_chars[7]}" \ -v t_top="${border_chars[8]}" \ -v t_bottom="${border_chars[9]}" \ -v cross="${border_chars[10]}" ' function trim(str) { gsub(/^[ \t]+|[ \t]+$/, "", str) return str } function calculate_column_widths() { for (i = 1; i <= max_cols; i++) { col_width[i] = 3 # Ancho mínimo } # Encabezados for (i = 1; i <= max_cols; i++) { len = length(trim(headers[i])) if (len > col_width[i]) col_width[i] = len } # Datos for (r = 1; r <= row_count; r++) { split(rows[r], row_fields, "|") for (i = 1; i <= max_cols; i++) { if (i in row_fields) { len = length(trim(row_fields[i])) if (len > col_width[i]) col_width[i] = len } } } } function print_border(type) { if (type == "top") { line = top_left; end_char = top_right; join_char = t_top } else if (type == "middle") { line = t_left; end_char = t_right; join_char = cross } else if (type == "internal") { line = t_left; end_char = t_right; join_char = cross } else { line = bottom_left; end_char = bottom_right; join_char = t_bottom } for (i = 1; i <= max_cols; i++) { for (j = 0; j < col_width[i] + 2; j++) line = line horizontal if (i < max_cols) line = line join_char } print line end_char } function print_row(values) { line = vertical for (i = 1; i <= max_cols; i++) { value = trim(values[i]) line = line " " sprintf("%-*s", col_width[i], value) " " vertical } print line } BEGIN { max_cols = 0; row_count = 0; is_table = 0; has_data = 0 } /^[|].*[|]$/ { if (!is_table) is_table = 1 gsub(/^\||\|$/, "", $0) n = split($0, fields, "|") if (n > max_cols) max_cols = n if (NR == 1) { for (i = 1; i <= n; i++) headers[i] = fields[i] } else if (NR == 2 && /^[\|\-[:space:]]+$/) { separator_found = 1 } else { row_data = "" for (i = 1; i <= n; i++) { if (i > 1) row_data = row_data "|" row_data = row_data fields[i] } rows[++row_count] = row_data row_separator[row_count] = 0 has_data = 1 } } /^---+$/ { if (is_table && has_data) { row_separator[row_count] = 1 } } END { if (!is_table || max_cols == 0) { print "No se encontró tabla Markdown válida en la entrada" exit 0 } calculate_column_widths() print_border("top") print_row(headers) if (row_count > 0) { print_border("middle") for (r = 1; r <= row_count; r++) { split(rows[r], row_fields, "|") print_row(row_fields) if (row_separator[r] == 1 && r < row_count) { print_border("internal") } } } print_border("bottom") } ' } # Función de prueba actualizada test_table_conversion() { local style="${1:-$DEFAULT_STYLE}" echo "🧪 Probando conversión de tabla (estilo: $style)..." # Tabla de prueba con 4 entradas y separadores cat > test_table.md << 'EOF' | Departamento | Nombre | Edad | Ciudad | Ocupación | |--------------|--------|------|--------|-----------| | Administración | Ana García | 28 | Madrid | Gerencia | | | Carlos López | 35 | Barcelona | Vice-gerencia | --- | Marketing | María Torres | 42 | Valencia | Gerente | | RRHH | David Chen | 29 | Sevilla | Reclutador | EOF echo "📊 Tabla original:" cat test_table.md echo -e "\n📋 Tabla convertida (estilo: $style):" # Usar la función interna directamente en lugar de llamar al script if [[ -f "test_table.md" ]]; then convert_md_table_to_ascii "test_table.md" "$style" rm test_table.md else echo "Error: No se pudo crear el archivo de prueba" fi } # Mostrar ayuda show_help() { echo "Uso: $0 [OPCIONES] " echo "" echo "Opciones:" echo " -d, --direct Entrada directa" echo " -s, --style ESTILO Estilo de bordes: box, rounded, double, bold (defecto: box)" echo " -t, --test [ESTILO] Probar conversión (opcionalmente con estilo específico)" echo " -h, --help Mostrar esta ayuda" echo "" echo "Ejemplos:" echo " $0 tabla.md" echo " $0 --style=double tabla.md" echo " $0 --direct --style=rounded" echo " cat tabla.md | $0 --direct --style=bold" echo " $0 --test double" echo "" echo "Estilos disponibles:" echo " box: ┌────┐ (actual)" echo " rounded: ╭────╮ (esquinas redondeadas)" echo " double: ╔════╗ (líneas dobles)" echo " bold: ┏━━━━┓ (líneas gruesas)" } # Procesar argumentos process_arguments() { local input_file="" local style="$DEFAULT_STYLE" local mode="file" local test_mode=false local test_style="" while [[ $# -gt 0 ]]; do case "$1" in -d|--direct) mode="direct" shift ;; -s=*|--style=*) style="${1#*=}" shift ;; -s|--style) style="$2" shift 2 ;; -t|--test) test_mode=true if [[ -n "$2" && ! "$2" =~ ^- ]]; then test_style="$2" shift fi shift ;; -h|--help) show_help exit 0 ;; -*) echo "Error: Opción desconocida $1" show_help exit 1 ;; *) input_file="$1" shift ;; esac done # Validar estilo if [[ ! " box rounded double bold " =~ " $style " ]]; then echo "Error: Estilo '$style' no válido. Usa: box, rounded, double, bold" exit 1 fi if [[ "$test_mode" == true ]]; then test_table_conversion "${test_style:-$style}" elif [[ "$mode" == "direct" ]]; then echo "Pega tu tabla Markdown (Ctrl+D para finalizar):" input=$(cat) convert_md_table_direct "$input" "$style" else if [[ -z "$input_file" ]]; then echo "Error: Se requiere archivo de entrada" show_help exit 1 fi convert_md_table_to_ascii "$input_file" "$style" fi } # Ejecutar script principal if [[ $# -eq 0 ]]; then show_help else process_arguments "$@" fi