Dando continuadade ao post sobre Rakefile. Agora, vamos focar na criação de rascunhos e na menção de imagens.

Apenas para recordar, esse artigo é a segunda parte de 3:

Na primeira parte tivemos:

  1. apanhado geral do Rakefile
  2. rodar o Jekyll
  3. fazer a ação de publicar (leia artigo Movendo de draft para post)

Na parte 2 teremos:

  1. regras e patterns simples
  2. criar um novo post (leia artigo Criando posts com Makefile)
  3. pegar menção de imagem (leia artigo Automatizando menção de imagem)

E por fim, na parte 3:

  1. iremos remover chamadas de bash e ficar apenas com ruby

Regras e patterns simples

No primeiro artigo vimos o exemplo de como criar uma task simples. Aqui, vamos começar com um arquivo simples e evoluir para uma rule simples.

Você pode se aprofundar mais no assunto consultando a documentação

Arquivos simples, file

Vamos criar um arquivo simples chamado de hello.md. Para começar, podemos ter no conteúdo dele o seguinte conteúdo:

# Hello, world!

Para isso, no Rakefile precisamos criar um alvo para ele criar: que no caso será o hello.md:

file 'hello.md'

Só que essa task ainda não faz nada. Pelo manos não falha. Podemos incrementar apenas para ver que está funcionando colocando um puts com o nome do arquivo algo:

file 'hello.md' do |t|
    puts t.name
end

E voi là, imprimiu o nome base:

$ rake hello.md
hello.md

Ok, prova de conceito estabelecida. Agora, como escrever o conteúdo nesse arquivo?

Bem, uma maneira é abrir o arquivo em modo de escrita e escrever lá. O ruby oferece uma maneira de abrir o arquivo e passar um bloco logo em seguida; nesse bloco operações podem ser feitas no arquivo e, ao sair do bloco, o arquivo será fechado e liberado. Para tal, só usar o File.open do Ruby e passar o arquivo em questão, o modo de abertura (que para escrita é w) e o bloco com a escrita para o arquivo.

No caso, o trecho responsável por escrever no arquivo é o seguinte:

File.open t.name, mode = 'w' do |file|
    file.write '# Hello, world!'
end

Ao executar rake hello.md, podemos perceber que agora ele cria o arquivo hello.md com o conteúdo esperado. Alterar o arquivo e fazer subsequentes chamadas a rake hello.md não ocasiona nenhuma alteração, conforme esperado.

Essa regra fica assim:

file 'hello.md' do |t|
    File.open t.name, mode = 'w' do |file|
        file.write '# Hello, world!'
    end
end

Regras de criação, rule

Agora, vamos aumentar o poder do nosso “hello, world”? Vamos criar arquivos .md com o nome das pessoas que queremos dar “hello”? Por exemplo, podemos criar um jeff-hello.md, com os dizeres # Hello, jeff!.

Para isso, usamos uma rule no rakefile. Vamos primeiro adaptar o que já existia e depois adicionamos a capacidade dele de detectar o nome:

rule '-hello.md' do |t|
    File.open t.name, mode = 'w' do |file|
        file.write '# Hello, world!'
    end
end

Com isso já conseguimos fazer um rake jeff-hello.md.

Notou que a regra usamos -hello.md e isso passou a identificar o que em Makefile detectaria %-hello.md? Então, os padrões mais simples de regras no Rakefile seguem essa lógica. Inclusive, caso se deseje colocar dependência, automaticamente ele detectar isso.

Side-track: usando dependências

Um exemplo de dependência da própria docimentação:

rule '.o' => '.c'

Isso quer dizer que para gerar o bola.o ele vai precisar consultar o arquivo bola.c, e alterações em bola.c vão desencadear na geração de um novo bola.o.

Hmmm, e se eu tivesse um requisito? Vamos supor que queremos criar um arquivo .md.bkp, que é a cópia de um arquivo .md (apenas para motivo de exemplo, não tem muita coisa por baixo disso). Podemos fazer essa regra de dependência desse jeito:

rule '.md.bkp' => '.md'

E para processar? Bem, nesse caso podemos chamar o método File.copy_stream, passando como arquivo o nosso fonte e o nosso alvo. A regra inteira fica assim:

rule '.md.bkp' => '.md' do |t|
    File.copy_stream(t.source, t.name)
end

E se eu chamar, por exemplo, com jeff-hello.md.bkp, sendo que não existe o arquivo jeff-hello.md a priori? Bem, como temos uma regra que define como computar arquivos -hello.md, ela será disparada para criar o jeff-hello.md e, logo em seguida, a chamada para criar jeff-hello.md.bkp descrita ali em cima.

Note que não é mandatório que só se tenha uma única dependência, podemos ter diversas dependências.

Finalizando a escrita

