Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save guites/b5b53d5b04096351dae1cbefe8d82f32 to your computer and use it in GitHub Desktop.
Save guites/b5b53d5b04096351dae1cbefe8d82f32 to your computer and use it in GitHub Desktop.
markdown de exemplo pra geração de sumário
## O que é markdown
Se você usa o github, já deve ter formatado algum texto em markdown.
O markdown em si se trata de uma [ferramenta de conversão de texto em html](https://daringfireball.net/projects/markdown/),
e agiliza bastante o processo de quem edita textos direto pra web.
O github usa, notoriamente, uma versão incrementada, onde o markdown que você sobe \(geralmente no README.md\) passa por
uma etapa de processamento extra antes de ser transformado no html na página do seu repositório.
No seu formato mais básico, você pode simplesmente criar um arquivo em um editor de texto qualquer com a extensão .md,
e, seguindo as [regras de formatação](https://daringfireball.net/projects/markdown/syntax#autolink),
ele poderá ser utilizado para gerar um arquivo html.
### Extendendo o markdown
Uma das alterações mais legais feitas pelo github é gerar links baseados nos _headers_ da página.
Assim, pra cada tag <h1>,<h2>, etc, você pode clicar nela e utilizá-la de ancora.
Neste post eu mostro como criar um _wrapper_ pro programa _markdown_ do linux, com os seguintes objetivos:
1. Transformar os headers presentes no arquivo em links que adicionem um #nome-do-header na URL, facilitando o compartilhamento.
2. Gerar um sumário pro texto escrito baseado nos links gerados para os headers da página, em formato de lista de hyperlink.
Para isso, pretendo usar os programas _markdown_ e os utilitários comuns do linux, como _grep_, _sed_, etc.
Caso vocẽ não tenha o markdown:
sudo apt-install markdown
#### Gist com o markdown que gerou essa página
Se você quiser acompanhar os exemplos, pode baixar o markdown dessa página [neste link]().
### Buscando pelas tags no texto
No markdown, um header pode ser criado de três maneiras:
1. no estilo "setext", colocando sinais de igual ou hífens na linha inferior do texto do header.
> teste.md
> título
> =
>
> parágrafo
>
> subtítulo
> \-
2. no estilo "atx", colocando de 1 a 6 hashes \(#\) pra identificar o nível do header.
> teste2.md
> \# título
>
> parágrafo
>
> \#\# subtítulo
3. usando html puro. Nesse caso, atributos adicionais podem ser passados, como um `id` usado pra referenciar o
título em outro lugar do texto.
> teste3.md
> <h1 id="introducao">titulo</h1>
>
> parágrafo
> <h2>subtítulo</h2>
Nos dois primeiros casos, o html gerado vai ser
> teste.html
> &lt;h1&gt;Meu título&lt;/h1&gt;
> &lt;p&gt;Meu parágrafo de texto&lt;/p&gt;
>
>
> &lt;h2&gt;Meu subtítulo&lt;/h2&gt;
O terceiro caso geraria o mesmo html, mas com um id definido na tag `h1`:
> &lt;h1 id="introducao"&gt;Meu título&lt;/h1&gt;
Vou focar nos estilos atx e html, deixando o setext disponível caso eu queria headers não linkados.
### Organizando um script em bash
Vamos criar um arquivo mdwrapper.sh,
#!/bin/bash
if [ -z "$1" ]; then
echo "Você precisa passar o nome do arquivo .md como argumento."
return
fi;
Começamos testando se o usuário passou um argumento ao chamar o programa. A flag \-z no
bash retorna verdadeiro quando uma variável está vazia.
Precisamos também verificar se o argumento representa um arquivo válido, e se a extensão é a esperada.
Para verificar se é um arquivo, temos a flag \-f, e a extensão pode ser verificada utilizando o _bashism_
[parameter extension](https://stackoverflow.com/a/965072/14427854)
#!/bin/bash
file=$1
extension="${file##*.}"
if ( [ -z "$file" ] || ! [ -f "$file" ] || [ "md" != "$extension" ] ); then
echo >&2 "Você precisa passar o nome de um arquivo .md como argumento."
exit 1
fi;
Para encontrar as linhas que começam com o hash, vamos utilizar o comando `grep`, onde _^_ significa "início de linha"
grep ^\# $file
Que, para um arquivo .md arbitrário, vai nos dar um output do tipo
> \#\# O que é markdown
> \#\#\# Extendendo o markdown
> \#\#\#\# Gist com o markdown que gerou essa página
> \#\#\# Buscando pelas tags no texto
> \#\#\# Organizando um script em bash
> \#\#\#\# Alterando o separador padrão do bash
> \#\#\#\# Iterando sobre uma lista
> \#\#\# Gerando o html e concatenando os arquivos
> \#\#\# Adicionando o ID correspondente aos headers
> \#\# Código completo
> \#\# Fontes não citadas no texto:
Para incluir títulos criados no estilo html, você vai precisar usar um operador OR (`|`) dentro do grep.
```
grep '^\#\|^<h' $file
```
Precisamos transformar esse output em uma lista, para que possamos iterar através de cada header.
#### Alterando o separador padrão do bash
Vamos substituir o grep anterior pela sequência abaixo:
IFS_BAK=${IFS}
IFS="
"
allHeaders=($(grep ^\# $file))
IFS=${IFS_BAK}
Como o output do grep é separado por linhas, precisamos alterar o [IFS](https://bash.cyberciti.biz/guide/$IFS) \(Internal Field Separator\) padrão do sistema. O IFS é a variável que determina como o bash vai delimitar os campos. Por padrão, são utilizados espaços e tabs.
Pra ilustrar o efeito de alterar o IFS, podemos rodar o mesmo comando acima com o IFS padrão
allHeaders=($(grep ^\# $file))
echo ${allHeaders[0]}
echo ${allHeaders[1]}
echo ${allHeaders[2]}
echo ${allHeaders[3]}
echo ${allHeaders[4]}
echo ${allHeaders[5]}
echo ${allHeaders[6]}
Teremos como saída
> \#\#
> O
> que
> é
> markdown
Pois o shell colocou cada palavra separada por espaço como um ítem da lista.
#### Iterando sobre uma lista
Vamos fazer um loop e formatar cada uma das linhas da nossa lista. Precisamos transformar o texto do header em um slug.
Cada item, após ser formatado em markdown, vai ser concatenado em uma string, que depois será transformada em html.
mkd=''
for header in ${allHeaders[@]}; do
anchortext=$(echo $header | sed 's/\#//g')
anchorlink=$(echo $anchortext | sed 's/[ -]/\\-/g' | sed 's/^..//g' | sed 's/[\(\)<>_*]//g')
li="- [$anchortext](\#$anchorlink)"
mkd=$mkd$li
done
IFS=${IFS_BAK}
Primeiro, utilizo o `sed 's/\#//g'` para remover os hashes do texto que vai ficar no link.
Depois, uma série de substituições para ajustar o link em si, que vai ser utilizado como atributo _href_
1. `sed 's/[ -]/\\-/g'` Trocar os espaços, que não podem ser utilizados em links, e hífens, que são considerados um caractere reservado no markdown, pelo hífen escapado.
2. `sed 's/^..//g'` Remover os dois primeiros caracteres da linha. Como fiz a substituição dos espaços por hífens, precisei ajustar.
3. `sed 's/[\(\)<>_*]//g'` Substituir outros caracteres que não podem ser utilizados em link. Detalhe que para os parenteses, que são reservados no grep, e precisam ser escapados.
Então, formato a string começando com um traço, que no markdown representa um item de lista, e uso o texto dentro dos colchetes seguidos pelo link entre parenteses. Neste formato, o markdown cria uma anchor tag \(&lt;a href="..."&gt;\).
Essa string é concatenada junto com o que já estiver salvo na variável, até que o loop seja concluido. Por fim, retornamos o separador IFS pro valor padrão do sistema.
### Gerando o html e concatenando os arquivos
Vamos passar o valor da variável $mkd como entrada no comando markdown, e concatenar com o html do arquivo md.
cat <(markdown <(echo $mkd | tr ')' '\n' | sed 's/$/)/g' | head --lines=-1)) <(markdown $file) > $file.html
Aqui você pode escolher entre deixar o sumário no início ou fim da página, alterando a ordem dos parametros do cat.
Note que ainda aqui precisei ajustar a string em $mkd pra ter a saída correta. Como a string é de uma só linha, uso o comando tr pra substituir os parenteses \) por um \n. Depois, preciso readicionar o parentese removido.
Neste ponto, o seu programa deve estar gerando o sumário neste formato:
> \- \[ O que é markdown\]\(\#O-que-é-markdown\)- \[ Extendendo o markdown\]\(\#Extendendo-o-markdown\)- \[ Gist com o markdown que gerou essa página\]\(\#Gist-com-o-markdown-que-gerou-essa-página\)- \[ Buscando pelas tags no texto\]\(\#Buscando-pelas-tags-no-texto\)- \[ Organizando um script em bash\]\(\#Organizando-um-script-em-bash\)- \[ Alterando o separador padrão do bash\]\(\#Alterando-o-separador-padrão-do-bash\)- \[ Iterando sobre uma lista\]\(\#Iterando-sobre-uma-lista\)- \[ Gerando o html e concatenando os arquivos\]\(\#Gerando-o-html-e-concatenando-os-arquivos\)- \[ Adicionando o ID correspondente aos headers\]\(\#Adicionando-o-ID-correspondente-aos-headers\)- \[ Código completo\]\(\#Código-completo\)- \[ Fontes não citadas no texto:\]\(\#Fontes-não-citadas-no-texto:\)
E, após transformado em html:
em html
<ul>
<li><a href="#O-que-é-markdown"> O que é markdown</a></li>
<li><a href="#Extendendo-o-markdown"> Extendendo o markdown</a></li>
<li><a href="#Gist-com-o-markdown-que-gerou-essa-página"> Gist com o markdown que gerou essa página</a></li>
<li><a href="#Buscando-pelas-tags-no-texto"> Buscando pelas tags no texto</a></li>
<li><a href="#Organizando-um-script-em-bash"> Organizando um script em bash</a></li>
<li><a href="#Alterando-o-separador-padrão-do-bash"> Alterando o separador padrão do bash</a></li>
<li><a href="#Iterando-sobre-uma-lista"> Iterando sobre uma lista</a></li>
<li><a href="#Gerando-o-html-e-concatenando-os-arquivos"> Gerando o html e concatenando os arquivos</a></li>
<li><a href="#Adicionando-o-ID-correspondente-aos-headers"> Adicionando o ID correspondente aos headers</a></li>
<li><a href="#Código-completo"> Código completo</a></li>
<li><a href="#Fontes-não-citadas-no-texto:"> Fontes não citadas no texto:</a></li>
</ul>
### Adicionando o ID correspondente aos headers
Com o sumário formatado corretamente, só nos falta adicionar o Id correspondente nos headers, para que a navegação funcione.
Vamos utilizar o grep pra criar uma nova lista, com as tags h1,h2.. no formato "&lt;h1&gt;Título da seção&lt;/h1&gt;".
IFS_BAK=${IFS}
IFS="
"
htmlHeaders=($(grep "<h.>" $file.html))
for header in ${htmlHeaders[@]}; do
headerId=$(echo $header | grep -o ">.*<" | sed 's/[ -]/\-/g' | sed 's/[()<>_*]//g')
tagWithId=$(echo $header | sed "s/>/ id=\"$headerId\"><a href=\"$headerId\"><\/a>/1")
sed -i "s{$header{$tagWithId{g" $file.html
done
IFS=${IFS_BAK}
exit 0
Pegamos o id, que é tratado da mesma forma que fizemos para gerar o link. Depois, colocamos o atributo no html e substituimos a tag antiga pela nova, com o id, direto no arquivo gerado pelo markdown.
Eu optei por deixar o texto do link no header vazio, pra depois estilizar com css.
Pronto!
## Código completo
Você pode usar o código abaixo, basta salvá-lo num arquivo, por exemplo, mdwrapper.sh.
Depois, dê permissão de execução e mova-o para um diretório que estiver no seu PATH.
#!/bin/bash
file=$1
extension="${file##*.}"
if ( [ -z "$file" ] || ! [ -f "$file" ] || [ "md" != "$extension" ] ); then
echo >&2 "Você precisa passar o nome de um arquivo .md como argumento."
exit 1
fi;
IFS_BAK=${IFS}
IFS="
"
allHeaders=($(grep ^\# $file))
mkd=''
for header in ${allHeaders[@]}; do
anchortext=$(echo $header | sed 's/\#//g')
anchorlink=$(echo $anchortext | sed 's/[ -]/\\-/g' | sed 's/^..//g' | sed 's/[\(\)<>_*]//g')
li="- [$anchortext](\#$anchorlink)"
mkd=$mkd$li
done
IFS=${IFS_BAK}
cat <(markdown <(echo $mkd | tr ')' '\n' | sed 's/$/)/g' | head --lines=-1)) <(markdown $file) > $file.html
IFS_BAK=${IFS}
IFS="
"
htmlHeaders=($(grep "<h.>" $file.html))
for header in ${htmlHeaders[@]}; do
headerId=$(echo $header | grep -o ">.*<" | sed 's/[ -]/\-/g' | sed 's/[()<>_*]//g')
tagWithId=$(echo $header | sed "s/>/ id=\"$headerId\"><a href=\"$headerId\"><\/a>/1")
sed -i "s{$header{$tagWithId{g" $file.html
done
IFS=${IFS_BAK}
exit 0
Utilize-o com
mdwrapper nome_do_arquivo_markdown.md
## Fontes não citadas no texto:
- <https://tldp.org/LDP/abs/html/exitcodes.html>
- <https://bash.cyberciti.biz/guide/$IFS>
- <https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html#Shell-Parameter-Expansion>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment