Blog

  • La rueda del ratón recorre el historial de comandos en lugar de desplazar la terminal

    Hace poco me volvió a pasar que después de algún evento que no consigo identificar bien, la rueda del raton en lugar de desplazar la vista de la terminal empieza a recorrer su historial de comandos.

    Algunas aplicaciones de terminal tienen la capacidad de restaurar el comportamiento anterior, pero este no era el caso así que busqué por Internet y descubrí un comando del que nunca antes había oído hablar; tput. En concreto la llamada que logra restaurar el desplazamiento de la terminal es el siguiente:

    tput rmcup

  • Automatizar el reemplazo de un método en tests

    Método para automatizar el reemplazo de un método en el código a comprobar.

    class BaseTestEnvironment(unittest.IsolatedAsyncioTestCase):
        def _patch_method(self):
            async def new_method(arg_1: Any, *args: Any):
                return "anything"
    
            patcher = patch.object(
                TheClass,
                "the_method",
                side_effect=new_method,
            )
            self._m_method = patcher.start()
            self.addCleanup(patcher.stop)

    Llamando desde cualquier test a self._patch_method se realizará el patch del método TheClass.the_method durante la ejecución del test.

  • Forzar como cadena el valor de una celda en Google Sheets API

    Cuando se edita manualmente el valor de una celda en Google Sheets, el formato se elige automáticamente según el contenido, si se introduce 1234 se tomará como un número, si se introduce 2024-07-08 como una fecha, asdfg como una cadena,… El problema viene cuando queremos introducir una cadena que parece otra cosa.

    Supongamos que tenemos una serie de identificadores hexadecimales, 01af38 es tratado como una cadena, pero 983193 se interpreta como un número.

    Si queremos que un valor sea una cadena en el interfaz de usuario de Google Sheets podemos preceder el valor del apóstrofe ':

    Esto funciona bien en el UI, pero al utilizar la API resulta que si pasamos '983193, lo interpreta como una cadena, por empezar por ' y le añade un ' más, quedando el resultado así:

    Esto es debido al tipo de entrada usado por defecto en la API, para que trate ese apóstrofe como si se estuviera tecleando en el UI de la celda hay que utilizar el tipo de entrada USER_ENTERED.

  • Utilizando el comando ‘read’

    El comando read permite leer la entrada de texto del usuario y guardarlo en una variable de entorno. Además, puede separar la entrada en campos definiendo en la variable IFS (Input Field Separator) el separador.

    IP=$(ip a show dev eth0 | awk '/inet / { print $2}')
    IFS=/ read ip mask <<< "$IP"
    IFS=. read ip1 ip2 ip3 ip4 <<< "$ip"
    echo ${ip1}.${ip2}.${ip3}.${ip4}/${mask}
  • Mi VSCode settings.json

    Este es el contenido (en evolución) de mi fichero de configuración de VSCode. Está únicamente orientado a Python, ya que es lo único que utilizo ahora mismo. No está aun completo, hay tareas (como la inserción automática de los import necesarios) con las que aun no me siento lo suficientemente cómodo en VSCode.

    {
    "debug.console.fontSize": 14,
    "diffEditor.codeLens": true,"editor.fontSize": 14,
    "editor.minimap.showSlider": "always",
    "editor.rulers": [88],
    "editor.wordWrap": "wordWrapColumn",
    "editor.wordWrapColumn": 88,
    "editor.wrappingIndent": "indent",
    
    "gitlens.currentLine.enabled": false,
    "gitlens.hovers.currentLine.over": "line",
    
    "remote.SSH.defaultExtensions": [
        "gitpod.gitpod-remote-ssh"
    ],
    "remote.SSH.configFile": "/var/folders/qn/whatever/T/gitpod_ssh_config-more-whatever",
    
    "python.analysis.autoImportCompletions": true,
    "python.analysis.completeFunctionParens": true,
    "python.analysis.diagnosticSeverityOverrides": {
        "reportMissingTypeStubs": "information"
    },
    "python.analysis.indexing": true,
    "python.analysis.typeCheckingMode": "strict",
    "python.analysis.useLibraryCodeForTypes": true,
    "python.diagnostics.sourceMapsEnabled": true,
    "python.envFile": "${workspaceFolder}/.env",
    "python.formatting.autopep8Args": [
        "\"--max-line-length\", \"89\""
    ],
    "python.languageServer": "Pylance",
    "python.linting.banditEnabled": true,
    "python.linting.mypyEnabled": true,
    "python.terminal.activateEnvInCurrentTerminal": true,
    "python.testing.pytestEnabled": true,
    "python.testing.unittestEnabled": true,
    "vsintellicode.python.completionsEnabled": true,
    
    "scm.inputFontSize": 14,
    "terminal.integrated.fontSize": 14,
    "vim.textwidth": 89,
    "workbench.fontAliasing": "antialiased",
    "workbench.tree.indent": 16,
    }

  • Recuperar la huella digital de una clave pública reconocida

    Cuando nos conectamos por primera vez a un servidor SSH, se nos muestra la huella digital de su clave pública para que tengamos la oportunidad de verificar que ese es el servidor al que realmente deseamos conectarnos y no otro que de alguna forma está impersonándolo se está haciendo pasar por aquél.

    Mi antiguo yo tenía la fea costumbre de dar por buena dicha huella digital, confiando en que la resolución DNS, el enrutamiento y otras cuantas cosas no estuvieran comprometidos. Hasta hoy, hoy me he hecho una simple pregunta, ¿cómo verifico que esa huella es la del servidor que ya conozco y tengo reconocido en otra cuenta? Cómo no, la respuesta estaba en StackExchange – superuser.

    % ssh-keygen -l -f ~/.ssh/known_hosts
    2048 c2:e7:c0:9f:cd:c8:54:88:ac:b3:6b:a6:51:73:2b:e3 mach1,192.168.1.3 (RSA) 2048 a2:5e:8c:4e:2e:be:be:eb:23:12:5e:fe:6c:4b:23:dd mach2,192.168.1.1 (RSA) 1024 ae:5f:bc:e3:33:c3:dd:45:1e:18:1a:46:d1:d6:d2:39 mach3,192.168.1.6 (RSA) ... ...

    Y para nuestro servidor en concreto pues:

    % ssh-keygen -l -f ~/.ssh/known_hosts -F mach1
    2048 c2:e7:c0:9f:cd:c8:54:88:ac:b3:6b:a6:51:73:2b:e3 mach1 (RSA)
    
  • Uso de la salida de un comando como contenido de un fichero

    En ocasiones es conveniente utilizar la salida de un comando como si fuera el contenido de un fichero que se quiere pasar a otro comando como argumento.

    El caso más común con el que yo me encuentro es comparar la salida de dos comandos. Por ejemplo comparar las diferencias entre revisiones de dos ficheros.

    git diff mainline -- file1
    git diff mainline -- file2

    Puesto que diff espera como argumentos nombres de ficheros la solución más inmediata sería enviar la salida de cada comando a un fichero distinto y comparar ambos ficheros. Sin embargo, esto no queda muy bien que digamos.

    Afortunadamente los desarrolladores de Bash y Zsh (entre otros intérpretes de comandos) tampoco estaban contentos con esa chapuza, así que implementaron la capacidad llamada Sustitución de Proceso.

    La sustitución de procesos permite que la salida (o la entrada) sea referida como un nombre de fichero. Así, la ejecución de diff sobre la salida de los dos comandos anteriores se expresaría de la siguiente forma:

    diff <(git diff mainline -- file1) <(git diff mainline -- file2)

    Si lo que se quiere es utilizar la entrada de un comando como fichero se expresaría del siguiente modo:

    date > >(cat)

    Sí, ya sé que es un ejemplo un poco cutre, pero se entiende.

  • Configuración de tmux

    Contenido de mi fichero ~/.tmux.conf:

    set-window-option -g mouse on
    set -g terminal-overrides 'xterm*:smcup@:rmcup@'

    Eso sirve para desplazar el buffer de la terminal con la rueda del ratón, en lugar de cambiar la entrada de texto desde la historia de comandos.

  • Mi .gitconfig

    Este es el contenido de mi fichero .gitconfig:
    [color]
            ui = auto
    [core]
            pager = less -FMRiX
            excludesfile = /home/<myuser>/.config/git/excludes
    [push]
            default = simple
    [alias]
            hist = log --pretty=format:'%C(yellow)[%ad]%C(reset) %C(green)[%h]%C(reset) | %C(cyan)%s %C(bold red){{%an}}%C(reset) %C(magenta)%d%C(reset)' --graph --date=relative
            head = hist -10 --topo-order
            co = checkout
            wd = diff --color-words --word-diff-regex=[^[:space:],.\\:\\'\\\"\\+\\=\\/]+
            fork = !zsh -c '"orig_helper=${2:+${1}}; orig=${orig_helper:-main}; target=${2:-${1:-HEAD}}; diff -U 1 <(git rev-list --first-parent ${orig}) <(git rev-list --first-parent ${target}) | tail -1"' -
            showfork = !zsh -c 'git show $(git fork ${1} ${2})' -
            difffork = !zsh -c '"target=${2:-${1:-HEAD}}; git diff $(git fork ${1} ${2}) ${target}"' -
    [user]
            email = my@email.com
            name = My name
    [filter "lfs"]
    	clean = git-lfs clean -- %f
    	smudge = git-lfs smudge -- %f
    	process = git-lfs filter-process
    	required = true

    El fichero /home/<myuser>/.config/git/excludes equivale a un .gitignore aplicable a todos los repositorios.

    El alias fork devuelve el hash del commit en el que diverge la rama actual de main si no se proporciona ninguna rama. Si se proporciona una rama devuelve el hash del commit en el que diverge la rama proporcionada de main. Y si se proporcionan dos ramas devuelve el hash del commit en el que la segunda rama diverge de la primera rama proporcionada. Ejemplos:

          E---F---G rama HEAD
         /
    A---B---C---D main
             \
              H---I otra_rama

    Estando HEAD en el commit G, fork master devuelve el hash del commit B, es decir, el commit en el que la rama actual diverge de la rama indicada.

    $ git fork
    <hash del commit B>
    $ git fork otra_rama
    <hash del commit C>
    $ git fork rama otra_rama
    <hash del commit B>

    El alias showfork hace un show del commit en el que divergen las ramas indicadas. Y el alias difffork hace un diff entre el commit en el que divergen las ramas indicadas y el commit referido por la última de las ramas indicadas.

  • Indicador (prompt) para el intérprete de comandos Zsh

    Configuración del prompt Zsh para mostrar ficheros con cambios o no registrados en los directorios parte de un repositorio git.

    Despues del exito de Indicadores (prompt) para el intérprete de comandos Bash llega la version para Zsh.

    Definición en ~/.zshrc:

     # Git support for prompt
    autoload -Uz vcs_info
    precmd_vcs_info() { vcs_info }
    precmd_functions+=( precmd_vcs_info )
    setopt prompt_subst

    export PROMPT="
    %{$fg[white]%}(%D{%Y-%m-%d} %*) <%(?.%?.%F{red}%?%f)> [%~] $program %{$fg[default]%}\$vcs_info_msg_0_
    %{$fg[cyan]%}%m %#%{$fg[default]%} "

    Resultado:

    (2019-12-03 13:59:19) <130> [~/git/repositorio] (git)-[RamaGit]-
    MyHostname %

    Aunque el prompt no esta mal se nota que el presupuesto no es el mismo que el de la primera version, de ahi que el resultado no se vea igual de bien que en la version para Bash.

    Ya se sabe, segundas partes nunca fueron buenas.

    Finalmente se encontró el presupuesto para mejorar los efectos especiales y este es el remake:

    export PATH=$HOME/bin:$PATH

    export CLICOLOR=1

    # Git support for prompt
    # zstyle ':vcs_info:git*' formats "%{$fg[grey]%}%s %{$reset_color%}%r/%S%{$fg[grey]%} %{$fg[blue]%}%b%{$reset_color%}%m%u%c%{$reset_color%} "
    zstyle ':vcs_info:*' enable git
    zstyle ':vcs_info:*' check-for-changes true
    zstyle ':vcs_info:*' unstagedstr '!'
    zstyle ':vcs_info:*' stagedstr '+'
    zstyle ':vcs_info:git*' formats "%F{blue}%r/%S%} %F{yellow}%b%f%m%F{red}%u%f%F{green}%c%f"
    autoload -Uz vcs_info
    precmd_vcs_info() { vcs_info }
    precmd_functions+=( precmd_vcs_info )
    setopt prompt_subst

    export PROMPT="
    %F{green}%D{%Y-%m-%d} %D{%H:%M:%S}%f [%F{magenta}%~%f] \$vcs_info_msg_0_
    %F{cyan]%}%n%f@%F{blue}%m%f%} %(?..<%K{red}%F{yellow}%?%f%k> )%# "

    export RPROMPT=

    Y este el resultado (aprox):

    2022-08-31 12:32:21 [~/workspaces/proyecto] repositorio_git/. RamaGit
    user@nombre_equipo % error
    zsh: command not found: error

    2022-08-31 12:32:24 [~/workspaces/proyecto] repositorio_git/. RamaGit
    user@nombre_equipo <127> %

    Por último, además de mostrar la existencia de cambios presentados o no (traducción libre de staged) a git, añado un bloque para mostrar también la presencia de ficheros no registrados por git (untracked).

    export CLICOLOR=1

    # Git support for prompt
    zstyle ':vcs_info:*' enable git
    zstyle ':vcs_info:*' check-for-changes true
    zstyle ':vcs_info:*' unstagedstr '!'
    zstyle ':vcs_info:*' stagedstr '+'
    zstyle ':vcs_info:git*' formats "%F{blue}%r/%S%} %F{yellow}%b%f%m%F{red}%u%f%F{green}%c%f"
    autoload -Uz vcs_info
    precmd_vcs_info() { vcs_info }
    precmd_functions+=( precmd_vcs_info )
    setopt prompt_subst

    ### Display the existence of files not yet known to VCS

    ### git: Show marker (*) if there are untracked files in repository
    # Make sure you have added staged to your 'formats': %c
    zstyle ':vcs_info:git*+set-message:*' hooks git-untracked

    +vi-git-untracked(){
    if [[ $(git rev-parse --is-inside-work-tree 2> /dev/null) == 'true' ]] && \
    git status --porcelain | grep '??' &> /dev/null ; then
    # This will show the marker if there are any untracked files in repo.
    # If instead you want to show the marker only if there are untracked
    # files in $PWD, use:
    #[[ -n $(git ls-files --others --exclude-standard) ]] ; then
    hook_com[staged]+='%K{red]%}%F{black}*%f%k'
    fi
    }

    # export PROMPT="
    %F{green}%D{%Y-%m-%d} %D{%H:%M:%S}%f [%F{magenta}%~%f] \$vcs_info_msg_0_
    %F{cyan]%}%n%f@%F{blue}%m%f%} %(?..<%K{red}%F{yellow}%?%f%k> )%# "
    export PROMPT="
    %F{green}%D{%Y-%m-%d} %D{%H:%M:%S}%f \$vcs_info_msg_0_%F{cyan]%}%n%f [%F%F{magenta}%~%f%f%}] %(?..<%K{red}%F{yellow}%?%f%k> )%# "

    export RPROMPT=