jq: filtrar índice inverso por valor de un subcampo

Minientrada

Amo jq, pero a la vez daría lo que fuera por poder aprender del todo su lenguaje de consulta porque a veces se me atraganta.

Sería muy fácil con jq hacer una búsqueda en un array de objetos en función de lo que vale uno de los campos de cada objeto, ¿verdad? Hablo de un array como este:

[
  {
    "nombre": "Carmen",
    "departamento": "Ventas",
    "superior": "Ana"
  }
]

Pero ¿qué pasa si en vez de un array tengo un objeto a modo de índice inverso? Me refiero a la clásica de fabricar con este array un objeto donde como valores tengo cada uno de los valores del array, pero como clave tengo algún identificador concreto de cada objeto. Al fin y al cabo, buscar items en una lista tiene complejidad O(n), pero hacerlo en un mapa tiene complejidad O(1). Es más rápido hacer lista["Ana"] que hacer lista.find(i => i.name == "Ana").

{
  "Carmen": {
    "nombre": "Carmen",
    "departamento": "Ventas",
    "superior": "Ana"
  }
}

El secreto está en usar to_entries y from_entries para convertir entre objeto y array de entries. jq 'to_entries | from_entries' < input.json se anula entre sí. Entre ambas sentencias podemos plantar un select para hacer un filtro de aquellos elementos del nuevo array generado por to_entries que filtre por una condición concreta para .[].value.whatever.

jq 'to_entries | select(.[].value.superior == "Ana") | from_entries' < empleados.json

La salida de to_entries es un array, por lo que cuando se hace el select para filtrar elementos que cumplan un criterio, hay que empezar la query con un .[], para poder hacer introspección sobre cada elemento del array. Realmente no es muy complejo, pero me lo apunto porque me ha costado un poco de ensayo y error dar con el orden correcto en el que tengo que poner los puntos y los corchetes.

Importando de redes sociales con XML-RPC

Minientrada

Una de las cosas bonitas de la GDPR es que casi obliga a las redes sociales y sitios similares a tener un sistema de exportado de datos. Y casi siempre lo hacen con algún formato estructurado como CSV, JSON o XML, fácil de procesar por un ordenador.

Paralelamente, WordPress tiene el viejo confiable XML-RPC para crear posts de forma programática. Si no tienes mucha idea, recomiendan cerrar ese endpoint al exterior para evitar problemas. Pero en verdad, si sabes protegerlo, resulta muy práctico para crear posts a golpe de petición HTTP. (En teoría existe la API REST, pero bueno…)

Con la ayuda de un cliente XML-RPC para WordPress que hay en NPM y de la API de Hacker News, hice en Node.js un script de un solo uso que crease una entrada por cada comentario y enlace enviado para enlazar hacia el comentario o la historia, siguiendo el modelo de sindicación PESOS de IndieWeb (Publish Elsewhere, Syndicate to Own Site). Le he puesto la etiqueta hn-import a la colección. ¿Cuál será el próximo archivo que importe?

En sí la librería no es muy complicada de utilizar. Una vez tienes un cliente conectado es muy fácil chutarle un array de posts a crear (por ejemplo, procesar fila a fila un CSV o item a item un JSON o un XML). Primero se crea un cliente:

const { createClient } = require("wordpress")

const client = createClient({
  url: "https://example.com",
  username: "mi user",
  password: "mi password"
})

Luego definiendo el payload. Aquí es donde encuentro más cómodo crear un objeto JSON que declarar todo el chorizo XML de XML-RPC:

const payload = {
  title: "El título de mi entrada",
  content: `
    <p>El contenido de mi entrada.</p>
    <blockquote>Aceptamos HTML.</blockquote>
    <p><a href="https://www.example.com">Y enlaces</a></p>
  `.trim(),
  status: 'publish', // podría ser 'draft' o 'private'
  termNames: {
    category: ['Categoría'],
    post_tag: ['tag 1', 'tag 2', 'tag 3'],
  },
  date: '2006-01-02 15:04:05',
  format: 'link' // podría ser 'aside', 'status', 'photo', ...
}

Y ya postear usando newPost. La librería es previa a promesas así que su único punto malo es que está orientada a callbacks.

client.newPost(payload, (err, id) => {
  if (err) {
    console.error(err)
  } else {
    console.log(`ID del nuevo post: ${id}`)
  }
})