diff --git a/bash/functions/infra/ensure_repo_synced.md b/bash/functions/infra/ensure_repo_synced.md new file mode 100644 index 00000000..a60b00f0 --- /dev/null +++ b/bash/functions/infra/ensure_repo_synced.md @@ -0,0 +1,60 @@ +--- +name: ensure_repo_synced +kind: pipeline +lang: bash +domain: infra +version: "1.0.0" +purity: impure +signature: "ensure_repo_synced(directory: string, owner?: string, repo_name?: string, branch?: string, commit_msg?: string) -> void" +description: "Garantiza que un directorio tenga repo Gitea y este sincronizado. Crea el repo remoto si no existe, inicializa .git si falta, commitea cambios pendientes y pushea a origin. Idempotente." +tags: [gitea, git, sync, repo, pipeline, infra] +uses_functions: + - gitea_create_repo_bash_infra + - gitea_push_directory_bash_infra +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: directory + desc: "ruta del directorio local que se quiere sincronizar" + - name: owner + desc: "owner Gitea (org o user); default 'dataforge'" + - name: repo_name + desc: "nombre del repo Gitea; default basename del directorio" + - name: branch + desc: "rama destino del push; default 'master'" + - name: commit_msg + desc: "mensaje de commit usado si hay cambios pendientes; default 'chore: sync from fn_registry'" +output: "vacio — efectos en Gitea: repo creado (si no existia) y rama push-eada con los contenidos del directorio" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/infra/ensure_repo_synced.sh" +--- + +# ensure_repo_synced + +Pipeline bash que compone `gitea_create_repo` + `gitea_push_directory` para garantizar que un directorio local este sincronizado con un repo Gitea, creando el repo si no existe. + +## Cuando usarlo + +Cuando se quiere sincronizar a Gitea un directorio que **podria** no tener todavia repo remoto creado, sin tener que comprobarlo manualmente. Idempotente: se puede llamar repetidamente sin romper nada. + +## Variables de entorno requeridas + +- `GITEA_URL` — base URL del Gitea (ej. `https://gitea.example.com`) +- `GITEA_TOKEN` — token de API con permisos de creacion de repos y push + +## Ejemplo + +```bash +source bash/functions/infra/ensure_repo_synced.sh + +# Sincronizar app local a dataforge/ +ensure_repo_synced apps/mi_app + +# Sincronizar con owner/repo custom y rama main +ensure_repo_synced apps/mi_app dataforge mi_app main "feat: initial sync" +``` diff --git a/bash/functions/infra/ensure_repo_synced.sh b/bash/functions/infra/ensure_repo_synced.sh new file mode 100755 index 00000000..b68458af --- /dev/null +++ b/bash/functions/infra/ensure_repo_synced.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +# ensure_repo_synced — Garantiza que un directorio tenga repo Gitea y este +# sincronizado: crea el repo remoto si no existe, inicializa .git si falta, +# commitea cambios pendientes y pushea a origin. +# +# Pipeline que compone gitea_create_repo + gitea_push_directory. +# +# Uso: +# ensure_repo_synced [owner] [repo_name] [branch] [commit_msg] +# +# Defaults: +# owner = dataforge +# repo_name = basename(directory) +# branch = master +# commit_msg = "chore: sync from fn_registry" + +ensure_repo_synced() { + local directory="$1" + local owner="${2:-dataforge}" + local repo_name="$3" + local branch="${4:-master}" + local commit_msg="${5:-chore: sync from fn_registry}" + + if [[ -z "$directory" ]]; then + echo "ensure_repo_synced: se requiere directory" >&2 + return 1 + fi + if [[ ! -d "$directory" ]]; then + echo "ensure_repo_synced: directorio '$directory' no existe" >&2 + return 1 + fi + if [[ -z "$repo_name" ]]; then + repo_name=$(basename "$(realpath "$directory")") + fi + + if [[ -z "${GITEA_URL:-}" || -z "${GITEA_TOKEN:-}" ]]; then + echo "ensure_repo_synced: GITEA_URL y GITEA_TOKEN deben estar seteados" >&2 + return 1 + fi + + local script_dir + script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + # shellcheck source=./gitea_create_repo.sh + source "$script_dir/gitea_create_repo.sh" + # shellcheck source=./gitea_push_directory.sh + source "$script_dir/gitea_push_directory.sh" + + echo "ensure_repo_synced: $directory → $owner/$repo_name [$branch]" >&2 + + # 1. Asegurar repo remoto en Gitea (idempotente — 409 si existe) + if ! gitea_create_repo "$owner" "$repo_name" "false" "Synced from fn_registry" >/dev/null; then + echo "ensure_repo_synced: fallo creando/comprobando repo remoto" >&2 + return 1 + fi + + # 2. Inicializar .git, commitear y pushear + # gitea_push_directory ya hace commit con su mensaje por defecto. Para + # respetar commit_msg custom, hacemos commit aqui antes si hay cambios. + if [[ -d "$directory/.git" ]]; then + local status + status=$(git -C "$directory" status --porcelain) + if [[ -n "$status" ]]; then + echo "ensure_repo_synced: commiteando cambios con mensaje: $commit_msg" >&2 + git -C "$directory" add -A + git -C "$directory" \ + -c user.email="agent@fn-registry" -c user.name="fn-registry agent" \ + commit -m "$commit_msg" + fi + fi + + if ! gitea_push_directory "$directory" "$owner" "$repo_name" "$branch"; then + echo "ensure_repo_synced: fallo en gitea_push_directory" >&2 + return 1 + fi + + echo "ensure_repo_synced: ok '$owner/$repo_name'" >&2 +} + +# Si se invoca como script (no source), ejecutar la funcion. +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + ensure_repo_synced "$@" +fi diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 84747b76..749aab33 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -100,6 +100,7 @@ add_library(fn_framework STATIC functions/core/tokens.cpp functions/core/icon_font.cpp functions/core/app_settings.cpp + functions/core/app_about.cpp functions/core/fps_overlay.cpp functions/core/panel_menu.cpp functions/core/layouts_menu.cpp diff --git a/cpp/framework/app_base.cpp b/cpp/framework/app_base.cpp index f1185999..94a28820 100644 --- a/cpp/framework/app_base.cpp +++ b/cpp/framework/app_base.cpp @@ -8,6 +8,7 @@ #include "core/tokens.h" #include "core/icon_font.h" #include "core/app_settings.h" +#include "core/app_about.h" #include "core/fps_overlay.h" #include @@ -131,6 +132,9 @@ int run_app(AppConfig config, std::function render_fn) { // Ventana de Settings (no-op si esta cerrada). fn_ui::settings_window_render(); + // Ventana About (no-op si esta cerrada). + fn_ui::about_window_render(); + // FPS overlay si esta activado en Settings. if (fn_ui::settings().show_fps) { fps_overlay(); diff --git a/cpp/functions/core/app_about.cpp b/cpp/functions/core/app_about.cpp new file mode 100644 index 00000000..35b58207 --- /dev/null +++ b/cpp/functions/core/app_about.cpp @@ -0,0 +1,68 @@ +#include "core/app_about.h" + +#include "imgui.h" + +#include + +namespace fn_ui { + +namespace { + +std::string g_project = "fn_registry app"; +std::string g_version = ""; +std::string g_description = ""; +bool g_open = false; + +} // namespace + +void about_window_set_info(const char* project, + const char* version, + const char* description) { + if (project) g_project = project; + if (version) g_version = version; + if (description) g_description = description; +} + +bool about_window_is_open() { return g_open; } +void about_window_set_open(bool v) { g_open = v; } +void about_window_toggle() { g_open = !g_open; } + +bool about_window_menu_item(const char* label) { + if (ImGui::MenuItem(label)) { + g_open = true; + return true; + } + return false; +} + +void about_window_render() { + if (!g_open) return; + + ImGui::SetNextWindowSize(ImVec2(420, 220), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("About", &g_open, ImGuiWindowFlags_NoCollapse)) { + ImGui::End(); + return; + } + + ImGui::TextUnformatted(g_project.c_str()); + + if (!g_version.empty()) { + ImGui::SameLine(); + ImGui::TextDisabled("v%s", g_version.c_str()); + } + + if (!g_description.empty()) { + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + ImGui::TextWrapped("%s", g_description.c_str()); + } + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::TextDisabled("fn_registry"); + + ImGui::End(); +} + +} // namespace fn_ui diff --git a/cpp/functions/core/app_about.h b/cpp/functions/core/app_about.h new file mode 100644 index 00000000..a82f564e --- /dev/null +++ b/cpp/functions/core/app_about.h @@ -0,0 +1,37 @@ +#pragma once + +// Ventana flotante "About" para apps del registry. Muestra nombre del proyecto, +// version y descripcion opcional. Se abre desde el MenuItem "About..." en el +// submenu "Settings" de la MainMenuBar (ver app_menubar). +// +// Lifecycle: +// - app llama about_window_set_info(name, version, [description]) en su init +// - app_menubar() incluye el MenuItem "About..." en el submenu "Settings" +// - end of frame: about_window_render() — invocado por fn::run_app +// +// Apps que NO usan fn::run_app deben llamar about_window_render() manualmente +// despues del render_fn. + +namespace fn_ui { + +// Setea la informacion mostrada en la ventana About. Llamar una vez en el init +// de la app, antes de fn::run_app. Si no se llama, la ventana muestra valores +// por defecto ("fn_registry app", sin version). +void about_window_set_info(const char* project, + const char* version, + const char* description = ""); + +// Estado abierto/cerrado de la ventana. +bool about_window_is_open(); +void about_window_set_open(bool v); +void about_window_toggle(); + +// MenuItem componible. Llamar dentro de un BeginMenu/BeginMainMenuBar exitoso. +// Click → abre la ventana. Returns true si el usuario clico. +bool about_window_menu_item(const char* label = "About..."); + +// Render de la ventana. No-op si is_open == false. Llamada por fn::run_app al +// final del frame, despues del render_fn de la app. +void about_window_render(); + +} // namespace fn_ui diff --git a/cpp/functions/core/app_about.md b/cpp/functions/core/app_about.md new file mode 100644 index 00000000..fb7b991f --- /dev/null +++ b/cpp/functions/core/app_about.md @@ -0,0 +1,60 @@ +--- +name: app_about +kind: function +lang: cpp +domain: core +version: "1.0.0" +purity: impure +signature: "void fn_ui::about_window_set_info(const char* project, const char* version, const char* description = \"\"); bool fn_ui::about_window_is_open(); void fn_ui::about_window_set_open(bool); void fn_ui::about_window_toggle(); bool fn_ui::about_window_menu_item(const char* label = \"About...\"); void fn_ui::about_window_render()" +description: "Ventana flotante About para apps del registry con nombre del proyecto, version y descripcion. Se abre desde el submenu Settings -> About... de la MainMenuBar. Render automatico via fn::run_app." +tags: [imgui, about, window, ui, version] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [imgui, string] +tested: false +tests: [] +test_file_path: "" +file_path: "cpp/functions/core/app_about.cpp" +framework: imgui +params: + - name: project + desc: "Nombre del proyecto/app mostrado como titulo en la ventana About" + - name: version + desc: "Cadena de version (ej. '0.2.0') mostrada al lado del nombre del proyecto" + - name: description + desc: "Descripcion opcional mostrada debajo del titulo. Vacio = no se muestra" + - name: label + desc: "Texto del MenuItem en la menubar (default 'About...')" +output: "about_window_set_info muta el estado global del modulo. about_window_render es no-op si la ventana esta cerrada. about_window_menu_item retorna true si el usuario clico" +--- + +# app_about + +Ventana flotante About para apps C++ del registry. Funciona en pareja con `app_settings`: ambas se exponen bajo el menu `Settings` de la MainMenuBar via `app_menubar`. + +## Uso + +```cpp +#include "core/app_about.h" + +int main() { + fn_ui::about_window_set_info( + "Mi App", + "1.2.3", + "Descripcion corta de la app." + ); + return fn::run_app({.title = "Mi App"}, render); +} +``` + +`fn::run_app` se encarga de invocar `about_window_render()` al final de cada frame. + +## Integracion con app_menubar + +`app_menubar` añade un menu `Settings` con dos items: + +- `Settings...` — abre la ventana de `app_settings` +- `About...` — abre la ventana de `app_about` diff --git a/cpp/functions/core/app_menubar.cpp b/cpp/functions/core/app_menubar.cpp index b853bb7e..9f324be6 100644 --- a/cpp/functions/core/app_menubar.cpp +++ b/cpp/functions/core/app_menubar.cpp @@ -19,9 +19,14 @@ bool app_menubar(const PanelToggle* panels, std::size_t count, changed |= layouts_menu_items("Layouts", *layouts_cb); } - // MenuItem "Settings..." — siempre. Abre la ventana flotante; el render - // ocurre al final del frame en fn::run_app. - changed |= settings_window_menu_item("Settings..."); + // Menu "Settings" — siempre. Submenus: Settings... y About... + // Las ventanas se renderizan al final del frame en fn::run_app. + if (ImGui::BeginMenu("Settings")) { + changed |= settings_window_menu_item("Settings..."); + ImGui::Separator(); + changed |= about_window_menu_item("About..."); + ImGui::EndMenu(); + } ImGui::EndMainMenuBar(); return changed; diff --git a/cpp/functions/core/app_menubar.h b/cpp/functions/core/app_menubar.h index daf0ae0c..f7d72b35 100644 --- a/cpp/functions/core/app_menubar.h +++ b/cpp/functions/core/app_menubar.h @@ -3,22 +3,25 @@ #include "core/panel_menu.h" #include "core/layouts_menu.h" #include "core/app_settings.h" +#include "core/app_about.h" namespace fn_ui { // Renderiza una MainMenuBar completa con: // * Menu "View" (panel_menu_items con los toggles dados) [si panels] // * Menu "Layouts" (layouts_menu_items con las callbacks dadas) [si layouts_cb] -// * MenuItem "Settings..." (abre la ventana de settings) [siempre] +// * Menu "Settings" con submenus: [siempre] +// - "Settings..." → abre la ventana de settings +// - "About..." → abre la ventana About // // Llamar despues de NewFrame() y antes del DockSpaceOverViewport. // Si layouts_cb es nullptr, omite Layouts. // Si panels es nullptr o count == 0, omite View. -// El item Settings siempre aparece — la ventana se renderiza al final del -// frame en fn::run_app via settings_window_render(). +// Las ventanas Settings y About se renderizan al final del frame en +// fn::run_app via settings_window_render() y about_window_render(). // // Returns: true si el usuario togglo paneles, disparo accion de layouts, -// o abrio la ventana de settings este frame. +// o abrio una ventana este frame. bool app_menubar(const PanelToggle* panels, std::size_t count, LayoutCallbacks* layouts_cb);