Deixando a side-track de lado, agora precisamos identificar o nome passado para dar as saudações no arquivo. O jeito mais fácil é remover o -hello.md do nome do arquivo. Então, por que não?

rule '-hello.md' do |t|
    fileName = t.name
    person = fileName.sub '-hello.md', ''
    File.open fileName, mode = 'w' do |file|
        file.write "# Hello, #{person}!"
    end
end

Padrões avançados

Ao ser informada uma string para o rule, ele vai tratar que tudo que estiver com aquele terminador será um arquivo a ser tratado. Porém, podemos passar coisas mais inteligentes para esse fim: pode ser uma regex.

No caso específico, gostaria que ficasse dentro do diretório _drafts/ e que termine com .md. No caso de passar uma regex para o rule precisamos envolver com parênteses todo o conteúdo antes do bloco:

rule(/^_drafts\/.*\.md$/)

As dependências podem ser declaradas como uma função que recebe a task em questão e retorna a lista com as dependências.

Criando um novo post

Bem, já demos um spoiler ao indicar que rascunhos são criados necessariamente na pasta _drafts, e para o caso específico eles são .md, e que não tem dependência.

rule(/^_drafts\/.*\.md$/) do |t|
    # magic here
end

Pegando a ideia do criando posts com Makefile, seria interessante perguntar o título e as tags. Então, obtendo essas informações, e sabendo o nome do arquivo (chamado de radix), podemos substituir no template:

# title lido do usuário
# tags lido do usuário
# radix baseado em t.name
    template =  "---
layout: post
title: \"#{title}\"
author: \"Jefferson Quesado\"
tags: #{tags}
base-assets: \"/assets/#{radix}/\"
---
"

Então, com o modelo em mãos, só abrir o arquivo e escrever:

rule(/^_drafts\/.*\.md$/) do |t|
    fileName = t.name

    radix = fileName.sub /_drafts\/(.*)\.md/, '\1'
    require "cli/ui"
    title = CLI::UI::Prompt.ask('Qual o título?')
    tags = CLI::UI::Prompt.ask('Quais as tags (separadas por espaço)?')

    template =  "---
layout: post
title: \"#{title}\"
author: \"Jefferson Quesado\"
tags: #{tags}
base-assets: \"/assets/#{radix}/\"
---
"
    File.open fileName, mode = 'w' do |file|
        file.write template
    end
end

Porém, tem uma coisa que poderia deixar mais fácil o meu trabalho: abrir o markdown recém criado no VSCode. Para tal, a maneira mais fácil que eu achei foi gerar um novo processo da shell e esperar ele se concluir:

spawn("command")
Process.wait

No caso específico do VSCode, fiz o bund das saídas com as saídas do terminal. Ficou algo assim:

spawn("command", :out -> :out, :err => :err)
Process.wait

E, finalmente, o comando de verdade é esse, chamando code na CLI e passando como argumento o nome do arquivo:

spawn("code #{fileName}", :out => :out, :err => :err)
Process.wait

Mencionando imagens

A menção de imagens não consegui fazer de modo tão natural quanto no shell script, já que o auto-complete do rakefile vai inspecionar atrás de tasks internas cadastradas.

De toda sorte, foi feito.

Tal qual o mention-image.sh, ele produz uma saída bem simples:

{{ page.base-assets | append: <IMAGE-TO-BE-CITED> | relative_url }}

Para evitar chocar com nomes existentes, usei por convenção que comandos de citação são necessariamente terminados em :mention.

Então, para mencionar o Ferris associado a ester artigo, faço a chamada:

rake assets/rakefile-create-draft/ferris.jpg:mention

E obtenha como saída:

{{ page.base-assets | append: "ferris.jpg" | relative_url }}

E posso usar o output para colocar o Ferris aqui:

Ferris, como imagem de exemplo

A regra para criar isso:

rule(/^assets\/.*\.(png|jpe?g|gif|svg):mention$/) do |t|
    referenceFromBaseAssets = t.name.split(":")[0..-2].join(":").split("/")[2..].join("/")

    puts "{{ page.base-assets | append: \"#{referenceFromBaseAssets}\" | relative_url }}"
end

Por partes:

  • eu pego o algo (t.name)
  • removo apenas o :mention do final (na real, o último :<string>):
    • separo em cima do : com split(":")
    • pego um slice do vetor, indo da primeira posição 0 até a penúltima -2
    • para pegar um slice, só usar <ini>..<fim>
    • a última posição é -1, portanto a penúltima é -2
    • junto tudo de novo com join(":")
  • removo o assets/<nome da base dos assets>/
    • semelhante a como removi o :mention, mas removendo os 2 primeiros componentes de diretórios
    • separo em cima do / com split("/")
    • pego a terceira posição 2 até o fim com um splice 2..
    • junto tudo de novo com join("/")