O problema
Eu uso o moneylog já há algum tempo e desenvolvi algumas funções para facilitar a minha vida, e algo que estava me incomodando era o fato de ter que organizar todos os canhotos do cartão de crédito para depois lançá-los no meu registro.
Tá, eu sei que no HTML o moneylog mostra ordenado os registros no browser, mas eu queria eles organizados no texto tb
O que eu queria era poder lançar os registros na ordem em que fosse pegando os registros e que depois “uma função mágica” ordenasse eles pra mim
(fácil né?!)
A solução
Como eu uso o emacs para lançar os registros, nada mais natural que fosse dentro do emacs que fosse feita essa organização.
Os meus registros tem o seguinte formato:
AAAA-MM-DD -X.XX tag1, tag2| @MM.DD Descrição do gasto
Imaginei várias soluções, desde a mais simples: ordenar as linhas alfabéticamente, até outras mais complexas, onde preencheria hash tables com a data do gasto para poder organizar o conjunto de linhas.
Como cada registro é uma única linha, eu já tinha uma idéia de como seriam os registros.
O problema para a solução mais simples é que, como eu coloco a data de lançamento para a data de vencimento do meu cartão ordenar as entradas pelo primeiro campo (AAAA-MM-DD) não iria adiantar nada, pois as entradas teriam a mesma data
Para a abordagem de colocar todas as entradas em hash tables há o problema de que posso ter várias entradas no mesmo dia, independente de usar o campo AAAA-MM-DD ou @MM.DD e sendo assim eu perderia entradas
A solução acabou sendo a mais simples (ou segunda mais simples), copiar a data de compra @MM.DD para o início da linha, onde meu registro temporário ficaria assim:
@MM.DD AAAA-MM-DD -X.XX tag1, tag2| @MM.DD Descrição do gasto
E depois mandar ordenar as linhas com (sort-lines nil (point-min) (point-max)).
Pronto, os registros já estavam ordenados, porém agora eu não tinha mais o formato do moneylog, mas estava fácil voltar ao formato original.
Bastava recortar a primeira parte do texto com (kill-rectangle minr maxr) e voi-lá, estaria de volta no formato certo.
Uso
Para usar a função, basta selecionar a região e digitar M-x i-ordena-ccredit.
O resultado esperado é que os registros apareçam ordenados e com o texto #ordenado na linha logo antes do primeiro registro.
Antes
2011-01-17 -1.25 ccred11_01, diversos, mercadorama| @12.20 1 refri Cini no mercadorama sabor gengibirra 2011-01-17 -1.20 ccred11_01, bapka| @12.23 2 picolés de morango na bapka 2011-01-17 -1.20 ccred11_01, bapka| @12.27 2 picolés de morango na bapka 2011-01-17 -0.60 ccred11_01, bapka| @12.21 Sorvete de Morango na Bapka
Depois
# ordenado 2011-01-17 -1.25 ccred11_01, diversos, mercadorama| @12.20 1 refri Cini no mercadorama sabor gengibirra 2011-01-17 -0.60 ccred11_01, bapka| @12.21 Sorvete de Morango na Bapka 2011-01-17 -1.20 ccred11_01, bapka| @12.23 2 picolés de morango na bapka 2011-01-17 -1.20 ccred11_01, bapka| @12.27 2 picolés de morango na bapka
O Código
;; Selecione ANTES a região com as linhas a serem ordenadas ;; muito provavelmente os registros do cartão de crédito;; A função Ordena os registros do seguinte tipo com base na data colocada em @mm.dd ;;-------------------------------------------------------------------------- 2011-01-17 -0.60 ccred11_01, bapka| @12.21 Sorvete de Morango na Bapka 2011-01-17 -60.00 ccred11_01, carro| @12.21 Gasolina para o Carro 2011-01-17 -6.30 ccred11_01| @12.21 Carpetes e Velcro de Proteção para Árvore na Tecidos Avenida 2011-01-17 -10.50 ccred11_01, estac| @12.22 Estacionamento Almoço 2011-01-17 -36.00 ccred11_01, presente| @12.21 Camisetas ;;--------------------------------------------------------------------------- (defun i-ordena-ccredit () "Ordena os registros de data do cartão de crédito." (interactive) ;; discover point-min and point-max from the selected region, or use the next word (let (pos1 pos2 bds) (if (and transient-mark-mode mark-active) (setq start (region-beginning) end (region-end)) (progn (setq bds (bounds-of-thing-at-point 'symbol)) (setq start (car bds) end (cdr bds)) )) ; idiom for string replacement in region ;; restrict the atuation of the next commands to the selected region (with-output-to-temp-buffer "*registros*" (save-restriction (narrow-to-region start end) (goto-char (point-min)) (while (< (point) (point-max)) ;; reads the buffer line (setq linha (buffer-substring (line-beginning-position) (line-end-position))) ;; matches the date field (string-match "\\(@[0-9][0-9].[0-9][0-9]\\)" linha) ;; puts the date matched in the data variable (setq data-compra (match-string 0 linha)) ;; prints the line with the buy date's first (princ (format "%s %s\n" data-compra linha)) (forward-line 1) ) ;; repeat for other string pairs ) ) ;; creates a new buffer called *registros to manipulate the strings (let ((oldbuf (current-buffer))) (set-buffer "*registros*") ;; let we modify the buffer (setq buffer-read-only nil) ;; as the buffer already has the lines in the right format, we sort them all ;; @12.21 2011-01-17 -36.00 ccred11_01, presente| @12.21 Camisetas (sort-lines nil (point-min) (point-max)) ;; we need to define the region that will be killed (the @mm.dd part at the front of the line) ;; that was inserted before (setq minr (point-min)) (goto-char (point-max)) (forward-line -1) (re-search-forward "\\(@[0-9][0-9].[0-9][0-9] \\)") (setq maxr (point)) ;; kill the rectangle (kill-rectangle minr maxr) ;; now we kill the whole region to put it into the kill ring (kill-region (point-min) (point-max)) ;; kill the temporary buffer (kill-buffer (current-buffer)) (set-buffer oldbuf)) ;; we'll kill the old region to put the new sorted lines (kill-region (region-beginning) (region-end)) (goto-char (region-end)) (forward-line) (insert (format "# ordenado\n")) ;; as we have killed the selected region, the ordered lines are in the 2nd position of the kill ring (yank 2) ) )
Ah, o mesmo código acima pode ser encontrado no pastebin.
E é isso