Biblioteca de Scripts Úteis
Scripts curtos e prontos para as operações que ninguém quer fazer à mão, mais o padrão para transformá-los numa biblioteca reutilizável.
Antes de começar
- Scripting em Groovy — os pontos de entrada da API e as permissões
- Noção de atributos e ícones
O que você vai aprender
- Ter à mão scripts prontos para as operações repetitivas mais comuns
- Entender o padrão que cada um usa, para adaptá-los ao seu caso
- Organizar os seus scripts como biblioteca em vez de arquivos soltos
- Instalar um script como item de menu com atalho de teclado
Biblioteca de Scripts Úteis🔗
Um script vale a pena quando a operação é repetitiva, mecânica e propensa a erro humano. Os que estão aqui cobrem esse perfil: coisas que dá para fazer à mão em vinte minutos e que o script resolve em um segundo, sempre do mesmo jeito.
Todos assumem que há um nó selecionado e operam sobre o ramo dele. Todos devem ser testados numa cópia antes de rodar num mapa que importa — o desfazer de operações em massa não é confiável.
Estes scripts precisam da permissão de executar scripts sem assinatura em Preferências → Scripts. O que grava arquivo precisa também de escrever arquivos. Conceda o mínimo de que precisa e revise essas permissões depois.
1 — Numerar filhos🔗
Prefixa cada filho direto com um número sequencial. Útil para transformar um brainstorm em uma sequência de passos.
// NumerarFilhos.groovy
node.children.eachWithIndex { filho, i ->
def limpo = filho.text.replaceFirst(/^\d+\.\s*/, '')
filho.text = "${i + 1}. ${limpo}"
}
O replaceFirst é o que torna o script idempotente: rodá-lo duas vezes não produz 1. 1. Item. Vale a pena escrever assim todo script que altera texto — mais cedo ou mais tarde alguém roda de novo por engano.
2 — Registrar profundidade em atributo🔗
Escreve o nível de cada nó do ramo num atributo. Serve para filtrar por profundidade, coisa que a interface não oferece diretamente.
// RegistrarNivel.groovy
def raiz = node
node.branch.each { n ->
def d = 0
def p = n
while (p != raiz) { p = p.parent; d++ }
n['nivel'] = d as String
}
Depois disso, um filtro por nivel = 3 mostra exatamente um estrato do mapa.
3 — Achar nós órfãos de conteúdo🔗
Lista os nós que não têm texto útil — sobras de edição que se acumulam em mapas velhos.
// AcharVazios.groovy
def vazios = node.branch.findAll {
!it.plainText?.trim() && it.children.isEmpty()
}
if (vazios) {
vazios.each { it.style.name = 'Atenção' }
ui.informationMessage("${vazios.size()} nó(s) vazio(s) marcados com o estilo Atenção.")
} else {
ui.informationMessage('Nenhum nó vazio encontrado.')
}
Note o padrão: o script marca em vez de apagar. Alterar o mapa é fácil; desfazer não. Marcar e deixar a decisão com você é quase sempre o desenho certo para um script de faxina.
4 — Copiar ícone para os descendentes🔗
Propaga o ícone do nó selecionado para todo o ramo. Útil ao classificar um bloco inteiro de uma vez.
// PropagarIcones.groovy
def icones = node.icons.icons
node.branch.each { n ->
if (n == node) return
icones.each { ic ->
if (!n.icons.icons.contains(ic)) n.icons.add(ic)
}
}
A checagem de contains mantém a idempotência: rodar de novo não empilha o mesmo ícone várias vezes.
5 — Extrair o ramo como Markdown🔗
Gera um outline em Markdown do ramo selecionado e o coloca na área de transferência — o caminho mais rápido para levar estrutura do mapa para um editor de texto.
// RamoParaMarkdown.groovy
def linhas = []
def percorrer
percorrer = { n, nivel ->
linhas << (' ' * nivel) + '- ' + n.plainText.trim()
n.children.each { percorrer(it, nivel + 1) }
}
percorrer(node, 0)
def texto = linhas.join('\n')
java.awt.Toolkit.defaultToolkit.systemClipboard.setContents(
new java.awt.datatransfer.StringSelection(texto), null)
ui.informationMessage("${linhas.size()} linhas copiadas.")
A recursão com uma closure atribuída antes de ser usada (def percorrer; percorrer = {...}) é o idioma Groovy para recursão anônima — declarar e atribuir na mesma linha não funciona, porque a variável ainda não existe dentro do próprio corpo.
6 — Relatório de atributos ausentes🔗
Mostra quantos nós do ramo não têm cada chave preenchida. É o diagnóstico que vale rodar antes de confiar em qualquer filtro.
// AtributosAusentes.groovy
def chaves = ['responsável', 'prazo', 'estado']
def total = node.branch.size()
def relato = chaves.collect { chave ->
def faltam = node.branch.count { !it[chave] }
"${chave}: ${faltam} de ${total} sem valor"
}.join('\n')
ui.informationMessage(relato)
Se o número de ausentes for alto, o filtro que você acha que funciona está devolvendo menos do que deveria — e em silêncio.
Organizando como biblioteca🔗
Scripts soltos numa pasta são difíceis de manter. O caminho melhor é extrair o que se repete para um arquivo próprio e importá-lo:
~/.config/freeplane/<versão>/
├── scripts/
│ ├── NumerarFilhos.groovy
│ ├── RegistrarNivel.groovy
│ └── ...
└── lib/
└── Utilidades.groovy
Em lib/Utilidades.groovy:
class Utilidades {
static int profundidade(no, raiz) {
int d = 0
def p = no
while (p != raiz) { p = p.parent; d++ }
return d
}
static String outline(no, int nivel = 0) {
def s = (' ' * nivel) + '- ' + no.plainText.trim() + '\n'
no.children.each { s += outline(it, nivel + 1) }
return s
}
}
O diretório de bibliotecas é configurável em Preferências → Scripts. Uma vez apontado, os scripts passam a poder usar Utilidades.outline(node) sem repetir o código.
Versione a pasta scripts/ e lib/ num repositório Git. Além do histórico, é o que permite levar a sua biblioteca para outra máquina com um git clone — e é o passo anterior a empacotá-la como add-on.
Instalando como item de menu🔗
Um script salvo em scripts/ aparece automaticamente em Ferramentas → Scripts depois de reiniciar o Freeplane. Para dar-lhe atalho e posição de menu próprios, o caminho é declará-lo num add-on — que é, ele mesmo, um mapa com uma seção scripts contendo menuLocation, keyboardShortcut e executionMode.
A executionMode merece atenção: ON_SINGLE_NODE roda uma vez com node sendo o nó selecionado, enquanto ON_SELECTED_NODE roda uma vez por nó selecionado. Escolher errado faz um script de relatório abrir dez caixas de mensagem.
Erros comuns
- Rodar num mapa de trabalho sem testar em cópia — o desfazer de operações em massa não é confiável
- Escrever scripts não idempotentes, que corrompem o mapa quando rodados duas vezes
- Apagar em vez de marcar: um script de faxina que apaga é um script que você vai temer usar
- Conceder permissão de escrita em arquivos e nunca mais revisá-la
- Confundir
ON_SINGLE_NODEcomON_SELECTED_NODEe multiplicar a execução por seleção - Repetir o mesmo trecho em seis scripts em vez de extraí-lo para
lib/
Você aprendeu
- Script vale a pena quando a operação é repetitiva, mecânica e propensa a erro
- Idempotência não é refinamento: é o que impede estrago quando alguém roda de novo
- Marcar em vez de apagar mantém a decisão com o usuário
lib/mais o diretório de bibliotecas nas preferências transforma scripts soltos em biblioteca- Versionar a pasta de scripts é o passo anterior a empacotar como add-on
executionModedecide se o script roda uma vez ou uma vez por nó selecionado
Perguntas para reflexão
- O que torna o script de numeração idempotente, e por que isso importa?
- Por que um script de faxina deve marcar em vez de apagar?
- Qual é o idioma Groovy para recursão com closure, e por que a forma direta não funciona?
- Que problema o relatório de atributos ausentes detecta antes que ele apareça num filtro?
- Qual a diferença entre
ON_SINGLE_NODEeON_SELECTED_NODE? - Como se transforma um conjunto de scripts soltos numa biblioteca reutilizável?
Artigos relacionados
- Scripting em Groovy — a API e as permissões
- Fórmulas — quando não é caso de script
- Add-ons — empacotar e distribuir a sua biblioteca
- Linha de Comando e Automação — rodar sem abrir a interface
- Exportar atributos para CSV — um script completo, passo a passo
- Versionamento com Git — versionar a pasta de scripts