From 61d84601497fcdc5949071dc64eba2b449ec9855 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sun, 12 Apr 2026 13:54:15 +0200 Subject: [PATCH] feat: add bash shell utility functions 12 funciones Bash del dominio shell: utilidades de scripting (bash_log, bash_colors, bash_check_deps, bash_confirm, bash_handle_error, bash_safe_run), manipulacion de texto (convert_text_case), estructura de proyectos (create_project_structure), y operaciones git (git_clean_branches, git_log_visual, git_push_all_remotes, git_repo_status). Cada una con su .sh y .md de frontmatter. --- bash/functions/shell/bash_check_deps.md | 55 ++++++++ bash/functions/shell/bash_check_deps.sh | 75 +++++++++++ bash/functions/shell/bash_colors.md | 47 +++++++ bash/functions/shell/bash_colors.sh | 41 ++++++ bash/functions/shell/bash_confirm.md | 52 +++++++ bash/functions/shell/bash_confirm.sh | 36 +++++ bash/functions/shell/bash_handle_error.md | 57 ++++++++ bash/functions/shell/bash_handle_error.sh | 47 +++++++ bash/functions/shell/bash_log.md | 52 +++++++ bash/functions/shell/bash_log.sh | 64 +++++++++ bash/functions/shell/bash_safe_run.md | 52 +++++++ bash/functions/shell/bash_safe_run.sh | 44 ++++++ bash/functions/shell/convert_text_case.md | 54 ++++++++ bash/functions/shell/convert_text_case.sh | 73 ++++++++++ .../shell/create_project_structure.md | 44 ++++++ .../shell/create_project_structure.sh | 114 ++++++++++++++++ bash/functions/shell/git_clean_branches.md | 51 +++++++ bash/functions/shell/git_clean_branches.sh | 126 +++++++++++++++++ bash/functions/shell/git_log_visual.md | 56 ++++++++ bash/functions/shell/git_log_visual.sh | 75 +++++++++++ bash/functions/shell/git_push_all_remotes.md | 44 ++++++ bash/functions/shell/git_push_all_remotes.sh | 127 ++++++++++++++++++ bash/functions/shell/git_repo_status.md | 41 ++++++ bash/functions/shell/git_repo_status.sh | 95 +++++++++++++ 24 files changed, 1522 insertions(+) create mode 100644 bash/functions/shell/bash_check_deps.md create mode 100644 bash/functions/shell/bash_check_deps.sh create mode 100644 bash/functions/shell/bash_colors.md create mode 100644 bash/functions/shell/bash_colors.sh create mode 100644 bash/functions/shell/bash_confirm.md create mode 100644 bash/functions/shell/bash_confirm.sh create mode 100644 bash/functions/shell/bash_handle_error.md create mode 100644 bash/functions/shell/bash_handle_error.sh create mode 100644 bash/functions/shell/bash_log.md create mode 100644 bash/functions/shell/bash_log.sh create mode 100644 bash/functions/shell/bash_safe_run.md create mode 100644 bash/functions/shell/bash_safe_run.sh create mode 100644 bash/functions/shell/convert_text_case.md create mode 100644 bash/functions/shell/convert_text_case.sh create mode 100644 bash/functions/shell/create_project_structure.md create mode 100644 bash/functions/shell/create_project_structure.sh create mode 100644 bash/functions/shell/git_clean_branches.md create mode 100644 bash/functions/shell/git_clean_branches.sh create mode 100644 bash/functions/shell/git_log_visual.md create mode 100644 bash/functions/shell/git_log_visual.sh create mode 100644 bash/functions/shell/git_push_all_remotes.md create mode 100644 bash/functions/shell/git_push_all_remotes.sh create mode 100644 bash/functions/shell/git_repo_status.md create mode 100644 bash/functions/shell/git_repo_status.sh diff --git a/bash/functions/shell/bash_check_deps.md b/bash/functions/shell/bash_check_deps.md new file mode 100644 index 00000000..1f54401e --- /dev/null +++ b/bash/functions/shell/bash_check_deps.md @@ -0,0 +1,55 @@ +--- +name: bash_check_deps +kind: function +lang: bash +domain: shell +version: "1.0.0" +purity: impure +signature: "check_command(cmd: string, [error_code: string], [description: string]) -> void; check_commands(cmd...: string) -> void; check_directory(dir: string, [msg: string]) -> void; check_file(file: string, [msg: string]) -> void" +description: "Verifica existencia de comandos, directorios y archivos con output formateado. Complementa assert_command_exists con mensajes de error detallados y logging." +tags: [bash, check, dependency, command, exists, validation] +uses_functions: [bash_log_bash_shell] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: cmd + desc: "nombre del comando a verificar en PATH" + - name: error_code + desc: "codigo identificador del error; default COMMAND_NOT_FOUND" + - name: description + desc: "mensaje de error personalizado; default 'El comando CMD no esta disponible'" + - name: dir + desc: "ruta del directorio a verificar" + - name: file + desc: "ruta del archivo a verificar" +output: "exit code 0 si todas las verificaciones pasan; exit code 1 en caso de fallo con mensaje de error formateado" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/shell/bash_check_deps.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/lib/common.sh" +--- + +## Ejemplo + +```bash +source bash/functions/shell/bash_check_deps.sh + +check_command "docker" "DOCKER_NOT_FOUND" "Docker no esta instalado" +check_commands "git" "curl" "jq" +check_directory "/var/data" "El directorio de datos no existe" +check_file "/etc/config.yaml" "Falta el archivo de configuracion" +``` + +## Notas + +`check_command` acepta un error_code y descripcion opcionales para mensajes mas descriptivos que `assert_command_exists`. Usa `debug` internamente para loggear cada verificacion. + +`check_commands` verifica multiples comandos en una sola llamada y reporta todos los faltantes antes de retornar 1. + +Sourcea `bash_log.sh` automaticamente, que a su vez sourcea `bash_colors.sh`. No es necesario sourcea dependencias por separado. diff --git a/bash/functions/shell/bash_check_deps.sh b/bash/functions/shell/bash_check_deps.sh new file mode 100644 index 00000000..ace6c94c --- /dev/null +++ b/bash/functions/shell/bash_check_deps.sh @@ -0,0 +1,75 @@ +# bash_check_deps +# --------------- +# Verifica existencia de comandos, directorios y archivos. +# Output formateado con colores via bash_log. +# +# USO (sourced): +# source bash_check_deps.sh +# check_command "docker" "DOCKER_NOT_FOUND" "Docker no esta instalado" +# check_commands "git" "curl" "jq" +# check_directory "/path/to/dir" +# check_file "/path/to/file" + +SCRIPT_DIR_BASH_CHECK="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR_BASH_CHECK/bash_log.sh" +bash_log_init + +check_command() { + local cmd="$1" + local error_code="${2:-COMMAND_NOT_FOUND}" + local description="${3:-El comando '$cmd' no esta disponible}" + + debug "Verificando comando: $cmd" + + if ! command -v "$cmd" &> /dev/null; then + error "$description ($error_code)" + return 1 + fi + + return 0 +} + +check_commands() { + local failed=0 + for cmd in "$@"; do + if ! command -v "$cmd" &> /dev/null; then + error "Comando no encontrado: $cmd" + failed=1 + fi + done + + if [ $failed -eq 1 ]; then + error "Faltan multiples dependencias requeridas" + return 1 + fi + + return 0 +} + +check_directory() { + local dir="$1" + local error_msg="${2:-El directorio '$dir' no existe}" + + debug "Verificando directorio: $dir" + + if [ ! -d "$dir" ]; then + error "$error_msg" + return 1 + fi + + return 0 +} + +check_file() { + local file="$1" + local error_msg="${2:-El archivo '$file' no existe}" + + debug "Verificando archivo: $file" + + if [ ! -f "$file" ]; then + error "$error_msg" + return 1 + fi + + return 0 +} diff --git a/bash/functions/shell/bash_colors.md b/bash/functions/shell/bash_colors.md new file mode 100644 index 00000000..f466d997 --- /dev/null +++ b/bash/functions/shell/bash_colors.md @@ -0,0 +1,47 @@ +--- +name: bash_colors +kind: function +lang: bash +domain: shell +version: "1.0.0" +purity: pure +signature: "bash_colors() -> void" +description: "Exporta variables ANSI de colores, caracteres box drawing y simbolos unicode para uso en scripts de terminal." +tags: [bash, colors, ansi, terminal, symbols, box] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: [] +params: [] +output: "exporta variables de entorno con codigos ANSI: colores (RED, GREEN, BLUE, etc.), box drawing (BOX_TL, BOX_H, etc.) y simbolos (CHECKMARK, CROSS, ARROW, etc.)" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/shell/bash_colors.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/lib/common.sh" +--- + +## Ejemplo + +```bash +source bash/functions/shell/bash_colors.sh +bash_colors + +echo -e "${GREEN}Todo bien${NC}" +echo -e "${RED}${CROSS} Error detectado${NC}" +echo -e "${BOX_TL}${BOX_H}${BOX_TR}" +``` + +## Notas + +Funcion pura: solo define y exporta variables de entorno, sin I/O ni efectos secundarios. Debe llamarse una vez antes de usar cualquier variable de color o simbolo. + +Variables de color disponibles: PURPLE, MAGENTA, GREEN, BLUE, YELLOW, RED, CYAN, ORANGE, GRAY, DIM_GRAY, BOLD, DIM, NC. + +Variables box drawing: BOX_TL, BOX_TR, BOX_BL, BOX_BR, BOX_H, BOX_V, BOX_ML, BOX_MR, BOX_SEP. + +Simbolos unicode: CHECKMARK, CROSS, ARROW, BULLET, WARNING, INFO. diff --git a/bash/functions/shell/bash_colors.sh b/bash/functions/shell/bash_colors.sh new file mode 100644 index 00000000..fcdfe6d3 --- /dev/null +++ b/bash/functions/shell/bash_colors.sh @@ -0,0 +1,41 @@ +# bash_colors +# ----------- +# Variables ANSI de color, caracteres box drawing y simbolos para scripts. +# Diseñado para ser sourced por otros scripts. +# +# USO (sourced): +# source bash_colors.sh +# bash_colors + +bash_colors() { + export PURPLE='\033[35m' + export MAGENTA='\033[35m' + export GREEN='\033[0;32m' + export BLUE='\033[0;34m' + export YELLOW='\033[1;33m' + export RED='\033[0;31m' + export CYAN='\033[0;36m' + export ORANGE='\033[0;33m' + export GRAY='\033[0;90m' + export DIM_GRAY='\033[2;37m' + export BOLD='\033[1m' + export DIM='\033[2m' + export NC='\033[0m' + + export BOX_TL="╔" + export BOX_TR="╗" + export BOX_BL="╚" + export BOX_BR="╝" + export BOX_H="═" + export BOX_V="║" + export BOX_ML="╠" + export BOX_MR="╣" + export BOX_SEP="─" + + export CHECKMARK="✓" + export CROSS="✗" + export ARROW="→" + export BULLET="•" + export WARNING="⚠" + export INFO="ℹ" +} diff --git a/bash/functions/shell/bash_confirm.md b/bash/functions/shell/bash_confirm.md new file mode 100644 index 00000000..dbab4b23 --- /dev/null +++ b/bash/functions/shell/bash_confirm.md @@ -0,0 +1,52 @@ +--- +name: bash_confirm +kind: function +lang: bash +domain: shell +version: "1.0.0" +purity: impure +signature: "bash_confirm(prompt: string, [default: string]) -> exit_code" +description: "Dialogo interactivo de confirmacion y/n con valor por defecto configurable. Soporta respuestas yes/y/si." +tags: [bash, confirm, prompt, interactive, dialog] +uses_functions: [bash_colors_bash_shell] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: prompt + desc: "pregunta a mostrar al usuario; default '¿Continuar?'" + - name: default + desc: "valor por defecto cuando el usuario presiona Enter sin escribir nada; 'y' o 'n'; default 'n'" +output: "exit code 0 si el usuario confirma (y/yes/si), exit code 1 si niega o acepta el default negativo" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/shell/bash_confirm.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/lib/common.sh" +--- + +## Ejemplo + +```bash +source bash/functions/shell/bash_confirm.sh + +# Con default no (muestra [y/N]) +if bash_confirm "¿Deseas continuar?"; then + echo "Continuando..." +fi + +# Con default yes (muestra [Y/n]) +bash_confirm "¿Eliminar archivos temporales?" "y" && rm -rf /tmp/cache +``` + +## Notas + +El prompt muestra el default en mayuscula: `[Y/n]` cuando default=y, `[y/N]` cuando default=n. Si el usuario presiona Enter sin escribir, se usa el valor por defecto. + +Acepta variantes: y, Y, yes, YES, si, SI como afirmativo. Cualquier otra respuesta se trata como negativo. + +Usa colores de `bash_colors` para el prompt en amarillo. Requiere terminal interactiva (lee de stdin con `read -r`). diff --git a/bash/functions/shell/bash_confirm.sh b/bash/functions/shell/bash_confirm.sh new file mode 100644 index 00000000..53d33321 --- /dev/null +++ b/bash/functions/shell/bash_confirm.sh @@ -0,0 +1,36 @@ +# bash_confirm +# ------------ +# Dialogo de confirmacion y/n con valor por defecto. +# +# USO (sourced): +# source bash_confirm.sh +# bash_confirm "Continuar?" && echo "Si" || echo "No" +# bash_confirm "Eliminar?" "y" # default yes + +SCRIPT_DIR_BASH_CONFIRM="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR_BASH_CONFIRM/bash_colors.sh" +bash_colors + +bash_confirm() { + local prompt="${1:-¿Continuar?}" + local default="${2:-n}" + + if [ "$default" = "y" ]; then + prompt="$prompt [Y/n]" + else + prompt="$prompt [y/N]" + fi + + echo -ne "${YELLOW}$prompt ${NC}" + read -r response + + response=${response:-$default} + case "$response" in + [yY][eE][sS]|[yY]|[sS][iI]) + return 0 + ;; + *) + return 1 + ;; + esac +} diff --git a/bash/functions/shell/bash_handle_error.md b/bash/functions/shell/bash_handle_error.md new file mode 100644 index 00000000..266949e9 --- /dev/null +++ b/bash/functions/shell/bash_handle_error.md @@ -0,0 +1,57 @@ +--- +name: bash_handle_error +kind: function +lang: bash +domain: shell +version: "1.0.0" +purity: impure +signature: "handle_error(error_code: string, error_description: string, [solution: string]) -> exit_code" +description: "Muestra un box de error formateado con contexto del fallo: script, linea, funcion, directorio y usuario. Registra en log." +tags: [bash, error, handler, box, formatted, context] +uses_functions: [bash_colors_bash_shell, bash_log_bash_shell] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: error_code + desc: "codigo identificador del error, ej: BUILD_FAILED, DB_CONNECTION_ERROR" + - name: error_description + desc: "descripcion legible del error para mostrar al usuario" + - name: solution + desc: "solucion sugerida; opcional; si se provee se muestra en seccion separada" +output: "box de error formateado en stdout con contexto (script, linea, funcion, directorio, usuario) + registro en log; retorna exit code 1" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/shell/bash_handle_error.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/lib/common.sh" +--- + +## Ejemplo + +```bash +source bash/functions/shell/bash_handle_error.sh + +build_project() { + go build ./... || handle_error "BUILD_FAILED" \ + "La compilacion del proyecto fallo" \ + "Ejecuta 'go mod tidy' y verifica que todas las dependencias esten instaladas" +} + +connect_db() { + psql "$DB_URL" -c '\q' 2>/dev/null || handle_error "DB_CONNECTION_ERROR" \ + "No se pudo conectar a la base de datos" +} +``` + +## Notas + +El box usa caracteres unicode de `bash_colors` (BOX_TL, BOX_H, etc.) para el borde en rojo. La informacion de contexto se extrae de `BASH_SOURCE`, `BASH_LINENO` y `FUNCNAME` con offset 2 para apuntar al caller del caller. + +La funcion siempre retorna 1, permitiendo usarla como `cmd || handle_error ...` en pipelines de error. + +Usa `bash_colors` y `bash_log` como dependencias. Sourcea ambas automaticamente al cargarse. diff --git a/bash/functions/shell/bash_handle_error.sh b/bash/functions/shell/bash_handle_error.sh new file mode 100644 index 00000000..1a2a5924 --- /dev/null +++ b/bash/functions/shell/bash_handle_error.sh @@ -0,0 +1,47 @@ +# bash_handle_error +# ----------------- +# Muestra un box de error formateado con contexto del fallo. +# Incluye script, linea, funcion y directorio. +# +# USO (sourced): +# source bash_handle_error.sh +# handle_error "BUILD_FAILED" "La compilacion fallo" "Verifica las dependencias" + +SCRIPT_DIR_BASH_HANDLE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR_BASH_HANDLE/bash_colors.sh" +source "$SCRIPT_DIR_BASH_HANDLE/bash_log.sh" +bash_colors +bash_log_init + +handle_error() { + local error_code="$1" + local error_description="$2" + local solution="$3" + + echo "" + echo -e "${RED}╔════════════════════════════════════════════════════════════╗${NC}" + echo -e "${RED}║ ${CROSS} ERROR DETECTADO ║${NC}" + echo -e "${RED}╚════════════════════════════════════════════════════════════╝${NC}" + echo "" + echo -e "${RED}${BOLD}Error:${NC} ${error_description}" + echo -e "${GRAY}Codigo: ${error_code}${NC}" + echo "" + + if [ -n "$solution" ]; then + echo -e "${YELLOW}${INFO} Solucion sugerida:${NC}" + echo -e " ${solution}" + echo "" + fi + + echo -e "${GRAY}${INFO} Informacion adicional:${NC}" + echo -e " ${GRAY}${BULLET} Script: ${BASH_SOURCE[2]:-desconocido}${NC}" + echo -e " ${GRAY}${BULLET} Linea: ${BASH_LINENO[1]:-desconocido}${NC}" + echo -e " ${GRAY}${BULLET} Funcion: ${FUNCNAME[2]:-main}${NC}" + echo -e " ${GRAY}${BULLET} Directorio: $(pwd)${NC}" + echo -e " ${GRAY}${BULLET} Usuario: $(whoami)${NC}" + echo "" + + log "ERROR" "Code: $error_code | Description: $error_description | Script: ${BASH_SOURCE[2]} | Line: ${BASH_LINENO[1]}" + + return 1 +} diff --git a/bash/functions/shell/bash_log.md b/bash/functions/shell/bash_log.md new file mode 100644 index 00000000..89c7e490 --- /dev/null +++ b/bash/functions/shell/bash_log.md @@ -0,0 +1,52 @@ +--- +name: bash_log +kind: function +lang: bash +domain: shell +version: "1.0.0" +purity: impure +signature: "bash_log_init() -> void; success(msg: string) -> void; info(msg: string) -> void; warning(msg: string) -> void; error(msg: string) -> void; debug(msg: string) -> void; progress(msg: string) -> void" +description: "Funciones de logging con colores para scripts bash. Incluye niveles success/info/warning/error/debug/progress con escritura a archivo de log." +tags: [bash, log, logging, colors, terminal] +uses_functions: [bash_colors_bash_shell] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: msg + desc: "mensaje a mostrar y registrar en el archivo de log" +output: "mensaje formateado con colores en stdout/stderr y registro con timestamp en archivo de log (ERROR_LOG_FILE)" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/shell/bash_log.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/lib/common.sh" +--- + +## Ejemplo + +```bash +source bash/functions/shell/bash_log.sh +bash_log_init + +success "Servicio iniciado correctamente" +info "Procesando 42 registros..." +warning "El puerto 8080 ya esta en uso" +error "No se pudo conectar a la base de datos" +debug "Valor de variable: $VAR" +progress "Descargando imagen Docker..." +``` + +## Notas + +Llama a `bash_log_init` una vez al inicio para configurar ERROR_LOG_FILE y DEBUG_MODE. Por defecto el log va a `/tmp/script-errors-YYYYMMDD.log`. + +La variable de entorno `DEBUG_MODE=1` activa los mensajes de debug a stderr y muestra timestamps en todos los logs. + +`error` escribe a stderr; el resto a stdout. Todos los niveles escriben al archivo de log independientemente de DEBUG_MODE. + +Fuente: sourcea `bash_colors.sh` automaticamente al cargarse. diff --git a/bash/functions/shell/bash_log.sh b/bash/functions/shell/bash_log.sh new file mode 100644 index 00000000..ab9c3ca2 --- /dev/null +++ b/bash/functions/shell/bash_log.sh @@ -0,0 +1,64 @@ +# bash_log +# -------- +# Funciones de logging con colores para scripts. +# Incluye: log, success, info, warning, error, debug, progress. +# +# USO (sourced): +# source bash_log.sh +# bash_log_init +# success "Operacion completada" +# info "Procesando..." +# error "Algo fallo" + +SCRIPT_DIR_BASH_LOG="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR_BASH_LOG/bash_colors.sh" +bash_colors + +bash_log_init() { + export ERROR_LOG_FILE="${ERROR_LOG_FILE:-/tmp/script-errors-$(date +%Y%m%d).log}" + export DEBUG_MODE="${DEBUG_MODE:-0}" +} + +log() { + local level="$1" + shift + local message="$@" + local timestamp + timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo "[$timestamp] [$level] $message" >> "${ERROR_LOG_FILE:-/tmp/script-errors.log}" + + if [ "${DEBUG_MODE:-0}" = "1" ]; then + echo -e "${GRAY}[$timestamp] [$level] $message${NC}" >&2 + fi +} + +success() { + echo -e "${GREEN}${CHECKMARK} $*${NC}" + log "SUCCESS" "$*" +} + +info() { + echo -e "${BLUE}${INFO} $*${NC}" + log "INFO" "$*" +} + +warning() { + echo -e "${YELLOW}${WARNING} $*${NC}" + log "WARNING" "$*" +} + +error() { + echo -e "${RED}${CROSS} Error: $*${NC}" >&2 + log "ERROR" "$*" +} + +debug() { + if [ "${DEBUG_MODE:-0}" = "1" ]; then + echo -e "${GRAY}[DEBUG] $*${NC}" >&2 + fi + log "DEBUG" "$*" +} + +progress() { + echo -e "${CYAN}${ARROW} $*${NC}" +} diff --git a/bash/functions/shell/bash_safe_run.md b/bash/functions/shell/bash_safe_run.md new file mode 100644 index 00000000..6f644721 --- /dev/null +++ b/bash/functions/shell/bash_safe_run.md @@ -0,0 +1,52 @@ +--- +name: bash_safe_run +kind: function +lang: bash +domain: shell +version: "1.0.0" +purity: impure +signature: "safe_run(cmd: string, [error_code: string], [error_desc: string]) -> void; setup_error_trap() -> void; error_trap_handler(exit_code: int, line_number: int) -> void" +description: "Ejecuta comandos con manejo de errores integrado. Incluye trap handler que captura fallos con numero de linea y codigo de salida." +tags: [bash, safe, run, error, trap, handler] +uses_functions: [bash_log_bash_shell] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: cmd + desc: "comando a ejecutar via eval" + - name: error_code + desc: "codigo identificador del error en caso de fallo; default COMMAND_FAILED" + - name: error_desc + desc: "descripcion del error a mostrar; default 'El comando fallo: CMD'" +output: "exit code 0 si el comando tuvo exito; exit code 1 con mensaje de error formateado si fallo" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/shell/bash_safe_run.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/lib/common.sh" +--- + +## Ejemplo + +```bash +source bash/functions/shell/bash_safe_run.sh +bash_log_init +setup_error_trap + +safe_run "go build ./..." "BUILD_FAILED" "La compilacion fallo" +safe_run "docker compose up -d" "DOCKER_FAILED" "No se pudo iniciar Docker Compose" +safe_run "npm install" "NPM_FAILED" +``` + +## Notas + +`safe_run` usa `eval` internamente para ejecutar el comando, lo que permite pasar comandos con pipes y redirecciones como string. Usar con precaucion en entornos con input no confiable. + +`setup_error_trap` instala un trap `ERR` que llama a `error_trap_handler` automaticamente en cualquier comando fallido del script, mostrando numero de linea y codigo de salida. + +`error_trap_handler` no llama a `exit` — el caller decide si continuar o abortar. Muestra la ruta al log para debugging. diff --git a/bash/functions/shell/bash_safe_run.sh b/bash/functions/shell/bash_safe_run.sh new file mode 100644 index 00000000..ba08301d --- /dev/null +++ b/bash/functions/shell/bash_safe_run.sh @@ -0,0 +1,44 @@ +# bash_safe_run +# ------------- +# Ejecutar comandos con manejo de errores y trap. +# Incluye safe_run, setup_error_trap y error_trap_handler. +# +# USO (sourced): +# source bash_safe_run.sh +# setup_error_trap +# safe_run "go build ./..." "BUILD_FAILED" "La compilacion fallo" + +SCRIPT_DIR_BASH_SAFE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR_BASH_SAFE/bash_log.sh" +bash_log_init + +safe_run() { + local cmd="$1" + local error_code="${2:-COMMAND_FAILED}" + local error_desc="${3:-El comando fallo: $cmd}" + + debug "Ejecutando: $cmd" + + if ! eval "$cmd"; then + error "$error_desc ($error_code)" + return 1 + fi + + return 0 +} + +setup_error_trap() { + trap 'error_trap_handler $? $LINENO' ERR +} + +error_trap_handler() { + local exit_code=$1 + local line_number=$2 + + if [ "$exit_code" -ne 0 ]; then + echo "" + error "El script fallo en la linea $line_number con codigo de salida $exit_code" + echo -e "${GRAY}Consulta el log: ${ERROR_LOG_FILE:-/tmp/script-errors.log}${NC}" + echo "" + fi +} diff --git a/bash/functions/shell/convert_text_case.md b/bash/functions/shell/convert_text_case.md new file mode 100644 index 00000000..c55a21ab --- /dev/null +++ b/bash/functions/shell/convert_text_case.md @@ -0,0 +1,54 @@ +--- +name: convert_text_case +kind: function +lang: bash +domain: shell +version: "1.0.0" +purity: impure +signature: "convert_text_case(mode: string, file: string, [output_file: string]) -> void" +description: "Convierte el contenido de un archivo de texto. Modos: upper (todo mayúsculas), lower (todo minúsculas), lf (normaliza saltos de línea a LF eliminando \\r), crlf (normaliza saltos a CRLF). Sin output_file imprime a stdout." +tags: [bash, text, convert, case, encoding] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: mode + desc: "transformación a aplicar: upper|lower|lf|crlf (requerido)" + - name: file + desc: "ruta al archivo de entrada (requerido)" + - name: output_file + desc: "ruta al archivo de salida (opcional; default: stdout)" +output: "texto convertido a stdout o escrito en output_file; exit code 1 si el modo o archivo son inválidos" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/shell/convert_text_case.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/conversores/conversor_texto.sh" +--- + +## Ejemplo + +```bash +source bash/functions/shell/convert_text_case.sh + +# Convertir a mayúsculas y mostrar en stdout +convert_text_case upper mi_archivo.txt + +# Convertir a minúsculas y guardar en archivo +convert_text_case lower input.txt output_lower.txt + +# Normalizar saltos de línea CRLF a LF +convert_text_case lf archivo_windows.txt archivo_unix.txt + +# Añadir CRLF (para Windows) +convert_text_case crlf unix.txt windows.txt +``` + +## Notas + +Usa `awk` para las conversiones de case (portable) y `sed` para los saltos de línea. La función no modifica el archivo original si se provee `output_file`. Sin `output_file` imprime directamente a stdout, útil para pipes. diff --git a/bash/functions/shell/convert_text_case.sh b/bash/functions/shell/convert_text_case.sh new file mode 100644 index 00000000..7b35a7cd --- /dev/null +++ b/bash/functions/shell/convert_text_case.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# convert_text_case +# ----------------- +# Convierte el contenido de texto de un archivo aplicando transformaciones: +# upper (mayúsculas), lower (minúsculas), lf (normalizar saltos a LF), +# crlf (normalizar saltos a CRLF). +# +# USO: +# source convert_text_case.sh +# convert_text_case mode file [output_file] +# +# ARGUMENTOS: +# mode Transformación a aplicar: upper|lower|lf|crlf (requerido) +# file Archivo de entrada (requerido) +# output_file Archivo de salida (opcional; por defecto imprime a stdout) + +convert_text_case() { + local mode="${1:-}" + local input_file="${2:-}" + local output_file="${3:-}" + + # Validar argumentos requeridos + if [[ -z "$mode" ]]; then + echo "convert_text_case: modo requerido (upper|lower|lf|crlf)" >&2 + return 1 + fi + + if [[ -z "$input_file" ]]; then + echo "convert_text_case: archivo de entrada requerido" >&2 + return 1 + fi + + if [[ ! -f "$input_file" ]]; then + echo "convert_text_case: archivo no encontrado: ${input_file}" >&2 + return 1 + fi + + # Función auxiliar que aplica la conversión y escribe a stdout o archivo + _apply_conversion() { + local src="$1" + local op="$2" + case "$op" in + upper) + awk '{ print toupper($0) }' "$src" + ;; + lower) + awk '{ print tolower($0) }' "$src" + ;; + lf) + sed 's/\r$//' "$src" + ;; + crlf) + sed 's/\r$//' "$src" | sed 's/$/\r/' + ;; + *) + echo "convert_text_case: modo no soportado: ${op}. Usa: upper|lower|lf|crlf" >&2 + return 1 + ;; + esac + } + + if [[ -n "$output_file" ]]; then + _apply_conversion "$input_file" "$mode" > "$output_file" + echo "convert_text_case: ${input_file} → ${output_file} (modo: ${mode})" + else + _apply_conversion "$input_file" "$mode" + fi +} + +# Ejecutar si se invoca directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + convert_text_case "$@" +fi diff --git a/bash/functions/shell/create_project_structure.md b/bash/functions/shell/create_project_structure.md new file mode 100644 index 00000000..e1e3a617 --- /dev/null +++ b/bash/functions/shell/create_project_structure.md @@ -0,0 +1,44 @@ +--- +name: create_project_structure +kind: function +lang: bash +domain: shell +version: "1.0.0" +purity: impure +signature: "create_project_structure(project_name: string) -> void" +description: "Crea la estructura de directorios estándar de un proyecto funcional en el directorio indicado: database (attachments, data, models), dist (desktop/mobile/docker), docker, docs, frontend, logs, notebooks, robots, scripts, src (application/core/middleware con tests y tipos)." +tags: [bash, project, structure, scaffold, init] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: project_name + desc: "nombre del proyecto o ruta destino donde crear la estructura (requerido)" +output: "crea los directorios a stdout con conteo final; exit code 1 si no se proporciona nombre" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/shell/create_project_structure.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/inicializar_repos/functional_structure.sh" +--- + +## Ejemplo + +```bash +source bash/functions/shell/create_project_structure.sh + +# Crear estructura en un directorio nuevo +create_project_structure mi-proyecto + +# Usar una ruta +create_project_structure /home/user/projects/nuevo-proyecto +``` + +## Notas + +Crea 68 directorios organizados en la estructura funcional de DevLauncher. La estructura separa claramente application (orquestación), core (lógica pura) y middleware (efectos/I/O) bajo `src/`. No crea archivos, solo directorios. Idempotente: si los directorios ya existen, no falla. diff --git a/bash/functions/shell/create_project_structure.sh b/bash/functions/shell/create_project_structure.sh new file mode 100644 index 00000000..3f9b644c --- /dev/null +++ b/bash/functions/shell/create_project_structure.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +# create_project_structure +# ------------------------ +# Crea la estructura de directorios estándar de un proyecto funcional +# (database, dist, docker, docs, frontend, logs, notebooks, robots, +# scripts, src con application/core/middleware) en el directorio indicado. +# +# USO: +# source create_project_structure.sh +# create_project_structure project_name +# +# ARGUMENTOS: +# project_name Nombre del proyecto / ruta destino (requerido) + +create_project_structure() { + local project_name="${1:-}" + + if [[ -z "$project_name" ]]; then + echo "create_project_structure: se requiere el nombre del proyecto" >&2 + echo " Uso: create_project_structure " >&2 + return 1 + fi + + local destino="$project_name" + + local directories=( + "database" + "database/attachments" + "database/attachments/audio" + "database/attachments/docs" + "database/attachments/images" + "database/attachments/video" + "database/data" + "database/data/01_raw" + "database/data/02_clean" + "database/data/03_objects" + "database/data/04_answers" + "database/models" + "dist" + "dist/desktop" + "dist/desktop/linux" + "dist/desktop/mac" + "dist/desktop/win" + "dist/docker" + "dist/mobile" + "dist/mobile/android" + "dist/mobile/ios" + "docker" + "docs" + "docs/documentation" + "docs/pdf" + "frontend" + "logs" + "notebooks" + "robots" + "scripts" + "src" + "src/application" + "src/application/event" + "src/application/jobs" + "src/application/pipes" + "src/application/sandbox" + "src/application/simulation" + "src/application/tasks" + "src/application/tests" + "src/application/tests/benchmark" + "src/application/tests/fuzzers" + "src/application/tests/mocks" + "src/application/tests/mutation" + "src/core" + "src/core/base" + "src/core/functions" + "src/core/rules" + "src/core/tests" + "src/core/tests/benchmark" + "src/core/tests/fuzzers" + "src/core/tests/mocks" + "src/core/tests/mutation" + "src/core/types" + "src/core/utils" + "src/middleware" + "src/middleware/api" + "src/middleware/backend" + "src/middleware/browser" + "src/middleware/config" + "src/middleware/connection" + "src/middleware/controller" + "src/middleware/i18n" + "src/middleware/interfaces" + "src/middleware/queue" + "src/middleware/servers" + "src/middleware/tests" + "src/middleware/tests/benchmark" + "src/middleware/tests/fuzzers" + "src/middleware/tests/mocks" + "src/middleware/tests/mutation" + "src/middleware/types" + "src/modules" + ) + + echo "Creando estructura del proyecto: ${destino}" + + for dir in "${directories[@]}"; do + mkdir -p "${destino%/}/${dir}" + done + + echo "Estructura creada en: ${destino%/}" + echo " ${#directories[@]} directorios creados." +} + +# Ejecutar si se invoca directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + create_project_structure "$@" +fi diff --git a/bash/functions/shell/git_clean_branches.md b/bash/functions/shell/git_clean_branches.md new file mode 100644 index 00000000..096f54f4 --- /dev/null +++ b/bash/functions/shell/git_clean_branches.md @@ -0,0 +1,51 @@ +--- +name: git_clean_branches +kind: function +lang: bash +domain: shell +version: "1.0.0" +purity: impure +signature: "git_clean_branches([base_branch: string], [--remote], [--force]) -> void" +description: "Elimina ramas locales ya mergeadas en la rama base (autodetecta main/master). Con --remote también borra las ramas en todos los remotes. Con --force omite la confirmación interactiva." +tags: [bash, git, branches, clean, merge] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: base_branch + desc: "rama base para detectar merges (opcional; autodetecta main o master)" + - name: --remote + desc: "flag para también eliminar ramas en todos los remotes" + - name: --force + desc: "flag para omitir confirmación interactiva" +output: "lista de ramas eliminadas a stdout; exit code 1 si no es repo Git o no se detecta la rama base" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/shell/git_clean_branches.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/git_utils/limpiar_ramas.sh" +--- + +## Ejemplo + +```bash +source bash/functions/shell/git_clean_branches.sh + +# Limpieza básica con confirmación +git_clean_branches + +# Sin confirmación, sobre rama base "develop" +git_clean_branches develop --force + +# Eliminar también en remotes +git_clean_branches main --remote --force +``` + +## Notas + +Ramas protegidas que nunca se eliminan: `main`, `master`, `develop`, `dev`, `staging`, `release`. Siempre hace prune de referencias remotas obsoletas antes de buscar candidatos. diff --git a/bash/functions/shell/git_clean_branches.sh b/bash/functions/shell/git_clean_branches.sh new file mode 100644 index 00000000..8f9a37f1 --- /dev/null +++ b/bash/functions/shell/git_clean_branches.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# git_clean_branches +# ------------------ +# Elimina ramas locales ya mergeadas en la rama base (main/master por defecto). +# Opcionalmente también elimina las ramas en remotes. +# +# USO: +# source git_clean_branches.sh +# git_clean_branches [base_branch] [--remote] [--force] +# +# ARGUMENTOS: +# base_branch Rama base (opcional; autodetecta main/master si se omite) +# --remote Elimina también las ramas en todos los remotes +# --force Omite confirmación interactiva + +git_clean_branches() { + local base_branch="" + local delete_remote=false + local force=false + + # Parsear argumentos + for arg in "$@"; do + case "$arg" in + --remote) delete_remote=true ;; + --force) force=true ;; + *) [[ -z "$base_branch" ]] && base_branch="$arg" ;; + esac + done + + # Validar repo + if ! git rev-parse --is-inside-work-tree &>/dev/null; then + echo "git_clean_branches: el directorio actual no es un repositorio Git" >&2 + return 1 + fi + + # Detectar rama base + if [[ -z "$base_branch" ]]; then + if git show-ref --verify --quiet refs/heads/main; then + base_branch="main" + elif git show-ref --verify --quiet refs/heads/master; then + base_branch="master" + else + echo "git_clean_branches: no se detectó main/master; pasa la rama base como argumento" >&2 + return 1 + fi + fi + + echo "git_clean_branches: rama base = ${base_branch}" + + # Hacer prune de referencias remotas obsoletas + echo "Haciendo prune de referencias remotas..." + git remote | while IFS= read -r remote; do + git remote prune "$remote" 2>/dev/null && echo " Pruned: ${remote}" + done + echo "" + + # Buscar ramas locales mergeadas + local candidates + candidates="$(git branch --merged "$base_branch" \ + | grep -v -E "^\*|^\s*(main|master|develop|dev|staging|release)$" \ + | sed 's/^[[:space:]]*//' \ + | grep -v "^$" || true)" + + if [[ -z "$candidates" ]]; then + echo "No hay ramas locales mergeadas para eliminar." + return 0 + fi + + echo "Ramas candidatas a eliminar:" + echo "$candidates" | while IFS= read -r b; do + echo " - ${b}" + done + echo "" + + # Confirmación (omitir si --force) + if [[ "$force" != true ]]; then + echo -n "¿Eliminar estas ramas locales? [y/N] " + read -r answer + [[ "$answer" != "y" && "$answer" != "Y" ]] && { echo "Operación cancelada."; return 0; } + fi + + # Eliminar ramas locales + local deleted=0 + local failed=0 + while IFS= read -r branch; do + if git branch -d "$branch"; then + echo " Eliminada local: ${branch}" + deleted=$((deleted + 1)) + else + echo " No se pudo eliminar: ${branch}" >&2 + failed=$((failed + 1)) + fi + done <<< "$candidates" + + echo "" + echo "Resumen: ${deleted} eliminada(s), ${failed} fallida(s)" + + # Eliminar en remotes si se solicitó + if [[ "$delete_remote" == true ]]; then + local remotes + remotes="$(git remote 2>/dev/null || true)" + if [[ -n "$remotes" ]]; then + echo "" + echo "Eliminando ramas en remotes..." + while IFS= read -r branch; do + while IFS= read -r remote; do + if git ls-remote --heads "$remote" "$branch" 2>/dev/null | grep -q "$branch"; then + if git push "$remote" --delete "$branch"; then + echo " Eliminada remota: ${remote}/${branch}" + else + echo " No se pudo eliminar remota: ${remote}/${branch}" >&2 + fi + fi + done <<< "$remotes" + done <<< "$candidates" + fi + fi + + echo "" + echo "Limpieza completada." +} + +# Ejecutar si se invoca directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + git_clean_branches "$@" +fi diff --git a/bash/functions/shell/git_log_visual.md b/bash/functions/shell/git_log_visual.md new file mode 100644 index 00000000..cc6744db --- /dev/null +++ b/bash/functions/shell/git_log_visual.md @@ -0,0 +1,56 @@ +--- +name: git_log_visual +kind: function +lang: bash +domain: shell +version: "1.0.0" +purity: impure +signature: "git_log_visual([--count N: int], [--author X: string], [--since D: string], [--all]) -> void" +description: "Muestra el historial de commits Git con grafo visual, colores y formato legible (hash, fecha, autor, mensaje, ramas). Soporta filtros por cantidad, autor, fecha y modo todas-las-ramas. Pagina con less." +tags: [bash, git, log, history, graph] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: --count + desc: "número de commits a mostrar (default: 20)" + - name: --author + desc: "filtrar por nombre o email del autor (parcial, igual que git --author)" + - name: --since + desc: "mostrar solo commits desde esta fecha (ej: '1 week ago', '2025-01-01')" + - name: --all + desc: "incluir commits de todas las ramas, no solo la actual" +output: "historial de commits paginado con less -R; exit code 1 si no es un repo Git" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/shell/git_log_visual.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/git_utils/historial_commits.sh" +--- + +## Ejemplo + +```bash +source bash/functions/shell/git_log_visual.sh + +# Últimos 20 commits (default) +git_log_visual + +# Últimos 50 commits de todas las ramas +git_log_visual --count 50 --all + +# Commits de un autor esta semana +git_log_visual --author "lucas" --since "1 week ago" + +# Commits de hoy en todas las ramas +git_log_visual --since "00:00:00" --all --count 100 +``` + +## Notas + +Usa `less -R --quit-if-one-screen` para paginar: si el log cabe en pantalla, no abre el paginador. El formato incluye hash corto (amarillo), fecha (cyan), autor (verde), mensaje y refs de ramas (rojo). diff --git a/bash/functions/shell/git_log_visual.sh b/bash/functions/shell/git_log_visual.sh new file mode 100644 index 00000000..d3ce5319 --- /dev/null +++ b/bash/functions/shell/git_log_visual.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# git_log_visual +# -------------- +# Muestra el historial de commits con grafo visual, colores y formato legible. +# Soporta filtros por cantidad, autor, fecha y todas las ramas. +# +# USO: +# source git_log_visual.sh +# git_log_visual [--count N] [--author X] [--since D] [--all] +# +# ARGUMENTOS: +# --count N Número de commits a mostrar (default: 20) +# --author X Filtrar por nombre o email del autor +# --since D Mostrar commits desde fecha (ej: "1 week ago", "2025-01-01") +# --all Incluir todas las ramas + +git_log_visual() { + local count=20 + local author="" + local since="" + local all_branches=false + + # Parsear argumentos + while [[ $# -gt 0 ]]; do + case "$1" in + --count|-n) + count="$2" + shift 2 + ;; + --author|-a) + author="$2" + shift 2 + ;; + --since|-s) + since="$2" + shift 2 + ;; + --all) + all_branches=true + shift + ;; + *) + shift + ;; + esac + done + + # Validar repo + if ! git rev-parse --is-inside-work-tree &>/dev/null; then + echo "git_log_visual: el directorio actual no es un repositorio Git" >&2 + return 1 + fi + + local log_format="%C(yellow)%h%C(reset) %C(cyan)%ad%C(reset) %C(green)%an%C(reset) %s%C(red)%d%C(reset)" + + # Construir argumentos del comando + local args=() + args+=("--graph") + args+=("--color=always") + args+=("--date=short") + args+=("--format=${log_format}") + args+=("-n" "${count}") + + [[ -n "$author" ]] && args+=("--author=${author}") + [[ -n "$since" ]] && args+=("--since=${since}") + [[ "$all_branches" == true ]] && args+=("--all") + + # Ejecutar + git log "${args[@]}" 2>/dev/null | less -R --quit-if-one-screen +} + +# Ejecutar si se invoca directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + git_log_visual "$@" +fi diff --git a/bash/functions/shell/git_push_all_remotes.md b/bash/functions/shell/git_push_all_remotes.md new file mode 100644 index 00000000..ee7ccbb6 --- /dev/null +++ b/bash/functions/shell/git_push_all_remotes.md @@ -0,0 +1,44 @@ +--- +name: git_push_all_remotes +kind: function +lang: bash +domain: shell +version: "1.0.0" +purity: impure +signature: "git_push_all_remotes([--message msg: string]) -> void" +description: "Hace commit de todos los cambios pendientes (git add -A + git commit) y pushea la rama actual a todos los remotes configurados. Si no hay cambios solo pushea. Requiere --message si hay cambios sin commitear." +tags: [bash, git, push, remote, all] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: --message + desc: "mensaje de commit a usar si hay cambios pendientes (requerido cuando hay cambios)" +output: "progreso del push a stdout por cada remote; exit code 1 si algún push falla" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/shell/git_push_all_remotes.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/git_utils/push_todos_remotes.sh" +--- + +## Ejemplo + +```bash +source bash/functions/shell/git_push_all_remotes.sh + +# Solo push (sin cambios pendientes) +git_push_all_remotes + +# Commit + push a todos los remotes +git_push_all_remotes --message "feat: nueva funcionalidad" +``` + +## Notas + +Usa `--set-upstream` automáticamente si la rama no existe en el remote. Sale con exit code 1 si hay cambios pero no se pasa `--message`, o si algún push falla. diff --git a/bash/functions/shell/git_push_all_remotes.sh b/bash/functions/shell/git_push_all_remotes.sh new file mode 100644 index 00000000..acbefe66 --- /dev/null +++ b/bash/functions/shell/git_push_all_remotes.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +# git_push_all_remotes +# -------------------- +# Hace commit de todos los cambios pendientes (si los hay) y pushea +# a todos los remotes configurados en el repositorio. +# +# USO: +# source git_push_all_remotes.sh +# git_push_all_remotes [--message "mensaje"] +# +# ARGUMENTOS: +# --message "msg" Mensaje de commit (si hay cambios). Si se omite y hay +# cambios, sale con error. + +git_push_all_remotes() { + local commit_msg="" + + # Parsear argumentos + while [[ $# -gt 0 ]]; do + case "$1" in + --message|-m) + commit_msg="$2" + shift 2 + ;; + *) + shift + ;; + esac + done + + # Validar repo + if ! git rev-parse --is-inside-work-tree &>/dev/null; then + echo "git_push_all_remotes: el directorio actual no es un repositorio Git" >&2 + return 1 + fi + + local branch + branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)" + + local remotes + remotes="$(git remote 2>/dev/null || true)" + + if [[ -z "$remotes" ]]; then + echo "git_push_all_remotes: no hay remotes configurados en este repositorio" >&2 + return 1 + fi + + echo "Rama actual: ${branch}" + echo "Remotes:" + echo "$remotes" | while IFS= read -r r; do + local url + url="$(git remote get-url "$r" 2>/dev/null || echo "?")" + echo " ${r} → ${url}" + done + echo "" + + # Gestión del commit si hay cambios + local has_changes=false + if [[ -n "$(git status --porcelain 2>/dev/null)" ]]; then + has_changes=true + fi + + if [[ "$has_changes" == true ]]; then + if [[ -z "$commit_msg" ]]; then + echo "git_push_all_remotes: hay cambios pero no se proporcionó --message" >&2 + echo " Usa: git_push_all_remotes --message \"tu mensaje\"" >&2 + return 1 + fi + + echo "Cambios detectados:" + git status --short | while IFS= read -r line; do + echo " ${line}" + done + echo "" + + echo "Añadiendo todos los cambios..." + git add -A + + echo "Haciendo commit: \"${commit_msg}\"" + git commit -m "$commit_msg" + echo "" + else + echo "Directorio de trabajo limpio. Se empujarán commits existentes." + echo "" + fi + + # Push a cada remote + local ok_count=0 + local fail_count=0 + local failed_remotes=() + local remote_count + remote_count="$(echo "$remotes" | wc -l | tr -d ' ')" + + while IFS= read -r remote; do + # Verificar si la rama existe en el remote + local push_flags="" + if ! git ls-remote --heads "$remote" "$branch" 2>/dev/null | grep -q "$branch"; then + push_flags="--set-upstream" + echo "La rama '${branch}' no existe en '${remote}', se creará" + fi + + echo "Push → ${remote} (${branch})..." + # shellcheck disable=SC2086 + if git push $push_flags "$remote" "$branch" 2>&1; then + echo " Push completado → ${remote}/${branch}" + ok_count=$((ok_count + 1)) + else + echo " Falló el push a ${remote}/${branch}" >&2 + fail_count=$((fail_count + 1)) + failed_remotes+=("$remote") + fi + echo "" + done <<< "$remotes" + + echo "=== Resumen ===" + echo "Push completado en ${ok_count}/${remote_count} remote(s)" + + if [[ $fail_count -gt 0 ]]; then + echo "Fallaron ${fail_count} remote(s): ${failed_remotes[*]}" >&2 + return 1 + fi +} + +# Ejecutar si se invoca directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + git_push_all_remotes "$@" +fi diff --git a/bash/functions/shell/git_repo_status.md b/bash/functions/shell/git_repo_status.md new file mode 100644 index 00000000..64d59f1b --- /dev/null +++ b/bash/functions/shell/git_repo_status.md @@ -0,0 +1,41 @@ +--- +name: git_repo_status +kind: function +lang: bash +domain: shell +version: "1.0.0" +purity: impure +signature: "git_repo_status() -> void" +description: "Muestra el estado completo de un repositorio Git: rama actual, upstream (ahead/behind), cambios pendientes, stash, remotes y últimos 8 commits. Sale con exit code 1 si el directorio actual no es un repo Git." +tags: [bash, git, status, repo, branch] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: "(ninguno)" + desc: "opera sobre el directorio de trabajo actual (cwd)" +output: "imprime el estado completo del repo a stdout; exit code 1 si no es un repo Git" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/shell/git_repo_status.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/git_utils/estado_repo.sh" +--- + +## Ejemplo + +```bash +# Desde un directorio con repo git +cd /home/user/my-project +source bash/functions/shell/git_repo_status.sh +git_repo_status +``` + +## Notas + +No requiere dependencias externas más allá de git. Los colores del log de commits usan `--color=always` de git directamente. No produce output en stdout en caso de error — los mensajes de error van a stderr. diff --git a/bash/functions/shell/git_repo_status.sh b/bash/functions/shell/git_repo_status.sh new file mode 100644 index 00000000..62319eb4 --- /dev/null +++ b/bash/functions/shell/git_repo_status.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +# git_repo_status +# --------------- +# Muestra el estado completo de un repositorio Git: rama actual, upstream, +# cambios pendientes, stash, remotes y últimos commits. +# Sale con exit code 1 si el directorio actual no es un repositorio Git. +# +# USO: +# source git_repo_status.sh +# git_repo_status +# +# O como script directo: +# bash git_repo_status.sh + +git_repo_status() { + if ! git rev-parse --is-inside-work-tree &>/dev/null; then + echo "git_repo_status: el directorio actual no es un repositorio Git" >&2 + return 1 + fi + + local branch + branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)" + + # Upstream + local upstream + upstream="$(git rev-parse --abbrev-ref "${branch}@{upstream}" 2>/dev/null || echo "")" + local upstream_info + if [[ -z "$upstream" ]]; then + upstream_info="sin upstream" + else + local ahead behind + ahead="$(git rev-list "${upstream}..HEAD" --count 2>/dev/null || echo 0)" + behind="$(git rev-list "HEAD..${upstream}" --count 2>/dev/null || echo 0)" + upstream_info="↑${ahead} ↓${behind} (${upstream})" + fi + + echo "=== Rama & Upstream ===" + echo " Rama actual: ${branch}" + echo " Upstream: ${upstream_info}" + echo "" + + # Cambios + echo "=== Cambios ===" + local changes + changes="$(git status --short 2>/dev/null)" + if [[ -z "$changes" ]]; then + echo " Directorio de trabajo limpio" + else + echo " Cambios pendientes:" + echo "$changes" | while IFS= read -r line; do + echo " ${line}" + done + fi + echo "" + + # Stash + echo "=== Stash ===" + local stash_count + stash_count="$(git stash list 2>/dev/null | wc -l | tr -d ' ')" + if [[ "$stash_count" -eq 0 ]]; then + echo " Sin entradas en stash" + else + echo " ${stash_count} entrada(s) en stash:" + git stash list | head -5 | while IFS= read -r line; do + echo " ${line}" + done + fi + echo "" + + # Remotes + echo "=== Remotes ===" + local remotes + remotes="$(git remote -v 2>/dev/null | grep '(fetch)' || true)" + if [[ -z "$remotes" ]]; then + echo " Sin remotes configurados" + else + echo "$remotes" | while IFS= read -r line; do + local name url + name="$(echo "$line" | awk '{print $1}')" + url="$(echo "$line" | awk '{print $2}')" + echo " ${name} → ${url}" + done + fi + echo "" + + # Últimos commits + echo "=== Últimos commits ===" + git log --oneline --decorate --color=always -8 2>/dev/null || true + echo "" +} + +# Ejecutar si se invoca directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + git_repo_status "$@" +fi