{"version":"https://jsonfeed.org/version/1.1","title":"Alessandro Jean's Blog","description":"Just a personal blog.","authors":[{"name":"Alessandro Jean","avatar":"https://alessandrojean.github.io/img/avatar-okabe-small.webp"}],"language":"pt-BR","home_page_url":"https://alessandrojean.github.io/blog","feed_url":"https://alessandrojean.github.io/blog/feed.json","icon":"https://alessandrojean.github.io/img/apple-touch-icon.png","items":[{"id":"dica-de-serie-halt-and-catch-fire","url":"https://alessandrojean.github.io/post/dica-de-serie-halt-and-catch-fire","title":"Dica de série: Halt and Catch Fire","summary":"Recomendação de uma ótima série sobre tecnologia.","content_html":"<p>Descobri por acaso a série <span lang=\"en\">Halt and Catch Fire</span> navegando pelo Reddit, e comecei a assistir por conta do trama ser relacionado a tecnologia. Não esperava que ia me deparar com uma série tão boa, me surpreende que não seja tão comentada.</p>\n<figure><img src=\"https://image.tmdb.org/t/p/w1920/3ObqsNzAOOZoK0K98KKtTeYP1uV.jpg\" alt=\"Foto promocional da segunda temporada da série. Nela, há quatro pessoas, onde três estão sentadas em um sofá, e uma delas está em pé em cima do sofá. No fundo na parede, há uma pichação em inglês escrito &#x22;Welcome to Mutiny&#x22;.\"><figcaption>© 2015 AMC Film Holdings LLC. Todos os direitos reservados.</figcaption></figure>\n<p>Pode-se dizer que essa série é a “irmã” dramática da série de comédia da HBO <a href=\"https://www.imdb.com/pt/title/tt2575988/\">Silicon Valley</a>, que também é ótima, por sinal. HaCF é bem mais direta ao ponto e mais “pé no chão” se você comparar com a outra série famosa da área: <a href=\"https://www.imdb.com/pt/title/tt4158110\">Mr. Robot</a>.</p>\n<p>Eu já assisti vários filmes sobre a história da tecnologia, sendo todos eles em geral biografias ou baseados em fatos reais. <abbr title=\"Halt and Catch Fire\">HaCF</abbr> não é exatamente baseado nos fatos reais, mas muitos dos acontecimentos são baseados neles.</p>\n<p>Por exemplo, a primeira temporada, que é excelente, é sobre o desenvolvimento de um clone do <a href=\"https://pt.wikipedia.org/wiki/IBM_PC\">IBM PC</a>. Na série, a empresa que os personagens trabalham, a Cardiff Electric, é a que está fazendo a P&#x26;D, enquanto na vida real foi a finada <a href=\"https://pt.wikipedia.org/wiki/Compaq\">Compaq</a>.</p>\n<p>Os personagens também são muito carismáticos, e boa parte do uso de tecnologia é verídico. Foi bem legal ver algumas cenas com o Gordon programando em um <a href=\"https://pt.wikipedia.org/wiki/Commodore_64\">Commodore 64</a> com a linguagem C, usando o livro <span lang=\"en\">“The C Programming Language”</span>. Como uma pessoa da área, estou me divertindo pra caramba com a tecnologia da época sendo mostrada e usada.</p>\n<p>Ainda não terminei de assistir todos os episódios, mas o que vi até agora tem um alto valor de produção. O bom também é que a série foi finalizada com 4 temporadas, cada uma com 10 episódios. Atualmente estamos acostumados a ver o lançamento das temporadas em <em>streamings</em> sem sequer saber se a série será renovada ou cancelada, então saber que ela foi planejada e terminada é um alívio.</p>","date_published":"2026-06-20T00:00:00.000Z","date_modified":"2026-06-21T11:29:00.000Z","tags":["Televisão"],"language":"pt-BR"},{"id":"criando-um-feed-rss-com-nuxt-content","url":"https://alessandrojean.github.io/post/criando-um-feed-rss-com-nuxt-content","title":"Criando um feed RSS com Nuxt Content","summary":"Um passo-a-passo simples em como criar um feed e também disponibilizar o HTML dos posts.","content_html":"<p>Disponibilizar um feed RSS quando você está utilizando o Nuxt Content não é difícil, mas você pode acabar encontrando alguns obstáculos no caminho.</p>\n<h2 id=\"gerando-o-feed\">Gerando o feed</h2>\n<p>Iremos começar criando a rota no servidor que irá disponibilizar o RSS. Normalmente é conveniente colocar o feed no mesmo caminho que seu conteúdo está. Por exemplo, se sua lista de posts fica em <code>/blog</code>, o caminho <code>/blog/feed.xml</code> é uma boa escolha.</p>\n<p>Usando o Nuxt, podemos criar essa rota ao criar o arquivo <code>server/routes/blog/feed.xml.get.ts</code>.</p>\n<div><p><strong>Cuidado:</strong></p><p>Esta não é uma rota de API, então não deve ser colocada em <code>/server/api</code>.</p></div>\n<p>O esqueleto do arquivo é basicamente este:</p>\n<pre><code class=\"language-ts\">import { queryCollection } from '@nuxt/content/server';\n\nexport default defineEventHandler(async (event) => {\n  const posts = await queryCollection(event, 'blog')\n    .order('created_at', 'DESC')\n    .limit(10)\n    .all();\n\n  return posts;\n});\n</code></pre>\n<p>O que queremos fazer é manipular as postagens tal que a rota na verdade retorne um feed RSS válido. Podemos usar o pacote <a href=\"https://npmjs.com/package/rss\" target=\"_blank\" rel=\"noopener noreferrer external\">rss</a> para nos ajudar, já que ele contém um construtor de RSS que facilita as coisas.</p>\n<p>Nós iremos usar a classe <code>RSS</code>.</p>\n<pre><code class=\"language-ts\">import RSS from 'rss';\n</code></pre>\n<p>Para começar, precisamos criar o feed e colocar algumas informações básicas.</p>\n<pre><code class=\"language-ts\">const url = 'https://exemplo.org';\n\nconst feed = new RSS({\n  title: 'Blog de exemplo',\n  description: 'Somente um blog.',\n  site_url: url,\n  feed_url: `${url}/blog/feed.xml`,\n  language: 'pt-BR',\n  custom_elements: [\n    { icon: `${url}/img/apple-touch-icon.png` },\n  ],\n  custom_namespaces: {\n    content: 'http://purl.org/rss/1.0/modules/content/',\n    dc: 'http://purl.org/dc/elements/1.1/',\n    sy: 'http://purl.org/rss/1.0/modules/syndication/',\n  },\n});\n</code></pre>\n<p>Algumas das propriedades são bem diretas. Outras são só complementos, como a <code>custom_namespaces</code>, que permite que o XML gerado tenha <em>tags</em> extras que não estão na especificação do RSS.</p>\n<p>Como nós temos uma lista de postagens, podemos iterar sobre ela e criar cada item do feed.</p>\n<pre><code class=\"language-ts\">for (const post of posts) {\n  feed.item({\n    title: post.title,\n    guid: `${url}/post/${post.path}`,\n    url: `${url}/post/${post.path}`,\n    description: post.description,\n    date: new Date(post.created_at),\n    categories: post.category ? [post.category] : undefined,\n    custom_elements: [\n      { 'dc:creator': { _cdata: 'Fulano de Tal' } },\n    ],\n  });\n}\n</code></pre>\n<p>Agora que o feed está completo, podemos definir o cabeçalho <code>Content-Type</code>.</p>\n<pre><code class=\"language-ts\">setResponseHeader(event, 'Content-Type', 'text/xml');\n</code></pre>\n<p>Então nós podemos finalmente retornar o feed XML.</p>\n<pre><code class=\"language-ts\">return feed.xml();\n</code></pre>\n<p>Como exemplo, você pode dar uma olhada no <a href=\"https://alessandrojean.github.io/blog/feed.xml\">RSS deste site</a>.</p>\n<h2 id=\"pré-renderizando\">Pré-renderizando</h2>\n<p>Se seu site usa um <em>deploy</em> com um servidor, já está tudo certo. Mas, se usa um <em>deploy</em> como site estático, como o GitHub Pages, você precisa de uma etapa adicional.</p>\n<p>Precisamos informar ao Nuxt para também pré-renderizar esta rota do servidor durante o <em>build</em>. Isso pode ser feito editando a propriedade <code>nitro</code> no arquivo <code>nuxt.config.ts</code>.</p>\n<pre><code class=\"language-ts\">export default defineNuxtConfig({\n  nitro: {\n    prerender: {\n      routes: ['/blog/feed.xml'],\n    },\n  },\n});\n</code></pre>\n<h2 id=\"informando-leitores-de-rss-sobre-o-feed\">Informando leitores de RSS sobre o feed</h2>\n<p>Você pode por um <em>link</em> para o feed RSS na sua página, mas isso só vai funcionar se o usuário copiar e colar manualmente no seu leitor de RSS. Você pode deixar as coisas um pouco mais fáceis ao usar uma <em>tag</em> <code>&#x3C;link rel=\"alternate\"></code> no <code>&#x3C;head></code> do seu HTML.</p>\n<p>Com isso, se o usuário colar o URL de seu blog no leitor RSS, ele poderá obter o <em>link</em> do RSS automaticamente. No Nuxt, isso pode ser feito com o <em>composable</em> <code>useHead</code>.</p>\n<pre><code class=\"language-ts\">useHead({\n  link: [{ \n    rel: 'alternate', \n    type: 'application/rss+xml', \n    title: 'Feed (RSS)', \n    href: '/blog/feed.xml',\n  }],\n});\n</code></pre>\n<p>Isso não é obrigatório, mas é considerado uma boa prática.</p>\n<h2 id=\"disponibilizando-o-html-da-postagem\">Disponibilizando o HTML da postagem</h2>\n<p>O feed atual irá funcionar corretamente para todos os usuários, mas nós estamos somente disponibilizando os <em>links</em>: não há conteúdo para as postagens. A maioria dos sites faz somente isso, mas acredito que é uma boa pedida também disponibilizar o conteúdo completo da postagem, assim o usuário pode ler em seu ambiente preferido.</p>\n<p>Esta é a parte mais difícil de ser feita com Nuxt Content. No momento que escrevo este artigo, o Nuxt Content não parece ter um método que retorna sua postagem renderizada em HTML no contexto do servidor. Precisamos fazer isso <strong>manualmente</strong>.</p>\n<p>O Nuxt Content usa o <a href=\"https://npmjs.com/package/@nuxtjs/mdc\" target=\"_blank\" rel=\"noopener noreferrer external\">@nuxtjs/mdc</a> por baixo dos panos, que por sua vez usa os projetos <a href=\"https://npmjs.com/package/remark\" target=\"_blank\" rel=\"noopener noreferrer external\">remark</a> e o <a href=\"https://npmjs.com/package/rehype\" target=\"_blank\" rel=\"noopener noreferrer external\">rehype</a> do ecossistema do Unified.</p>\n<p>Se você der uma olhada na propriedade <code>post.body</code>, pode notar que é uma Árvore Sintática Abstrata (ASA) do Minimark. Poderíamos usar o pacote <a href=\"https://npmjs.com/package/minimark\" target=\"_blank\" rel=\"noopener noreferrer external\">minimark</a> para obter a <em>string</em> do Markdown, mas eu encontrei alguns problemas de conversão quando tentei. É mais fácil obter o código-fonte do arquivo Markdown diretamente.</p>\n<p>Para deixar as coisas reutilizáveis, iremos criar uma função utilitária no servidor chamada <code>markdownToHtml</code>.</p>\n<pre><code class=\"language-ts\">import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nexport async function markdownToHtml(fileName: string) {\n  const filePath = join(process.cwd(), 'content', `${fileName}.md`);\n  const markdown = await readFile(filePath, 'utf-8');\n}\n</code></pre>\n<p>A parte difícil é criar o <em>pipeline</em> da biblioteca Unified de tal modo que ele faça uma conversão similar a que o Nuxt Content faz. Podemos começar importando os <em>plugins</em>.</p>\n<pre><code class=\"language-ts\">import rehypeSlug from 'rehype-slug';\nimport rehypeStringify from 'rehype-stringify';\nimport remarkFrontmatter from 'remark-frontmatter';\nimport remarkGfm from 'remark-gfm';\nimport remarkMdc from 'remark-mdc';\nimport remarkParse from 'remark-parse';\nimport remarkRehype from 'remark-rehype';\nimport { unified } from 'unified';\n</code></pre>\n<p>E então criando o <em>pipeline</em>.</p>\n<pre><code class=\"language-ts\">const result = await unified()\n  .use(remarkParse)\n  .use(remarkFrontmatter, ['yaml'])\n  .use(remarkGfm)\n  .use(remarkMdc)\n  .use(remarkRehype)\n  .use(rehypeSlug)\n  .use(rehypeStringify)\n  .process(markdown);\n\nreturn String(result);\n</code></pre>\n<p>Isso irá fazer o <em>parse</em> do Markdown, criar uma ASA, que então irá ser convertida para HAST e processada até que seja reduzida a uma <em>string</em> final com o HTML.</p>\n<p>Para a maioria dos casos, isso irá funcionar, mas o MDC suporta componentes Vue, o que é um problema neste caso. A abordagem que eu escolhi foi converter os componentes que eu uso em componentes simples e equivalentes que usam apenas <em>tags</em> já existentes do HTML.</p>\n<p>Se você tem algum componente no seu Markdown, o <em>pipeline</em> existente irá colocá-lo no HTML como se ele fosse um elemento personalizado. Por exemplo, o <code>Note.vue</code>, chamado por <code>::note</code> no Markdown, irá ser transformado em <code>&#x3C;note>&#x3C;/note></code>, que não é um elemento existente do HTML.</p>\n<p>Podemos contornar isso usando o <em>plugin</em> <a href=\"https://npmjs.com/package/rehype-components\" target=\"_blank\" rel=\"noopener noreferrer external\">rehype-components</a>. Também iremos precisar instalar o pacote <a href=\"https://npmjs.com/package/hastscript\" target=\"_blank\" rel=\"noopener noreferrer external\">hastscript</a>.</p>\n<p>O que esse <em>plugin</em> faz é converter elementos personalizados em outros que você especifica. Podemos colocar ele no <em>pipeline</em> com o seguinte código.</p>\n<pre><code class=\"language-ts\">.use(rehypeComponents, {\n  components: {\n    'note': Note,\n  },\n})\n</code></pre>\n<p>E então criamos um componente <code>Note</code> para a substituição. Neste caso, o elemento <code>Note</code> é originalmente um <em>callout</em>. A abordagem que eu escolhi foi transformá-lo numa <code>&#x3C;div></code> com <code>&#x3C;p>&#x3C;strong>Nota:&#x3C;/strong>&#x3C;/p></code> no seu primeiro filho.</p>\n<p>O componente <code>Note</code> pode ser definido como:</p>\n<pre><code class=\"language-ts\">import { h } from 'hastscript';\nimport type { ComponentFunction } from 'rehype-components';\n\nconst Note: ComponentFunction = (_, children) => h('div', [\n  h('p', h('strong', 'Nota:')),\n  ...children,\n]);\n</code></pre>\n<p>Neste exemplo, o seguinte bloco <code>Note</code> no Markdown</p>\n<pre><code class=\"language-mdc\">::note\nEsta é uma nota\n::\n</code></pre>\n<p>Será transformada no seguinte HTML:</p>\n<pre><code class=\"language-html\">&#x3C;div>\n  &#x3C;p>&#x3C;strong>Nota:&#x3C;/strong>&#x3C;/p>\n  &#x3C;p>Esta é uma nota&#x3C;/p>\n&#x3C;/div>\n</code></pre>\n<p>Como você deve ter imaginado, você irá precisar fazer isso para cada componente Vue que você usa nos seus arquivos Markdown.</p>\n<p>Com a função completada, podemos colocar o resultado em cada item do feed:</p>\n<pre><code class=\"language-ts\">feed.item({\n  title: post.title,\n  guid: `${url}/post/${slug}`,\n  url: `${url}/post/${slug}`,\n  description: post.description,\n  date: new Date(post.created_at),\n  categories: post.category ? [post.category] : undefined,\n  custom_elements: [\n    { 'dc:creator': { _cdata: 'Fulano de Tal' } },\n    { 'content:encoded': { _cdata: await markdownToHtml(post.path) } }, // [!CODE ++]\n  ],\n});\n</code></pre>\n<p>Agora o feed RSS está completo com o conteúdo de suas postagens também.</p>\n<h2 id=\"outra-abordagem-que-eu-considerei\">Outra abordagem que eu considerei</h2>\n<p>Uma idéia alternativa que eu tive para resolver este problema foi usar um dos <em>hooks</em> do Nuxt após o <em>build</em>. Você pode escrever um <em>script</em> personalizado que faça o <em>parse</em> do HTML gerado para <code>/blog</code> para pegar os <em>links</em>, e então fazer o <em>parse</em> de cada arquivo de postagem gerado para extrair a parte do texto.</p>\n<p>Isso vai funcionar, mas dependendo dos seus elementos personalizados, você pode acabar obtendo um HTML sujo quando comparado ao fazer o <em>parse</em> do Markdown. Por exemplo, alguns dos blocos <code>&#x3C;pre></code> que eu uso aqui são customizados para incluir uma janela bonitinha ao redor com o nome do arquivo e também um botão para copiar o código. Se você não tratar esses casos, os leitores RSS podem não renderizar o conteúdo de uma maneira correta.</p>\n<h2 id=\"conclusão\">Conclusão</h2>\n<p>Seria legal se a equipe do Nuxt disponibilizasse uma maneira mais fácil de fazer isso, já que feeds RSS ainda são bem úteis e usados por algumas pessoas. Talvez com uma reescrita do módulo para usar Comark eles acabem implementando? Quem sabe.</p>","date_published":"2026-06-17T00:00:00.000Z","date_modified":"2026-06-19T18:36:00.000Z","tags":["Programação"],"language":"pt-BR"},{"id":"chegou-a-hora-de-aposentar-o-notion-como-cms","url":"https://alessandrojean.github.io/post/chegou-a-hora-de-aposentar-o-notion-como-cms","title":"Chegou a hora de aposentar o Notion como CMS","summary":"Notas sobre a migração do CMS do blog de Notion para Nuxt Content.","content_html":"<p>Há quase quatro anos atrás, eu comecei a utilizar o Notion como uma espécie de CMS para este <em>blog</em> pessoal. Durante um tempo, esta abordagem funcionou bem, mas depois comecei a me sentir bastante limitado com os blocos que o Notion oferece para escrever os textos.</p>\n<p>Apesar de facilitar um pouco o fluxo de publicação, a plataforma sempre ofereceu algumas barreiras em como eles entregam seu conteúdo pela API, seja por conta das muitas requisições necessárias para obter uma única página, ou até mesmo pelo prazo de expiração nas imagens enviadas.</p>\n<p>Como não ando escrevendo mais com tanta frequência, acredito que vale a pena voltar a manter os <em>posts</em> em formato Markdown diretamente no repositório. Isto acaba me oferecendo mais autonomia e também um controle maior da estrutura dos elementos que posso usar durante a escrita.</p>\n<p>No aspecto técnico, acabei utilizando o <a href=\"https://content.nuxt.com/\">Nuxt Content</a> para fazer o gerenciamento do conteúdo. Converter o que estava no Notion foi relativamente fácil, já que agora eles permitem exportar os documentos em formato Markdown.</p>\n<p>Uma mudança pequena, mas significativa que acabei fazendo na migração, foi remover os <em>embeds</em> de <em>tweets</em>. Já não é de hoje que não me sinto mais confortável em usar o que o Twitter se tornou, então acredito que é justo também não colocar nenhum <em>script</em> deles no meu site. Para o único post (por enquanto) que fazia uso desse tipo de conteúdo, optei por criar uma citação especial, inspirada no estilo do <a href=\"https://manualdousuario.net/\">Manual do Usuário</a>.</p>\n<blockquote><p>As citações de <em>posts</em> de redes sociais agora são assim, o que também permite maior flexibilidade já que posso citar <em>posts</em> de quaisquer sites.</p><cite>— Alessandro Jean, Autor deste site</cite></blockquote>\n<p>Removi também a seção de comentários de filmes do site. Com meu uso mais frequente do <a href=\"https://Letterboxd.com/alessandrojean\">Letterboxd</a>, creio que faz mais sentido postar meus comentários nos <a href=\"https://letterboxd.com/alessandrojean/reviews/\"><em>reviews</em></a> por lá.</p>\n<p>De coisas futuras a serem feitas, eu ainda quero melhorar um pouco a tipografia.</p>","date_published":"2026-06-14T00:00:00.000Z","date_modified":"2026-06-16T22:48:00.000Z","tags":["Programação"],"language":"pt-BR"},{"id":"redesign-do-site","url":"https://alessandrojean.github.io/post/redesign-do-site","title":"Redesign do site","summary":"Sobre o redesign do site e a nova sessão de comentários de filmes.","content_html":"<p>Após um período parado, consegui um tempo para fazer um <em>redesign</em> no site.</p>\n<p>Os posts no blog mantém a mesma estrutura, mas inaugurei uma sessão nova com comentários sobre filmes que assisto (<a href=\"https://alessandrojean.github.io/movies\">link aqui</a>). Estou longe de ser um crítico especializado em cinema, então esses comentários são descompromissados e apenas casuais, como uma espécie de diário.</p>\n<p>A página inicial foi simplificada, bem como a listagem dos posts na página do blog. Por baixo dos panos, o site foi totalmente reescrito, mas continua usando o Notion como “banco de dados” e Nuxt como o framework gerador.</p>\n<p>Provavelmente ainda devem ter alguns <em>bugs</em> e problemas aqui e ali, mas espero resolvê-los com o passar do tempo.</p>","date_published":"2025-10-03T00:00:00.000Z","date_modified":"2025-10-03T00:00:00.000Z","tags":["Cotidiano"],"language":"pt-BR"},{"id":"interagindo-com-bibliotecas-em-c-usando-codigo-em-haskell","url":"https://alessandrojean.github.io/post/interagindo-com-bibliotecas-em-c-usando-codigo-em-haskell","title":"Interagindo com bibliotecas em C usando código em Haskell","summary":"Uma breve introdução ao recurso de interoperabilidade da linguagem Haskell, mostrando a conversão entre os tipos das linguagens por meio de exemplos com as bibliotecas em C do FFmpeg e do VLC.","content_html":"<p>Uma necessidade comum em algumas aplicações específicas é poder utilizar alguma biblioteca nativa do sistema operacional diretamente, mesmo não utilizando a linguagem que ela foi escrita (normalmente C ou C++). A maioria das linguagens de programação modernas permitem que tais funções sejam utilizadas através do recurso de interoperabilidade.</p>\n<p>O objetivo deste tutorial é <mark>demonstrar como a interoperabilidade funciona no Haskell</mark> no sentido de utilizar bibliotecas existentes em C, bem como a <mark>conversão entre os tipos de ambas as linguagens</mark>. Para tal, serão mostrados alguns exemplos onde serão criadas algumas bibliotecas próprias, e outros onde serão alguns exemplos mais objetivos através da utilização do <mark>FFmpeg e VLC</mark>.</p>\n<p>O código final deste artigo está disponível no GitHub no repositório <a href=\"https://github.com/alessandrojean/haskell-c-interop\" target=\"_blank\" rel=\"noopener noreferrer external\">alessandrojean/haskell-c-interop</a>.</p>\n<div><p><strong>Nota:</strong></p><p>Este tutorial foi escrito como um dos projetos da disciplina <a href=\"https://folivetti.github.io/teaching/2024-summer-teaching-1\"><em>Desenvolvimento Guiado a Tipos</em></a> do Bacharelado em Ciência da Computação na UFABC em Abril de 2024.</p></div>\n<h2 id=\"conteúdo\">Conteúdo</h2>\n<ol>\n<li><a href=\"#defini%C3%A7%C3%A3o\">Definição</a></li>\n<li><a href=\"#criando-o-projeto\">Criando o projeto</a></li>\n<li><a href=\"#um-exemplo-introdut%C3%B3rio\">Um exemplo introdutório</a></li>\n<li><a href=\"#usando-a-biblioteca-mathh-diretamente\">Usando a biblioteca <code>math.h</code> diretamente</a></li>\n<li><a href=\"#utilizando-a-biblioteca-do-ffmpeg\">Utilizando a biblioteca do FFmpeg</a></li>\n<li><a href=\"#reproduzindo-o-arquivo-com-a-biblioteca-do-vlc\">Reproduzindo o arquivo com a biblioteca do VLC</a></li>\n<li><a href=\"#conclus%C3%A3o\">Conclusão</a></li>\n<li><a href=\"#bibliografia\">Bibliografia</a></li>\n</ol>\n<h2 id=\"definição\">Definição</h2>\n<blockquote>\n<p>Interoperabilidade é a capacidade de duas linguagens de programação diferentes interagirem de maneira nativa como parte de um mesmo sistema e operar com os mesmos tipos de estruturas de dados.</p>\n<cite>— <a href=\"https://en.wikipedia.org/wiki/Language_interoperability\">Wikipédia</a> (em inglês)</cite>\n</blockquote>\n<p>O Haskell oferece a interoperabilidade através do método de <em>interfaces de funções externas</em> (FFI), o que permite que nosso código em Haskell possa chamar funções escritas em C ou C++. Essas interfaces permitem que sejam construídas bibliotecas em Haskell que ofereçam funcionalidades de bibliotecas em C, facilitando o acesso e inclusive permitindo um estilo que seja mais idiomático para o Haskell, com (mas não limitado somente) a conversão para o paradigma funcional (<a href=\"https://en.wikipedia.org/wiki/Language_interoperability\">Wikipédia</a>). Tais bibliotecas são comumente conhecidas como <em>wrappers</em> ou <em>bindings</em>.</p>\n<h2 id=\"criando-o-projeto\">Criando o projeto</h2>\n<p>Similar ao tutorial anterior, iremos utilizar o <a href=\"https://docs.haskellstack.org/en/stable/\">Haskell Stack</a> para a criação do projeto. Todo o pacote de desenvolvimento foi instalado através do <a href=\"https://www.haskell.org/ghcup/\">GHCup</a>. Para iniciar a pasta do projeto, utilizamos o comando:</p>\n<pre><code class=\"language-console\">$ stack new c-interop simple\n</code></pre>\n<p>Após as dependências e o <em>template</em> terem sido baixados, podemos abrir a pasta em um editor de código com suporte a Haskell e C/C++, como o Visual Studio Code.</p>\n<h2 id=\"um-exemplo-introdutório\">Um exemplo introdutório</h2>\n<p>Começando com um exemplo simples, utilizaremos uma biblioteca própria que só vai ter uma função. Para este exemplo, vamos criar uma biblioteca de matemática que irá exportar a função exponencial (<code class=\"language-math math-inline\">e ^ x</code>) definida como <code>my_exp</code>.</p>\n<p>Primeiramente, precisaremos criar o arquivo de cabeçalho que irá definir todas as funções que nossa biblioteca exportará para o uso de outros programas.</p>\n<pre><code class=\"language-c\">#ifndef MY_MATH_H\n#define MY_MATH_H\n\nextern double my_exp(double);\n\n#endif // my_math\n</code></pre>\n<p>A diferença significativa deste código para o que é comumente utilizado em arquivos de cabeçalho em C é a utilização da palavra-chave <code>extern</code> antes da definição da função. Ela que irá indicar quais funções ficarão acessíveis por outros programas.</p>\n<p>Com o cabeçalho pronto, podemos implementar a nossa função em um arquivo separado.</p>\n<pre><code class=\"language-c\">#include &#x3C;math.h>\n#include \"my_math.h\"\n\n#define EULER_NUM 2.71828\n\ndouble my_exp(double number) {\n  return pow(EULER_NUM, number);\n}\n</code></pre>\n<p>Este código não tem muito segredo: utilizamos a biblioteca <code>libmath</code> do C para usar somente a função <code>pow</code>, permitindo que façamos a operação <code>e ^ number</code>. Poderíamos implementá-la manualmente, mas para fins de simplificação do código, é mais rápido utilizar a função oficial.</p>\n<h3 id=\"compilando-a-biblioteca-de-modo-dinâmico\">Compilando a biblioteca de modo dinâmico</h3>\n<p>Para poder compilar a biblioteca, utilizaremos um fluxo em duas etapas: primeiro criaremos o objeto (<code>my_math.o</code>) para aí sim poder criar a biblioteca (<code>libmy_math.so</code>).</p>\n<p>Para criar o objeto, utilizaremos o <code>gcc</code> do mesmo modo que seria utilizado para compilar um arquivo simples qualquer. A diferença, é que utilizaremos a <em>flag</em> <code>-fpic</code>, ideal para o uso em bibliotecas, pois <a href=\"https://stackoverflow.com/a/5311538\">evita alguns possíveis conflitos</a> com outras bibliotecas.</p>\n<pre><code class=\"language-console\">$ gcc -c -Wall -Werror -fpic my_math.c\n</code></pre>\n<p>O comando irá produzir um arquivo <code>my_math.o</code>, que será utilizado em sequência no <code>gcc</code> para criar a biblioteca com o comando a seguir.</p>\n<pre><code class=\"language-console\">$ gcc -shared -o ../libs/libmy_math.so my_math.o\n</code></pre>\n<p>Com isso, temos a biblioteca compilada, podemos utilizá-la no nosso código em Haskell.</p>\n<h3 id=\"incluindo-a-biblioteca\">Incluindo a biblioteca</h3>\n<p>Para poder compilar o projeto com o <code>stack</code>, é necessário adicionar algumas <em>flags</em> ao compilador do Haskell, o <code>ghc</code>. No arquivo <code>c-interop.cabal</code> na raiz do projeto, é necessário incluir as seguintes linhas na propriedade <code>ghc-options</code>:</p>\n<pre><code class=\"language-cabal\">ghc-options: -- Demais flags omitidas.\n             -- Define um diretório extra para as bibliotecas. -- [!code ++]\n             -L./libs                                          -- [!code ++]\n             -- Define um diretório extra para os cabeçalhos.  -- [!code ++]\n             -I./my_math                                       -- [!code ++]\n             -- Utiliza a biblioteca my_math na compilação.    -- [!code ++]\n             -lmy_math                                         -- [!code ++]\n</code></pre>\n<p>Temos este pequeno trabalho a ser feito por conta dos arquivos não estarem em diretórios comuns do sistema operacional, mas veremos no próximos exemplo que não será necessário especificar estes diretórios ao utilizar dependências instaladas globalmente de maneira direta.</p>\n<h3 id=\"utilizando-a-função\">Utilizando a função</h3>\n<p>Agora finalmente poderemos modificar o código em Haskell. Primeiramente, é necessário usar duas diretivas de linguagem para habilitar a chamada de funções externas, bem como importar os tipos primitivos do C.</p>\n<pre><code class=\"language-haskell\">{-# LANGUAGE ForeignFunctionInterface, CApiFFI #-}\nmodule Main (main) where\n\nimport Foreign.C.Types\n</code></pre>\n<p>Os tipos primitivos nos possibilitarão uma melhor definição das funções externas até por conta das diferenças de tamanho entre os tipos do Haskell e do C, que também podem ter diferenças entre os processadores e sistemas operacionais. Tendo feito as importações, podemos definir no arquivo o acesso a função <code>my_exp</code> da nossa biblioteca utilizando o código a seguir.</p>\n<pre><code class=\"language-haskell\">foreign import capi \"my_math.h my_exp\" c_myExp :: CDouble -> CDouble\n</code></pre>\n<p>Essa importação é especial, pois explicita que a função irá vir de uma dependência externa, bem como define que será feito uma chamada a uma função em C, especificada juntamente ao cabeçalho da função. Como o compilador do Haskell não consegue prever a tipagem da função, é necessário deixar a tipagem explícita.</p>\n<p>Pode-se utilizar tanto a palavra-chave <code>capi</code> quando <code>ccall</code>, com a diferença que ao utilizar <code>capi</code>, os tipos serão checados com o compilador do C na hora de fazer o <em>link</em> entre as duas linguagens. Para um ambiente mais seguro em relação a tipos, torna-se interessante utilizar o <code>capi</code>, que inclusive é o recomendado pela <a href=\"https://downloads.haskell.org/ghc/9.0.1/docs/html/users_guide/exts/ffi.html?highlight=capiffi#extension-CApiFFI\">documentação do Haskell</a> atualmente.</p>\n<p>É uma convenção do Haskell utilizar o prefixo <code>c_</code> para indicar funções e/ou constantes que sejam de bibliotecas externas em C, por isso ela é nomeada assim. Após a importação, podemos utilizá-la como uma função qualquer em Haskell, com a diferença que será necessário lidar com alguns tipos especiais, como o <code>CString</code> e <code>Ptr</code> ao utilizá-los.</p>\n<pre><code class=\"language-haskell\">main :: IO ()\nmain = do\n  print $ c_myExp 5\n</code></pre>\n<h3 id=\"compilando-o-código-haskell-e-executando\">Compilando o código Haskell e executando</h3>\n<p>Para compilar utilizaremos o <code>stack</code> novamente, mas com algumas diferenças.</p>\n<pre><code class=\"language-console\">$ stack build\n</code></pre>\n<p>Após a compilação ter sido concluída, podemos executar o arquivo.</p>\n<pre><code class=\"language-console\">$ LD_LIBRARY_PATH=./libs stack exec c-interop\n148.41265995084171\n</code></pre>\n<p>Aqui tem uma diferença no comando: é necessário especificar o local das bibliotecas externas nesse caso por conta de não ser o comum do sistema operacional. Como as bibliotecas são carregadas em tempo de execução, o programa precisa saber novamente onde as encontrar.</p>\n<p>Ao executar, obtemos o resultado correto de <code>148.41265995084171</code> impresso na tela.</p>\n<h2 id=\"usando-a-biblioteca-mathh-diretamente\">Usando a biblioteca <code>math.h</code> diretamente</h2>\n<p>Na seção anterior, criamos uma biblioteca nossa e chamamos a função no código em Haskell. Nesta, iremos utilizar uma biblioteca da linguagem C que já está disponível globalmente no sistema operacional, onde teremos diferenças e simplificações na hora de compilação e execução.</p>\n<p>A principal diferença é que não precisaremos modificar as <em>flags</em> de compilação do <code>ghc</code> no arquivo <code>c-interop.cabal</code> para incluir os diretórios dos cabeçalhos e da nossa biblioteca, mas sim apenas será necessário a utilização da <code>libmath</code> do próprio C. As linhas podem ser comentadas ou removidas neste caso, pois não serão mais utilizadas.</p>\n<pre><code class=\"language-cabal\">ghc-options: -- Demais flags omitidas.\n             -L./libs     -- [!code --]\n             -I./my_math  -- [!code --]\n             -lmy_math    -- [!code --]\n             -lmath       -- [!code ++]\n</code></pre>\n<p>De maneira similar, iremos importar a função <code>exp</code> da biblioteca <code>math.h</code> no código Haskell.</p>\n<pre><code class=\"language-haskell\">foreign import capi \"math.h exp\" c_exp :: CDouble -> CDouble\n\nmain :: IO ()\nmain = do\n  print $ c_exp 5\n</code></pre>\n<p>Para compilar e executar, basta usarmos os comandos padrões do <code>stack</code>:</p>\n<pre><code class=\"language-console\">$ stack build\n$ stack exec c-interop\n148.4131591025766\n</code></pre>\n<p>Veja como não é necessário também definir a variável de ambiente <code>LD_LIBRARY_PATH</code> na hora de execução, visto que o programa sabe onde encontrar a biblioteca <code>libmath.so</code> na pasta padrão do sistema operacional, pois é uma biblioteca instalada globalmente.</p>\n<h2 id=\"utilizando-a-biblioteca-do-ffmpeg\">Utilizando a biblioteca do FFmpeg</h2>\n<p>Nesta seção, utilizaremos a API do <a href=\"https://ffmpeg.org/\">FFmpeg</a> para criar um programa simples que imprime os metadados de um arquivo suportado pela ferramenta.</p>\n<p>O FFmpeg é uma ferramenta de código aberto vastamente utilizada por diversos sistemas de multimídia, programado em C e Assembly para uma melhor performance. Ela permite a conversão entre formatos (<code>ffmpeg</code>), visualização de metadados (<code>ffprobe</code>) e reprodução de arquivos (<code>ffplay</code>). Todos os grandes <em>players</em> no mercado como YouTube e Netflix utilizam-o em algum dado momento nos seus fluxos.</p>\n<figure><img src=\"https://alessandrojean.github.io/posts/2024/2024-05-08-interagindo-com-bibliotecas-em-c-usando-codigo-em-haskell/xkcd.jpeg\" alt=\"Quadrinho do XKCD.\"><figcaption>Um quadrinho que brinca sobre a dependência de vários sistemas com o FFmpeg.\n</figcaption></figure>\n<p>O intuito deste exemplo é construir uma espécie simplificada da ferramenta <code>ffprobe</code> disponível pelo FFMpeg. O <code>ffprobe</code> é um utilitário que exibe informações de um arquivo passado pela linha de comando. Ao usar em um arquivo de exemplo, podemos observar a saída abaixo.</p>\n<pre><code class=\"language-console\">$ ffprobe big_buck_bunny.mp4\nffprobe version 6.0-6ubuntu1 Copyright (c) 2007-2023 the FFmpeg developers\nInput #0, mov,mp4,m4a,3gp,3g2,mj2, from '/home/alessandro/Downloads/bbb_sunflower_1080p_30fps_normal.mp4':\n  Metadata:\n    major_brand     : isom\n    minor_version   : 1\n    compatible_brands: isomavc1\n    creation_time   : 2013-12-16T17:44:39.000000Z\n    title           : Big Buck Bunny, Sunflower version\n    artist          : Blender Foundation 2008, Janus Bager Kristensen 2013\n    comment         : Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net\n    genre           : Animation\n    composer        : Sacha Goedegebure\n  Duration: 00:10:34.60, start: 0.000000, bitrate: 3481 kb/s\n  Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1920x1080 [SAR 1:1 DAR 16:9], 2998 kb/s, 30 fps, 30 tbr, 30k tbn (default)\n    Metadata:\n      creation_time   : 2013-12-16T17:44:39.000000Z\n      handler_name    : GPAC ISO Video Handler\n      vendor_id       : [0][0][0][0]\n  Stream #0:1[0x2](und): Audio: mp3 (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 160 kb/s (default)\n    Metadata:\n      creation_time   : 2013-12-16T17:44:42.000000Z\n      handler_name    : GPAC ISO Audio Handler\n      vendor_id       : [0][0][0][0]\n  Stream #0:2[0x3](und): Audio: ac3 (ac-3 / 0x332D6361), 48000 Hz, 5.1(side), fltp, 320 kb/s (default)\n    Metadata:\n      creation_time   : 2013-12-16T17:44:42.000000Z\n      handler_name    : GPAC ISO Audio Handler\n      vendor_id       : [0][0][0][0]\n    Side data:\n      audio service type: main\n</code></pre>\n<p>Para utilizar a API do FFmpeg, iremos precisar da <code>libavformat</code> e <code>libavutil</code> instaladas. Também iremos utilizar a <code>libavcodec</code>, mas ela já vem inclusa na instalação de ambas as bibliotecas, que estão disponíveis nos repositórios das distribuições Linux. Como exemplo do Ubuntu, elas estão disponíveis no <code>apt</code> e podem ser instaladas utilizando o comando abaixo.</p>\n<pre><code class=\"language-console\">$ sudo apt install libavformat-dev libavutil-dev\n</code></pre>\n<p>Utilizamos as variantes com o sufixo <code>-dev</code> pois queremos que os arquivos de cabeçalho também sejam baixados e disponibilizados para serem usados em programas que vamos escrever.</p>\n<p>O código deste exemplo é baseado em um exemplo oficial do FFmpeg que pode ser visto na íntegra <a href=\"https://github.com/FFmpeg/FFmpeg/blob/08781ebe1aabe99b94e2ee949ca0c95c1443c756/doc/examples/show_metadata.c\">neste link</a> no repositório do GitHub do projeto. No código, o arquivo passado como primeiro argumento na linha de comando é aberto como um <em>input</em>, onde seus metadados são processados, iterados, e exibidos na tela.</p>\n<p>Para poder replicar este código em Haskell, criaremos uma interface escrita em C para intermediar (e facilitar) a comunicação entre nosso programa em Haskell e a biblioteca do FFmpeg. Esse programa intermediador irá expor duas funções para ler as informações do arquivo e uma <code>struct</code> especial com menos campos para facilitar a declaração dos tipos no Haskell.</p>\n<h3 id=\"criando-o-arquivo-de-cabeçalho\">Criando o arquivo de cabeçalho</h3>\n<p>De maneira similar ao exemplo anterior, iremos criar um arquivo de cabeçalho <code>ffmpeg.h</code>.</p>\n<pre><code class=\"language-c\">#ifndef FFMPEG_H\n#define FFMPEG_H\n\n#include &#x3C;libavformat/avformat.h>\n#include &#x3C;libavutil/dict.h>\n\nstruct FfmpegInput {\n  AVDictionary * metadata;\n  AVDictionary * format_metadata;\n  AVDictionary ** streams_metadata;\n  int nb_streams;\n  AVFormatContext * context;\n};\n\ntypedef struct FfmpegInput FfmpegInput;\n\nextern FfmpegInput * load_input(const char *);\nextern void free_input(FfmpegInput *);\n\n#endif // ffmpeg_h\n</code></pre>\n<p>Explicando resumidamente, a <code>struct FfmpegInput</code> irá guardar três informações: um dicionário com os metadados do arquivo (<code>metadata</code>), um segundo dicionário com os metadados do formato do arquivo (<code>format_metadata</code>) e o contexto original da biblioteca, que será utilizado apenas para liberar a memória no final do uso do programa e não será acessado no código em Haskell.</p>\n<h3 id=\"carregando-um-arquivo-e-lendo-os-metadados\">Carregando um arquivo e lendo os metadados</h3>\n<p>Partindo para a implementação da primeira função, iremos utilizar uma abordagem similar a do exemplo no repositório do FFmpeg.</p>\n<pre><code class=\"language-c\">#include &#x3C;stdio.h>\n#include &#x3C;libavformat/avformat.h>\n#include &#x3C;libavutil/dict.h>\n#include &#x3C;libavcodec/codec.h>\n#include &#x3C;libavcodec/codec_desc.h>\n#include \"ffmpeg.h\"\n\nFfmpegInput * load_input(const char * file_name) {\n  AVFormatContext * format_context = NULL;\n\n  // Tenta abrir o arquivo, retorna um inteiro > 0 se deu algum erro.\n  if (avformat_open_input(&#x26;format_context, file_name, NULL, NULL)) {\n    return NULL;\n  }\n\n  // Tenta ver se há alguma trilha de vídeo e/ou áudio no arquivo.\n  // Retorna valores negativos se deu algum erro.\n  if (avformat_find_stream_info(format_context, NULL) &#x3C; 0) {\n    av_log(NULL, AV_LOG_ERROR, \"Cannot find stream information\\n\");\n    return NULL;\n  }\n\n  FfmpegInput * input = malloc(sizeof (FfmpegInput));\n\n  if (input == NULL) {\n    fprintf(stderr, \"Failed to allocate necessary bytes for input\\n\");\n    return NULL;\n  }\n\n  // Cria um dicionário que irá ter alguns metadados do arquivo.\n  AVDictionary * metadata = NULL;\n  av_dict_copy(&#x26;metadata, format_context->metadata, AV_DICT_DONT_OVERWRITE);\n  create_file_dict(&#x26;metadata, format_context);  \n\n  // Cria um dicionário que irá ter alguns metadados do formato.\n  AVDictionary * format_metadata = NULL;\n  create_format_dict(&#x26;format_metadata, format_context->iformat);\n\n  // Cria um vetor de dicionários que irão ter alguns metadados das streams.\n  AVDictionary ** streams_metadata = calloc(format_context->nb_streams, sizeof(AVDictionary *));\n  create_streams_dict(streams_metadata, format_context);\n\n  input->context = format_context;\n  input->metadata = metadata;\n  input->format_metadata = format_metadata;\n  input->nb_streams = format_context->nb_streams;\n  input->streams_metadata = streams_metadata;  \n\n  return input;\n}\n</code></pre>\n<p>Como explicado anteriormente, o código é baseado num exemplo oficial. A maior diferença é que estamos obtendo algumas informações da biblioteca e passando-as para um tipo de nosso domínio que é bem mais simplificado. Isso é feito porque os tipos da biblioteca do FFmpeg possuem macros e condicionais nas declarações, tornando a declaração no Haskell mais complexa e necessitando de ferramentas externas no fluxo de compilação.</p>\n<p>A função <code>create_format_dict</code> chamada no meio do código é apenas uma função auxiliar que preenche o dicionário com alguns atributos básicos do formato.</p>\n<pre><code class=\"language-c\">void create_format_dict(AVDictionary ** dict, const AVInputFormat * iformat) {\n  av_dict_set(dict, \"name\", iformat->name, AV_DICT_DONT_OVERWRITE);\n  av_dict_set(dict, \"long_name\", iformat->long_name, AV_DICT_DONT_OVERWRITE);\n  av_dict_set(dict, \"extensions\", iformat->extensions, AV_DICT_DONT_OVERWRITE);\n  av_dict_set(dict, \"mime_type\", iformat->mime_type, AV_DICT_DONT_OVERWRITE);\n}\n</code></pre>\n<p>De modo similar, as funções <code>create_streams_dict</code> e <code>create_file_dict</code> criam dicionários que conterão metadados das <em>streams</em> e do arquivo, respectivamente. O código delas foi omitido por simplificação, mas está disponível no repositório no GitHub do tutorial.</p>\n<p>Não é necessário alocar a memória para o tipo <code>AvDictionary</code> explicitamente, pois o FFmpeg já faz isso por padrão na primeira chamada da função <code>av_dict_set</code>, segundo a documentação.</p>\n<blockquote>\n<p><code>pm</code> — Pointer to a pointer to a dictionary struct. If <code>*pm</code> is <code>NULL</code> a dictionary struct is allocated and put in <code>*pm</code>.</p>\n</blockquote>\n<h3 id=\"liberando-a-memória\">Liberando a memória</h3>\n<p>A função de liberação de memória é bem simples e concisa.</p>\n<pre><code class=\"language-c\">void free_input(FfmpegInput * input) {\n  avformat_free_context(input->context);\n  av_dict_free(&#x26;input->format_metadata);\n  av_dict_free(&#x26;input->metadata);\n\n  for (int i = 0; i &#x3C; input->nb_streams; i++) {\n    av_dict_free(&#x26;input->streams_metadata[i]);\n  }\n\n  free(input);\n}\n</code></pre>\n<p>Aqui são utilizados duas funções específicas do FFmpeg para a liberação da memória tanto do contexto quanto do dicionário. A biblioteca lida com todos os campos internos de ambas as <em>structs</em>. Por fim, é necessário liberar o espaço de memória da nossa <em>struct</em> também.</p>\n<h3 id=\"compilando-a-biblioteca-intermediária\">Compilando a biblioteca intermediária</h3>\n<p>Idêntico ao primeiro exemplo, vamos criar uma biblioteca <code>libffmpeg.so</code> para ser acessada no nosso código em Haskell.</p>\n<pre><code class=\"language-console\">$ gcc -c -Wall -Werror -fpic -lavformat -lavutil -lavcodec ffmpeg.c\n$ gcc -shared -o ../libs/libffmpeg.so ffmpeg.o\n</code></pre>\n<p>Também é necessário incluir a biblioteca nas <em>flags</em> do GHC.</p>\n<pre><code class=\"language-cabal\">ghc-options: -- Demais flags omitidas\n             -L./libs    -- [!code ++]\n             -I./ffmpeg  -- [!code ++]\n             -lffmpeg    -- [!code ++]\n             -lavutil    -- [!code ++]\n             -lavformat  -- [!code ++]\n             -lavcodec   -- [!code ++]\n</code></pre>\n<h3 id=\"instalando-algumas-dependências-externas-do-hackage\">Instalando algumas dependências externas do Hackage</h3>\n<p>Para este segundo exemplo precisaremos usar duas bibliotecas externas disponíveis no Hackage. Para definir a dependência, precisamos primeiramente informar ao <code>stack</code> sobre elas, editando o arquivo de metadados do <code>stack</code> do nosso projeto.</p>\n<pre><code class=\"language-yaml\">extra-deps:\n  - c-storable-deriving-0.1.3 # [!code ++]\n  - pretty-terminal-0.1.0.0   # [!code ++]\n</code></pre>\n<p>Tendo informado ao <code>stack</code>, podemos declarar a dependência ao <code>cabal</code> também.</p>\n<pre><code class=\"language-cabal\">build-depends: base >= 4.7 &#x26;&#x26; &#x3C; 5,\n               containers,          -- [!code ++]\n               c-storable-deriving, -- [!code ++]\n               pretty-terminal      -- [!code ++]\n</code></pre>\n<p>A biblioteca <code>c-storable-deriving</code> nos ajudará a criar a conversão dos tipos do C e Haskell mais facilmente no código final, enquanto a <code>pretty-terminal</code> irá ajudar a deixar a saída no terminal mais elegante com algumas cores. O <code>containers</code> por sua vez é declarado por conta do tipo <code>Map</code>, que será utilizado para acessar os dicionários do FFmpeg mais facilmente após uma conversão.</p>\n<h3 id=\"o-módulo-ffmpeg-no-haskell\">O módulo <code>FFmpeg</code> no Haskell</h3>\n<p>Neste módulo iremos utilizar quatro diretivas de linguagem. Além das já utilizadas anteriormente <code>ForeignFunctionInterface</code> e <code>CApiFFI</code>, precisaremos de duas relacionadas a <code>Generics</code>: <code>DeriveGeneric</code> e <code>DeriveAnyClass</code>.</p>\n<pre><code class=\"language-haskell\">{-# LANGUAGE ForeignFunctionInterface, CApiFFI #-}\n{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}\nmodule FFmpeg where\n\nimport Control.Monad (forM_, join)\nimport Data.Maybe\nimport Foreign\nimport Foreign.C.Types\nimport Foreign.C (CString, withCString, peekCString)\nimport Foreign.CStorable (CStorable(..))\nimport GHC.Generics\nimport Data.Map (Map)\nimport qualified Data.Map as Map\n\nimport PrettyPrint\n</code></pre>\n<h3 id=\"definindo-os-tipos-da-biblioteca-intermediária-no-haskell\">Definindo os tipos da biblioteca intermediária no Haskell</h3>\n<p>Iremos começar replicando os tipos próprios do FFmpeg, mais especificamente os dicionários.</p>\n<p>O tipo <code>AVDictionary</code> de <code>libavutil/dict.h</code> é uma <em>struct</em> vazia, como pode-se observar no código da biblioteca no trecho abaixo.</p>\n<pre><code class=\"language-c\">typedef struct AVDictionary AVDictionary;\n</code></pre>\n<p>Como a <em>struct</em> não tem campos, podemos representá-la no Haskell como um ponteiro para <code>void</code> no C (<code>void *</code>). Tal representação é feita usando o tipo <code>Ptr ()</code>, que vem da biblioteca <code>Foreign</code>.</p>\n<pre><code class=\"language-haskell\">type AvDictionary = Ptr ()\n</code></pre>\n<p>O tipo <code>AVDictionaryEntry</code> de <code>libavutil/dict.h</code> por sua vez, é uma <em>struct</em> simples com apenas dois campos: <code>key</code> e <code>value</code>, ambos do tipo <code>char *</code>. Por possuir dois campos, podemos representar esta <em>struct</em> com um <em>record</em> semelhante.</p>\n<pre><code class=\"language-haskell\">data AvDictionaryEntry = AvDictionaryEntry\n  { avDictKey :: CString\n  , avDictValue :: CString\n  }\n</code></pre>\n<p>Para poder fazer a conversão entre as linguagens, as estruturas de dados precisarão providenciar uma instância da classe <code>Storable</code>. Essa instância pode ser implementada explicitamente, mas para poder implementar, é necessário ter conhecimento nos tamanhos dos tipos e de seus <em>paddings</em>, caso existam, nas estruturas do C, que podem ser consultados na tabela abaixo.</p>\n<table>\n<thead>\n<tr>\n<th align=\"left\">Tipo</th>\n<th align=\"center\">Arquitetura</th>\n<th align=\"right\">Tamanho (<em>byte</em>)</th>\n<th align=\"right\"><em>Padding</em> (<em>byte</em>)</th>\n<th align=\"right\">Total (<em>byte</em>)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td align=\"left\"><code>char</code></td>\n<td align=\"center\">-</td>\n<td align=\"right\">1</td>\n<td align=\"right\">3</td>\n<td align=\"right\">4</td>\n</tr>\n<tr>\n<td align=\"left\"><code>int</code></td>\n<td align=\"center\">-</td>\n<td align=\"right\">16</td>\n<td align=\"right\">0</td>\n<td align=\"right\">16</td>\n</tr>\n<tr>\n<td align=\"left\"><code>long</code></td>\n<td align=\"center\">-</td>\n<td align=\"right\">32</td>\n<td align=\"right\">0</td>\n<td align=\"right\">32</td>\n</tr>\n<tr>\n<td align=\"left\"><code>float</code></td>\n<td align=\"center\">-</td>\n<td align=\"right\">4</td>\n<td align=\"right\">0</td>\n<td align=\"right\">4</td>\n</tr>\n<tr>\n<td align=\"left\"><code>double</code></td>\n<td align=\"center\">-</td>\n<td align=\"right\">8</td>\n<td align=\"right\">0</td>\n<td align=\"right\">8</td>\n</tr>\n<tr>\n<td align=\"left\"><code>void *</code></td>\n<td align=\"center\">32 <em>bits</em></td>\n<td align=\"right\">4</td>\n<td align=\"right\">0</td>\n<td align=\"right\">4</td>\n</tr>\n<tr>\n<td align=\"left\"><code>void *</code></td>\n<td align=\"center\">64 <em>bits</em></td>\n<td align=\"right\">8</td>\n<td align=\"right\">0</td>\n<td align=\"right\">8</td>\n</tr>\n</tbody>\n</table>\n<p>Simplificando bastante e considerando uma arquitetura de 32 <em>bits</em>, o <em>padding</em> é usado em estruturas no C para alinhar seus membros internos aos limites de endereço naturais, ou seja, fazendo com que os endereços na memória de cada membro seja um múltiplo de 4. O <em>padding</em> é adicionado por padrão pelo compilador do C, mas pode ser desativado.</p>\n<div><p><strong>Dica:</strong></p><p><a href=\"https://stackoverflow.com/q/4306186\">Esta pergunta</a> no StackOverflow possui algumas explicações interessantes sobre como o <em>padding</em> e <em>packing</em> funcionam na linguagem C.</p></div>\n<p>Tendo essas informações em mente, podemos implementar nossa própria instância de <code>Storable</code> para nosso tipo <code>AvDictionaryEntry</code>.</p>\n<pre><code class=\"language-haskell\">instance Storable AvDictionaryEntry where\n  alignment _ = 8\n  sizeOf _ = 16\n  peek ptr = AvDictionaryEntry\n    &#x3C;$> peekByteOff ptr 0\n    &#x3C;*> peekByteOff ptr 8\n  poke ptr (AvDictionaryEntry k v) = do\n    pokeByteOff ptr 0 k\n    pokeByteOff ptr 8 v\n</code></pre>\n<p>Explicando detalhadamente cada uma das funções de <code>Storable</code>, temos:</p>\n<ol>\n<li><code>alignment</code> deve retornar o alinhamento da estrutura. É representado como o mínimo múltiplo comum entre todos os alinhamentos de seus tipos internos. Como na arquitetura 64 <em>bits</em> temos que um ponteiro representa 8 <em>bytes</em>, e o nosso tipo contém duas variáveis do tipo ponteiro, temos <code>mmc(8, 8) = 8</code>.</li>\n<li><code>sizeOf</code> deve retornar o tamanho total em <em>bytes</em> ocupado pela estrutura. Como temos dois ponteiros, temos o total de 16 <em>bytes</em>.</li>\n<li><code>peek</code> é a função que fará a leitura dos dados do ponteiro <code>ptr</code> e irá criar a instância do tipo do Haskell. Para tal feito, podemos utilizar a função <code>peekByteOff</code>, onde também é necessário informar o <em>offset</em> inicial da leitura. Esse <em>offset</em> é calculado usando o tamanho do tipo anterior na estrutura e seu possível <em>padding</em> (como acontece em variáveis do tipo <code>char</code>).</li>\n<li><code>poke</code> é a função que irá escrever os dados no endereço de memória armazenado no ponteiro. De maneira similar ao <code>peek</code>, usamos a função <code>pokeByteOff</code> também passando o <em>offset</em> e seguindo a ordem da declaração do tipo.</li>\n</ol>\n<p>Apesar da implementação de <code>Storable</code> para nosso tipo ser relativamente fácil e direta, ainda assim ela requer do programador um conhecimento prévio nos tipos do C para saber seus tamanhos, e ainda apresenta algumas dificuldades específicas como ter que lidar com o tamanho dos ponteiros caso esteja usando uma arquitetura diferente. Para evitar possíveis implementações erradas caso algum cálculo esteja incorreto, podemos utilizar a biblioteca <code>c-storable-deriving</code>.</p>\n<p>Para utilizar tal biblioteca, faremos com que o nosso tipo derive os tipos <code>Generic</code> e <code>CStorable</code>.</p>\n<pre><code class=\"language-haskell\">data AvDictionaryEntry = AvDictionaryEntry\n  { avDictKey :: CString\n  , avDictValue :: CString\n  }                               -- [!code --]\n  } deriving (Generic, CStorable) -- [!code ++]\n</code></pre>\n<p>É necessário que nosso tipo seja totalmente compatível com <code>CStorable</code>, ou seja, os tipos internos precisam ter uma implementação de <code>Storable</code> também, por isso utilizamos os tipos do C <code>CString</code> no lugar de <code>String</code> do Haskell. Agora, podemos simplificar a implementação de <code>Storable</code> usando funções da biblioteca.</p>\n<pre><code class=\"language-haskell\">instance Storable AvDictionaryEntry where\n  alignment _ = 8                       -- [!code --]\n  alignment = cAlignment                -- [!code ++]\n  sizeOf _ = 16                         -- [!code --]\n  sizeOf = cSizeOf                      -- [!code ++]\n  peek ptr = AvDictionaryEntry          -- [!code --]\n    &#x3C;$> peekByteOff ptr 0               -- [!code --]\n    &#x3C;*> peekByteOff ptr 8               -- [!code --]\n  peek = cPeek                          -- [!code ++]\n  poke ptr (AvDictionaryEntry k v) = do -- [!code --]\n    pokeByteOff ptr 0 k                 -- [!code --]\n    pokeByteOff ptr 8 v                 -- [!code --]\n  poke = cPoke                          -- [!code ++]\n</code></pre>\n<p>As funções funcionam da exata mesma maneira da nossa implementação explicita anterior, mas será criada automaticamente em tempo de compilação, além de prevenir erros de cálculo e não mais depender do tamanho dos tipos do C.</p>\n<div><p><strong>Nota:</strong></p><p>A documentação da biblioteca pode ser encontrada no <a href=\"https://hackage.haskell.org/package/c-storable-deriving-0.1.3/docs/Foreign-CStorable.html\">Hackage</a>.</p></div>\n<p>De modo similar, podemos agora definir o tipo que representará a <code>struct FfmpegInput</code>, já usando a implementação com a biblioteca <code>c-storable-deriving</code>.</p>\n<pre><code class=\"language-haskell\">data FfmpegInput = FfmpegInput\n  { ffMetadata :: AvDictionary\n  , ffFormat :: AvDictionary\n  , ffStreamsMetadata :: Ptr AvDictionary\n  , ffStreamsNb :: CInt\n  , ffContext :: Ptr ()\n  } deriving (Generic, CStorable)\n\ninstance Storable FfmpegInput where\n  peek = cPeek\n  poke = cPoke\n  alignment = cAlignment\n  sizeOf = cSizeOf\n</code></pre>\n<h3 id=\"importando-as-funções-externas-do-c\">Importando as funções externas do C</h3>\n<p>No nosso código em Haskell utilizaremos as duas funções expostas pela nossa biblioteca intermediária, bem como a <code>av_dict_get</code> para poder ler os valores nos dicionários.</p>\n<pre><code class=\"language-haskell\">foreign import capi \"ffmpeg.h load_input\"\n  c_loadInput :: CString -> IO (Ptr FfmpegInput)\n\nforeign import capi \"ffmpeg.h &#x26;free_input\"\n  c_freeInput :: FunPtr (Ptr FfmpegInput -> IO ())\n\nforeign import capi \"libavutil/dict.h av_dict_get\"\n  c_avDictGet :: AvDictionary -> CString -> Ptr AvDictionaryEntry -> CInt -> IO (Ptr AvDictionaryEntry)\n\nforeign import capi \"libavutil/dict.h value AV_DICT_IGNORE_SUFFIX\"\n  c_avDictIgnoreSuffix :: CInt\n</code></pre>\n<p>Note que na declaração de tipos sempre utilizamos os tipos do C que vem de <code>Foreign.C.Types</code> para representar a diferença com os tipos do Haskell. Também são utilizados ponteiros para representar as funções exatamente como são declaradas no C.</p>\n<p>Aqui cabe uma atenção especial a função <code>c_freeInput</code>. Ela é importada e declarada de uma maneira diferente das outras porque será utilizada em conjunto posteriormente com o tipo <code>ForeignPtr</code>. O Haskell é conhecido por ter um ótimo compilador e fazer um bom uso da memória, entretanto se tratando de ponteiros externos, a linguagem não tem como saber quando é necessário liberar a memória dessas estruturas, cabendo a nós liberarmos a memória manualmente. Porém, o Haskell nos disponibiliza o <code>ForeignPtr</code> justamente para essas situações: são ponteiros especiais que já liberam a memória automaticamente quando não há mais nenhum uso da variável, similar ao que acontece com classes que implementam o <code>Closeable</code> no Java.</p>\n<p>A última importação, por sua vez, utiliza o modificador especial <code>value</code>, que permite que sejam importados valores no lugar de funções, sejam esses uma variável ou até mesmo uma constante definida com um macro <code>#define</code> no compilador do C.</p>\n<h3 id=\"criando-funções-auxiliares-no-haskell\">Criando funções auxiliares no Haskell</h3>\n<p>Para evitar ter que usar as funções importadas do C diretamente, podemos criar algumas funções auxiliares para evitar ter que ficar usando ponteiros de tipos primitivos nos outros módulos.</p>\n<p>A primeira função que iremos criar é a <code>loadInput</code>, que será um <em>wrapper</em> ao redor de <code>c_loadInput</code> do C.</p>\n<pre><code class=\"language-haskell\">loadInput :: String -> IO (Maybe (ForeignPtr FfmpegInput))\nloadInput file = do\n  inputPtr &#x3C;- withCString file $ \\c_file -> c_loadInput c_file\n  foreignPtr &#x3C;- newForeignPtr c_freeInput inputPtr\n  return $ if inputPtr == nullPtr then Nothing else Just foreignPtr\n</code></pre>\n<p>Observe que a partir de agora praticamente todas as operações estarão dentro do escopo de <code>IO</code>, visto que a maioria das funções de leitura e escrita de ponteiros opera neste escopo.</p>\n<p>Como a função <code>c_loadInput</code> depende de uma <code>CString</code>, é necessário alocar uma. A biblioteca <code>Foreign</code> oferece uma função auxiliar chamada <code>withCString</code> que permite alocar uma <code>CString</code> temporária que será liberada da memória no fim de seu uso. Usando-a, podemos chamar a <code>c_loadInput</code> com o <code>file</code> convertido de <code>[Char]</code> do Haskell para <code>CString</code>.</p>\n<p>Tendo um ponteiro comum, transformamos-o em um <code>ForeignPtr</code>, informando que a função <code>c_freeInput</code> que será usada quando a memória poder ser liberada. No fim, verificamos se a chamada retornou um ponteiro não-nulo, e se for o caso podemos tirar vantagem do <code>Maybe</code> para evitar ter que ficar fazendo comparações diretas com <code>nullPtr</code>, o equivalente do <code>NULL</code> do C.</p>\n<p>A segunda função auxiliar que iremos criar é um <em>wrapper</em> ao redor de <code>c_avDictGet</code>.</p>\n<pre><code class=\"language-haskell\">dictGet :: AvDictionary -> Ptr AvDictionaryEntry -> IO (Ptr AvDictionaryEntry)\ndictGet dict previous = withCString \"\" $ \\c_str ->\n  c_avDictGet dict c_str previous avDictIgnoreSuffix\n</code></pre>\n<p>De maneira similar a <code>loadInput</code>, utilizamos novamente a <code>withCString</code>.</p>\n<p>A última função auxiliar faltando é a de conversão do tipo <code>AvDictionary</code> para um <code>Map String String</code> do Haskell, que permitirá um uso mais fácil dessa estrutura de dados. Essa função, chamada de <code>dictToMap</code>, será dividida em duas: a função simples e a recursiva. A simples será a “porta de entrada” que começará a recursividade.</p>\n<p>No exemplo oficial do FFmpeg, para poder ler um <code>AVDictionary</code>, é necessário usar um <code>while</code>.</p>\n<pre><code class=\"language-c\">const AVDictionaryEntry * tag = NULL;\n\nwhile ((tag = av_dict_get(fmt_ctx->metadata, \"\", tag, AV_DICT_IGNORE_SUFFIX))) {\n  printf(\"%s=%s\\n\", tag->key, tag->value);\n}\n</code></pre>\n<p>Para simular este comportamento e iterar sobre todos os itens do dicionário no Haskell, será necessário usar o paradigma funcional e, portanto, utilizar recursão.</p>\n<pre><code class=\"language-haskell\">dictToMap' :: AvDictionary -> Map String String -> Ptr AvDictionaryEntry -> IO (Map String String)\ndictToMap' dict mp tagPtr | tagPtr == nullPtr = pure mp\n                          | otherwise         = nextCall\n  where\n    key = peek tagPtr >>= \\t -> peekCString $ avDictKey t\n    value = peek tagPtr >>= \\t -> peekCString $ avDictValue t\n    newMap = liftA2 (\\k v -> Map.insert k v mp) key value\n    nextTag = dictGet dict tagPtr\n    nextCall = join $ liftA2 (dictToMap' dict) newMap nextTag\n</code></pre>\n<p>A ideia dessa função é ir iterando sobre todas as entradas no dicionário, adicionando-as num mapa até chegar na última entrada. Similar ao <code>while</code>, temos como condição de parada da recursão o ponteiro para a próxima entrada ser nulo, então é necessário comparar <code>tagPtr</code> com <code>nullPtr</code>.</p>\n<p>Aqui começamos o uso das funções de leituras de ponteiros, utilizamos <code>peek</code> para poder ler o valor de <code>tagPtr</code>, que nos colocará no contexto de <code>IO</code>. Também é necessário, em seguida, utilizar a <code>peekCString</code> para ler o valor da <code>CString</code> tanto de <code>key</code> quando de <code>value</code>. No final, teremos ambos como <code>IO String</code>.</p>\n<p>Como ambas as <em>strings</em> estão no contexto <code>IO</code>, precisamos chamar a função <code>Map.insert</code> dentro deste contexto. Para isso, podemos usar a função <code>liftA2</code> que permite fazer o <em>unbox</em> de duas variáveis no mesmo contexto, aplicar uma função com os valores internos e então retornar ao contexto anterior. No final, teremos um <code>IO (Map String String)</code>. Por baixo dos panos, a função <code>liftA2</code> está fazendo a mesma coisa do código abaixo.</p>\n<pre><code class=\"language-haskell\">newMap = do\n  k &#x3C;- key\n  v &#x3C;- value\n  return $ Map.insert k v\n</code></pre>\n<p>Agora podemos fazer a próxima chamada recursiva, mas como temos ambos o <code>newMap</code> e <code>nextTag</code> no contexto <code>IO</code>, precisamos usar o <code>join</code> em conjunto com o <code>liftA2</code> para poder acessar os valores de ambas para aí sim poder chamar a própria função novamente. O <code>join</code> neste caso irá extrair uma “camada” do <code>IO</code> do tipo, transformando um <code>IO (IO ())</code> em <code>IO ()</code>, por exemplo.</p>\n<p>Com a recursividade pronta, podemos criar a função simples de entrada.</p>\n<pre><code class=\"language-haskell\">dictToMap :: AvDictionary -> IO (Map String String)\ndictToMap dict = do\n  firstTag &#x3C;- dictGet dict nullPtr\n  dictToMap' dict Map.empty firstTag\n</code></pre>\n<p>Nesta função, precisamos obter a primeira entrada para iniciar a recursividade corretamente, caso contrário, estaríamos passando um <code>nullPtr</code> diretamente e a recursão nem iria começar. Agora com esta função, podemos converter um <code>AvDictionary</code> para <code>Map String String</code>, permitindo um acesso mais fácil no nosso código Haskell.</p>\n<p>A última função que iremos criar neste módulo é a responsável por enfim imprimir os metadados. Ela será chamada no módulo <code>Main</code> posteriormente. Para deixar a saída no terminal um pouco mais bonita, duas funções são usadas: <code>prettyTitle</code> e <code>prettyTitle</code>. O código dessas duas funções se encontra no módulo <code>PrettyPrint</code> e está disponível no repositório do GitHub deste tutorial. Para fins de simplificação, optei por não incluir a implementação aqui, mas o importante de saber sobre elas é que, como o nome sugere, uma irá imprimir um título de modo “bonito” e o outro imprimirá todas as entradas do <code>Map String String</code> de modo “bonito” também.</p>\n<p>A lógica dessa função segue o seguinte algoritmo:</p>\n<ol>\n<li>Obter o nome do arquivo da linha de comando;</li>\n<li>Ler o arquivo usando o FFmpeg;</li>\n<li>Obter os metadados do arquivo e do formato;</li>\n<li>Imprimir ambos de formato “bonito”.</li>\n</ol>\n<pre><code class=\"language-haskell\">printMetadata :: String -> IO ()\nprintMetadata fileName = do\n  inputPtr' &#x3C;- loadInput fileName\n  let inputForeignPtr = fromJust inputPtr'\n\n  withForeignPtr inputForeignPtr $ \\inputPtr -> do\n    input &#x3C;- peek inputPtr  \n    metadata &#x3C;- dictToMap $ ffMetadata input\n    formatMetadata &#x3C;- dictToMap $ ffFormat input\n\n    let nbStreams = fromIntegral . toInteger $ ffStreamsNb input\n        streamsMetadataPtr = ffStreamsMetadata input\n\n    streamsMetadata &#x3C;- peekArray nbStreams streamsMetadataPtr\n\n    prettyTitle \"File metadata\"\n    prettyPrint metadata\n    putStrLn \"\"\n\n    prettyTitle \"Format metadata\"\n    prettyPrint formatMetadata\n    putStrLn \"\"\n\n    forM_ (zip [0..nbStreams-1] streamsMetadata) $ \\(i, sMetadataPtr) -> do\n      sMetadata &#x3C;- dictToMap sMetadataPtr\n\n      prettyTitle $ \"Stream #\" ++ show i\n      prettyPrint sMetadata\n      putStrLn \"\"\n</code></pre>\n<p>Para fazer aproveitar da vantagem do <code>ForeignPtr</code>, utilizamos a função <code>withForeignPtr</code>, que nos permite ter acesso a um ponteiro <code>Ptr</code> comum, o qual podemos acessar e/ou modificar seus conteúdos. Toda a lógica que acessa os dados do nosso tipo <code>FFmpegInput</code> é feita dentro dessa função anônima que é passada para a função.</p>\n<p>Para iterar no vetor de ponteiros que representa o vetor dos dicionários das <em>streams</em>, precisamos primeiramente converter o tipo <code>Ptr [AvDictionary]</code> (que é igual a <code>Ptr [Ptr ()]</code>) para uma lista do Haskell, para poder iterar posteriormente, obtendo um <code>[AvDictionary]</code>. Tendo esta lista de ponteiros, podemos iterar usando o <code>forM_</code>, assim imprimindo os metadados de cada <em>stream</em>.</p>\n<p>Por conta das funções auxiliares no módulo <code>FFmpeg</code>, nosso código ficou muito mais legível e fácil de entender mesmo para quem não tem tanta familiaridade com as minúcias do C.</p>\n<h3 id=\"criando-o-arquivo-principal\">Criando o arquivo principal</h3>\n<p>Com o módulo <code>FFmpeg</code> pronto, podemos finalmente editar o nosso arquivo <code>Main.hs</code>. Iremos ler o caminho do arquivo da linha do comando do usuário.</p>\n<pre><code class=\"language-haskell\">module Main (main) where\n\nimport System.Environment\nimport FFmpeg\nimport Vlc\n\nmain :: IO ()\nmain = do\n  args &#x3C;- getArgs\n\n  let fileName = head args\n  printMetadata fileName\n</code></pre>\n<p>A implementação é bem direta ao ponto, obtemos os argumentos da linha de comando assumindo que o primeiro deles é um caminho válido para um arquivo existente. Tendo este caminho, basta utilizá-lo na chamada da função <code>printMetadata</code>. Note que por conta das funções auxiliares do módulo <code>FFmpeg</code>, não precisamos interagir com nada relacionado a linguagem C no módulo <code>Main</code>.</p>\n<h3 id=\"compilando-e-executando\">Compilando e executando</h3>\n<p>Com nosso código pronto, podemos compilar e executar de maneira similar ao primeiro exemplo.</p>\n<pre><code class=\"language-console\">$ stack build\n$ LD_LIBRARY_PATH=./libs stack exec c-interop big_buck_bunny.mp4\n</code></pre>\n<p>Ao observar a saída, vemos que o texto no terminal está colorido e também está seguindo a sintaxe de arquivos <code>.ini</code>, como pode-se observar abaixo na íntegra. O arquivo testado é o curta “Big Buck Bunny” da Blender Foundation, na versão “Sunflower”, disponível <a href=\"https://download.blender.org/demo/movies/BBB/bbb_sunflower_1080p_30fps_normal.mp4.zip\">neste link</a> para <em>download</em> gratuito no site oficial.</p>\n<pre><code class=\"language-ini\">[File metadata]\nartist = Blender Foundation 2008, Janus Bager Kristensen 2013\nbit_rate = 3481058 bit/s\ncomment = Creative Commons Attribution 3.0 - http://bbb3d.renderfarming.net\ncompatible_brands = isomavc1\ncomposer = Sacha Goedegebure\ncreation_time = 2013-12-16T17:44:39.000000Z\nduration = 0:10:34.600000\ngenre = Animation\nmajor_brand = isom\nminor_version = 1\nstreams_nb = 3\ntitle = Big Buck Bunny, Sunflower version\n\n[Format metadata]\nextensions = mov,mp4,m4a,3gp,3g2,mj2,psp,m4b,ism,ismv,isma,f4v,avif\nlong_name = QuickTime / MOV\nname = mov,mp4,m4a,3gp,3g2,mj2\n\n[Stream #0]\ncodec_long_name = H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10\ncodec_name = h264\ncodec_tag_string = avc1\ncreation_time = 2013-12-16T17:44:39.000000Z\nduration = 0:10:34.533333\nframe_rate = 30/1\nhandler_name = GPAC ISO Video Handler\nlanguage = und\nresolution = 1920 x 1080\nstart_time = 0:00:00.066667\ntime_base = 1/30000\ntype = video\nvendor_id = [0][0][0][0]\n\n[Stream #1]\nchannels = 2\ncodec_long_name = MP3 (MPEG audio layer 3)\ncodec_name = mp3\ncodec_tag_string = mp4a\ncreation_time = 2013-12-16T17:44:42.000000Z\nduration = 0:10:34.200000\nframe_rate = 0/0\nhandler_name = GPAC ISO Audio Handler\nlanguage = und\nsample_rate = 48000 Hz\nstart_time = 0:00:00.000000\ntime_base = 1/48000\ntype = audio\nvendor_id = [0][0][0][0]\n\n[Stream #2]\nchannels = 6\ncodec_long_name = ATSC A/52A (AC-3)\ncodec_name = ac3\ncodec_tag_string = ac-3\ncreation_time = 2013-12-16T17:44:42.000000Z\nduration = 0:10:34.144000\nframe_rate = 0/0\nhandler_name = GPAC ISO Audio Handler\nlanguage = und\nsample_rate = 48000 Hz\nstart_time = 0:00:00.000000\ntime_base = 1/48000\ntype = audio\nvendor_id = [0][0][0][0]\n</code></pre>\n<p>O arquivo de entrada é lido corretamente e a saída é o esperado: é identificado que o vídeo é do formato <code>mp4</code> e as informações do formato são apresentadas, bem como alguns metadados interessantes como <code>artist</code> e <code>genre</code>. Também são impressos os metadados individuais de cada <em>stream</em> no arquivo, que neste caso possui um vídeo e duas trilhas de áudio.</p>\n<h2 id=\"reproduzindo-o-arquivo-com-a-biblioteca-do-vlc\">Reproduzindo o arquivo com a biblioteca do VLC</h2>\n<p>Para complementar o exemplo anterior, podemos usar a <code>libvlc</code> para reproduzir o começo do arquivo em uma janela separada após exibir os metadados. Este exemplo é mais simples visto que não é necessário usar nenhuma conversão de tipos entre as linguagens, e podemos usar a <code>libvlc</code> diretamente no código em Haskell com as importações corretas.</p>\n<p>É necessário ter a <code>libvlc</code> instalada no sistema. Em distribuições como o Ubuntu, pode-se instalar usando o <code>apt</code>.</p>\n<pre><code class=\"language-console\">$ sudo apt install libvlc-dev\n</code></pre>\n<p>Este exemplo é baseado no código de exemplo oficial, disponível <a href=\"https://wiki.videolan.org/LibVLC_Tutorial/#Sample_libVLC_code\">neste link</a>.</p>\n<h3 id=\"adicionando-as-flags-de-compilação\">Adicionando as <em>flags</em> de compilação</h3>\n<p>É necessário adicionar a <em>flag</em> <code>-lvlc</code> no arquivo Cabal do projeto.</p>\n<pre><code class=\"language-cabal\">ghc-options: -- Demais flags omitidas\n             -lvlc -- [!code ++]\n</code></pre>\n<h3 id=\"criando-o-módulo\">Criando o módulo</h3>\n<p>Como no exemplo do FFmpeg, vamos criar um módulo <code>Vlc</code> que fará as importações das funções de <code>vlc/vlc.h</code> para usarmos.</p>\n<pre><code class=\"language-haskell\">module Vlc where\n\nimport Control.Concurrent (threadDelay)\nimport Foreign\nimport Foreign.C.Types\nimport Foreign.C.String\nimport Foreign.C.ConstPtr\n\n-- Cria uma nova instância do VLC\nforeign import capi \"vlc/vlc.h libvlc_new\" c_vlcNew :: CInt -> Ptr (ConstPtr CString) -> IO (Ptr ())\n-- Cria uma mídia a partir de um caminho\nforeign import capi \"vlc/vlc.h libvlc_media_new_path\" c_vlcMediaNewPath\n  :: Ptr () -> CString -> IO (Ptr ())\n-- Cria uma instância de um player a partir de uma mídia\nforeign import capi \"vlc/vlc.h libvlc_media_player_new_from_media\" c_vlcMediaPlayerNewFromMedia\n  :: Ptr () -> IO (Ptr ())\n\n-- Reproduz o arquivo no player\nforeign import capi \"vlc/vlc.h libvlc_media_player_play\" c_vlcMediaPlayerPlay\n  :: Ptr () -> IO ()\n-- Pausa a reprodução do arquivo no player\nforeign import capi \"vlc/vlc.h libvlc_media_player_stop\" c_vlcMediaPlayerStop\n  :: Ptr () -> IO ()\n\n-- Libera da memória a mídia\nforeign import capi \"vlc/vlc.h &#x26;libvlc_media_release\" c_vlcMediaRelease\n  :: FunPtr (Ptr () -> IO ())\n-- Libera da memória o player\nforeign import capi \"vlc/vlc.h &#x26;libvlc_media_player_release\" c_vlcMediaPlayerRelease\n  :: FunPtr (Ptr () -> IO ())\n-- Libera da memória a instância do VLC\nforeign import capi \"vlc/vlc.h &#x26;libvlc_release\" c_vlcRelease\n  :: FunPtr (Ptr () -> IO ())\n</code></pre>\n<p>Note que iremos apenas utilizar funções já disponíveis na biblioteca do VLC, e que também não precisaremos lidar com dados visto que a biblioteca utiliza <em>structs</em> vazias. O importante vai ser trabalhar com os ponteiros para <code>void</code>.</p>\n<h3 id=\"criando-as-funções-auxiliares\">Criando as funções auxiliares</h3>\n<p>O módulo <code>Vlc</code> fará um uso maior de <code>ForeignPtr</code> por conta de ter 3 ponteiros diferentes para utilizar e liberar da memória. Para facilitar o uso, é mais fácil criar funções auxiliares que usam tipos do Haskell em sua maioria para chamar internamente as funções do C, um conceito também conhecido como <em>binding</em>.</p>\n<pre><code class=\"language-haskell\">vlcNew :: CInt -> Ptr (ConstPtr CString) -> IO (ForeignPtr ())\nvlcNew argc argv = c_vlcNew argc argv >>= newForeignPtr c_vlcRelease\n\nvlcMediaNewPath :: ForeignPtr () -> String -> IO (ForeignPtr ())\nvlcMediaNewPath inst file = \n  withForeignPtr inst (withCString file . c_vlcMediaNewPath) \n    >>= newForeignPtr c_vlcMediaRelease\n\nvlcMediaPlayerNewFromMedia :: ForeignPtr () -> IO (ForeignPtr ())\nvlcMediaPlayerNewFromMedia media = \n  withForeignPtr media c_vlcMediaPlayerNewFromMedia \n    >>= newForeignPtr c_vlcMediaPlayerRelease\n\nvlcMediaPlayerPlay :: ForeignPtr () -> IO ()\nvlcMediaPlayerPlay player = withForeignPtr player c_vlcMediaPlayerPlay\n\nvlcMediaPlayerStop :: ForeignPtr () -> IO ()\nvlcMediaPlayerStop player = withForeignPtr player c_vlcMediaPlayerStop\n</code></pre>\n<p>Assim como no módulo <code>FFmpeg</code>, utilizaremos as funções <code>withForeignPtr</code> para ter acesso a um ponteiro comum e <code>newForeignPtr</code> para transformar os ponteiros comuns resultantes das chamadas das funções do C para um ponteiro externo.</p>\n<h3 id=\"criando-a-função-de-reprodução-no-haskell\">Criando a função de reprodução no Haskell</h3>\n<p>Nosso módulo <code>Vlc</code> terá apenas uma função em Haskell, a responsável por reproduzir o arquivo por um certo tempo e então liberar as instâncias da memória.</p>\n<pre><code class=\"language-haskell\">playFile :: String -> IO ()\nplayFile fileName = do\n  inst &#x3C;- vlcNew 0 nullPtr\n  m &#x3C;- vlcMediaNewPath inst fileName\n  mp &#x3C;- vlcMediaPlayerNewFromMedia m\n\n  vlcMediaPlayerPlay mp\n  threadDelay $ 10 * 1000000\n  vlcMediaPlayerStop mp\n\n  return ()\n</code></pre>\n<p>Note que o código é bem semelhante a estrutura do exemplo em C, com excessão que não precisamos nos preocupar com o gerenciamento e liberação da memória dos ponteiros. As mesmas funções são chamadas e na mesma ordem, com pequenas diferenças entre as linguagens tais como o uso de <code>threadDelay</code> no lugar do <code>sleep</code> do C.</p>\n<h3 id=\"usando-o-módulo-no-módulo-principal\">Usando o módulo no módulo principal</h3>\n<p>Agora podemos chamar a função <code>playFile</code> logo em seguida da impressão dos metadados.</p>\n<pre><code class=\"language-haskell\">main :: IO ()\nmain = do\n  args &#x3C;- getArgs\n  \n  let fileName = head args\n  printMetadata fileName\n  playFile fileName -- [!code ++]\n</code></pre>\n<p>Basta adicionar a chamada de <code>playFile</code> logo após a impressão dos metadados.</p>\n<h3 id=\"compilando-e-executando-1\">Compilando e executando</h3>\n<p>Usaremos os mesmos comandos do <code>stack</code> para compilar e executar.</p>\n<pre><code class=\"language-console\">$ stack build\n$ LD_LIBRARY_PATH=./libs stack exec c-interop big_buck_bunny.mp4\n</code></pre>\n<p>Ao executar, pode-se observar que uma janela gráfica com o vídeo é aberta e a reprodução começa imediatamente. Após 10 segundos, o vídeo é fechado e encerrado.</p>\n<figure><img src=\"https://alessandrojean.github.io/posts/2024/2024-05-08-interagindo-com-bibliotecas-em-c-usando-codigo-em-haskell/vlc-screenshot.png\" alt=\"Janela do VLC\"><figcaption>Trecho do curta </figcaption></figure>\n<h2 id=\"conclusão\">Conclusão</h2>\n<p>Utilizar a interoperabilidade pode ser vantajoso em casos onde é necessário performance ou se deseja utilizar uma biblioteca em código nativo já compilada e/ou disponível no sistema operacional. Através dos recursos do Haskell, torna-se fácil ter o acesso a tais funções e dados retornados visto que a linguagem já possui tal recurso implementado em seu núcleo. Apesar do código necessitar em casos mais avançados de um conhecimento intermediário em gerenciamento de memória em C, ainda assim é possível para usuários mais leigos fazer uso de funções mais simples para criar um conhecimento mais aprofundado posteriormente.</p>\n<h2 id=\"bibliografia\">Bibliografia</h2>\n<ul>\n<li>WIKIPÉDIA. <em>“Language interoperability”</em>. Wikipédia, disponível <a href=\"https://en.wikipedia.org/wiki/Language_interoperability\">neste link</a>.</li>\n<li>O’SULLIVAN, Bryan et al. <em>“Interfacing with C: the FFI”</em>. Real World Haskell, disponível <a href=\"https://book.realworldhaskell.org/read/interfacing-with-c-the-ffi.html\">neste link</a>.</li>\n<li>HASKELL. <em>Foreign Function Interface</em>. Haskell Wiki, disponível <a href=\"https://wiki.haskell.org/Foreign_Function_Interface\">neste link</a>.</li>\n<li>MOREIRA, Leandro. <em>FFmpeg libavformat tutorial</em>. GitHub, disponível <a href=\"https://github.com/leandromoreira/ffmpeg-libav-tutorial\">neste link</a>.</li>\n<li>VLC. <em>LibVLC Tutorial</em>. VLC Wiki, disponível <a href=\"https://wiki.videolan.org/LibVLC_Tutorial\">neste link</a>.</li>\n<li>UFABC. <em>Grupo de estudos em Haskell da UFABC</em>. Pesquisa UFABC, disponível <a href=\"https://haskell.pesquisa.ufabc.edu.br\">neste link</a>.</li>\n</ul>","date_published":"2024-05-08T00:00:00.000Z","date_modified":"2024-05-08T00:00:00.000Z","tags":["Programação"],"language":"pt-BR"},{"id":"convertendo-paginas-do-notion-para-markdown-usando-haskell","url":"https://alessandrojean.github.io/post/convertendo-paginas-do-notion-para-markdown-usando-haskell","title":"Convertendo páginas do Notion para Markdown usando Haskell","summary":"Um tutorial introdutório que aborda o problema com a abordagem em uma linguagem com um paradigma de programação funcional.","content_html":"<p>O <a href=\"https://notion.so\">Notion</a> possui um ótimo editor WYSIWYG (<em>What You See Is What You Get</em>), mas por baixo dos panos todos os blocos são armazenados individualmente e o serviço não oferece uma API que permita recuperar o conteúdo inteiro de uma página em particular em formatos mais abertos, tais como o <a href=\"https://www.markdownguide.org/getting-started/\">Markdown</a>.</p>\n<figure><img src=\"https://images.unsplash.com/photo-1642132652859-3ef5a1048fd1\" alt=\"Página inicial do site do Notion. Imagem por Unsplash.\"><figcaption>Página inicial do site do Notion. Imagem por Unsplash.</figcaption></figure>\n<p>O intuito desse tutorial é demonstrar como é possível construir um programa usando <a href=\"https://www.haskell.org/\">Haskell</a> com o paradigma de programação funcional que seja capaz de pegar os blocos de uma página no Notion e convertê-los para Markdown ou outros formatos usando a API da biblioteca <a href=\"https://pandoc.org/\">Pandoc</a>.</p>\n<p>Apesar de um esforço existir em relação a esse problema em Haskell pelo projeto <a href=\"https://github.com/dalpd/notion\" target=\"_blank\" rel=\"noopener noreferrer external\">dalpd/notion</a> no GitHub, este não possibilita (ainda) obter os blocos de uma página e está em estado muito inicial, além de estar com pouca manutenção. Este repositório citado utiliza as mesmas bibliotecas em relação ao <em>parse</em> da API do Notion, mas seu código difere do criado neste tutorial.</p>\n<p>Como exemplo de página para testes, será utilizada a desse próprio tutorial, que será o teste do programa em conseguir converter para Markdown no formato <a href=\"https://github.github.com/gfm/\"><em>GitHub Flavored Markdown</em></a>. O repositório com o código completo encontra-se disponível em <a href=\"https://github.com/alessandrojean/notion-doc\" target=\"_blank\" rel=\"noopener noreferrer external\">alessandrojean/notion-doc</a>.</p>\n<div><p><strong>Nota:</strong></p><p>Este tutorial foi escrito como um dos projetos da disciplina <a href=\"https://folivetti.github.io/teaching/2024-summer-teaching-1\"><em>Desenvolvimento Guiado a Tipos</em></a> do Bacharelado em Ciência da Computação na UFABC em Março de 2024.</p></div>\n<h2 id=\"conteúdo\">Conteúdo</h2>\n<ol>\n<li><a href=\"#motiva%C3%A7%C3%A3o-por-tr%C3%A1s\">Motivação por trás</a></li>\n<li><a href=\"#criando-o-projeto-e-instalando-as-depend%C3%AAncias\">Criando o projeto e instalando as dependências</a></li>\n<li><a href=\"#ideia-b%C3%A1sica\">Ideia básica</a></li>\n<li><a href=\"#obtendo-uma-chave-de-api\">Obtendo uma chave de API</a></li>\n<li><a href=\"#definindo-os-tipos-da-api\">Definindo os tipos da API</a></li>\n<li><a href=\"#fazendo-as-requisi%C3%A7%C3%B5es-na-api\">Fazendo as requisições na API</a></li>\n<li><a href=\"#convertendo-os-blocos\">Convertendo os blocos</a></li>\n<li><a href=\"#criando-o-markdown\">Criando o Markdown</a></li>\n<li><a href=\"#juntando-tudo\">Juntando tudo</a></li>\n<li><a href=\"#conclus%C3%A3o\">Conclusão</a></li>\n<li><a href=\"#bibliografia\">Bibliografia</a></li>\n</ol>\n<h2 id=\"motivação-por-trás\">Motivação por trás</h2>\n<p>A ideia deste tutorial veio de uma necessidade própria de ter uma maneira de conseguir fazer um <em>backup</em> se necessário do conteúdo desse site. Apesar do Notion ser super versátil e aumentar bastante a produtividade na escrita, é sempre bom poder ter controle dos dados se for necessário.</p>\n<h2 id=\"criando-o-projeto-e-instalando-as-dependências\">Criando o projeto e instalando as dependências</h2>\n<p>Seguindo o recomendado para a construção de aplicações modernas com Haskell, iremos utilizar o <a href=\"https://docs.haskellstack.org/en/stable/\">Haskell Stack</a> para a criação do projeto e para a manutenção das dependências externas. Todo o pacote de desenvolvimento foi instalado através do <a href=\"https://www.haskell.org/ghcup/\">GHCup</a>. Para iniciar a pasta do projeto, utilizamos o comando:</p>\n<pre><code class=\"language-console\">$ stack init notion-doc simple\n</code></pre>\n<p>Após a ferramenta terminar de transferir os arquivos necessários do <em>template</em> <code>simple</code>, podemos referenciar as dependências externas no <a href=\"https://hackage.haskell.org/\">Hackage</a> necessárias ao editar o arquivo <code>notion-doc.cabal</code> e incluí-las na seção <code>build-depends</code>:</p>\n<pre><code class=\"language-cabal\">build-depends: base >= 4.7 &#x26;&#x26; &#x3C; 5\n  , aeson\n  , aeson-casing\n  , http-conduit\n  , http-client\n  , http-client-tls\n  , http-types\n  , conduit\n  , conduit-extra\n  , bytestring\n  , pandoc\n  , pandoc-types\n  , slugify\n</code></pre>\n<p>O <code>aeson</code> será utilizado para fazer o <em>parse</em> do JSON proveniente das respostas da API do Notion, enquanto o <code>http-conduit</code> será o cliente HTTP para fazer as requisições. Ambos são utilizados em conjunto. Por sua vez, o <code>pandoc</code> será utilizado para escrever o arquivo final em Markdown.</p>\n<h2 id=\"ideia-básica\">Ideia básica</h2>\n<p>O algoritmo do programa consiste em fazer o <em>parse</em> dos blocos da API do Notion e convertê-los para blocos da API do Pandoc (a AST — <a href=\"https://en.wikipedia.org/wiki/Abstract_syntax_tree\"><em>Abstract Syntax Tree</em></a>). Tendo a AST corretamente construída, é possível convertê-la para qualquer formato que o Pandoc suporte, seja ele Markdown, HTML, EPUB, dentre muitos outros.</p>\n<p>O passo-a-passo, de uma forma resumida, é esse:</p>\n<ol>\n<li>Requisitar as informações da página e os seus blocos da API do Notion;</li>\n<li>Fazer o <em>parse</em> usando o <code>aeson</code> para as ADTs do projeto (módulo <code>NotionApiTypes</code>);</li>\n<li>Converter os blocos do Notion (<code>[N.Block]</code>) para os blocos do Pandoc (<code>[P.Block]</code>);</li>\n<li>Converter a AST do Pandoc para o formato desejado, o Markdown.</li>\n</ol>\n<p>Este tutorial não vai seguir necessariamente esta ordem do algoritmo, mas pretende abordar uma boa parte de como ele funciona de modo geral ao focar nas partes primordiais do código.</p>\n<h2 id=\"obtendo-uma-chave-de-api\">Obtendo uma chave de API</h2>\n<p>Para obter uma chave da API do Notion, é necessário <a href=\"https://developers.notion.com/docs/create-a-notion-integration\">criar uma integração</a> no <em>dashboard</em> e conectá-la as páginas que deseja <a href=\"https://developers.notion.com/docs/create-a-notion-integration#give-your-integration-page-permissions\">permitir seu acesso</a>. Para fins de simplificação, a chave será escrita diretamente no código, mas evite esta abordagem em códigos para produção, visto que é totalmente insegura: use variáveis de ambiente com a biblioteca <code>dotenv</code>.</p>\n<p>Para agrupar as funções da API do Notion, no programa, torna-se mais fácil criar um módulo para isso, que por enquanto terá apenas algumas constantes.</p>\n<pre><code class=\"language-haskell\">{-# LANGUAGE OverloadedStrings #-}\nmodule NotionApi where\n\nimport qualified Data.ByteString.Char8 as S8\n\nbaseUrl :: String\nbaseUrl = \"https://api.notion.com/v1\"\n\napiVersion :: S8.ByteString\napiVersion = \"2022-06-28\"\n\napiKey :: S8.ByteString\napiKey = \"sua_chave_da_api\"\n</code></pre>\n<p>Aqui vale uma observação: a maioria das funções do <code>http-conduit</code> espera como parâmetros <em>strings</em> do tipo <code>ByteString</code> e não a <code>String</code> do Haskell. Para evitar conversões em tempo de execução, é melhor já deixar as constantes no formato necessário.</p>\n<p>Também faz-se uso da linguagem de extensão <code>OverloadedStrings</code>, que permite escrever os literais das <em>strings</em> diretamente (usando aspas, de modo normal) e a conversão é feita em tempo de compilação, evitando o uso recorrente do <code>S8.pack</code>.</p>\n<h2 id=\"definindo-os-tipos-da-api\">Definindo os tipos da API</h2>\n<p>As páginas no Notion são compostas por diversos tipos de blocos, podendo ser, por exemplo, parágrafos, títulos, imagens etc. Cada bloco, por sua vez, tem particularidades específicas, como o de parágrafo que permite uma edição mais detalhada do texto, possibilitando o uso de recursos como negrito, itálico e sublinhado.</p>\n<p>Para obter uma boa referência de todos os tipos possíveis de blocos e suas propriedades particulares, pode-se utilizar a biblioteca <code>@notionhq/client</code> disponível no NPM para a linguagem JavaScript e TypeScript. Os tipos na linguagem TypeScript podem ser encontrados no arquivo <code>src/api-endpoints.ts</code> no repositório da biblioteca no GitHub (<a href=\"https://github.com/makenotion/notion-sdk-js/blob/main/src/api-endpoints.ts\">referência</a>), mas também é possível consultá-los num formato mais amigável e sucinto na referência de API do <a href=\"https://developers.notion.com/reference/block\">Block</a>.</p>\n<p>Tomando como exemplo o bloco de parágrafo, temos:</p>\n<pre><code class=\"language-ts\">type ParagraphBlock = {\n  type: \"paragraph\"\n  paragraph: { rich_text: Array&#x3C;RichTextItemResponse>; color: ApiColor }\n  id: string\n  has_children: boolean\n  // Outras propriedades omitidas.\n}\n</code></pre>\n<p>E tomando como exemplo um bloco de título 1, temos:</p>\n<pre><code class=\"language-ts\">type Heading1Block = {\n  type: \"heading_1\"\n  heading_1: {\n    rich_text: Array&#x3C;RichTextItemResponse>\n    color: ApiColor\n    is_toggleable: boolean\n  }\n  id: string\n  has_children: boolean\n  // Outras propriedades omitidas.\n}\n</code></pre>\n<p>Por fim, o tipo genérico do bloco é do tipo soma.</p>\n<pre><code class=\"language-ts\">// Outros tipos de blocos omitidos para simplificação.\ntype Block = ParagraphBlock | Heading1Block\n</code></pre>\n<p>Como explicado anteriormente, cada tipo de bloco possui propriedades específicas. No caso do parágrafo, a propriedade <code>paragraph</code> é única neste bloco, enquanto propriedades como <code>type</code>, <code>id</code> e <code>has_children</code> são comuns em todos os blocos.</p>\n<p>A propriedade <code>type</code> é a mais importante no nosso contexto, já que possibilitará a distinção do tipo do bloco, enquanto a <code>has_children</code> permitirá saber se será necessário fazer requisições extras para obter os blocos-filhos, no caso de blocos de listas de tópicos, por exemplo.</p>\n<p>Em um paradigma orientado a objetos, o bloco seria uma classe abstrata, ou até mesmo uma interface, com essas propriedades que deveria ser herdado por classes para cada bloco. O desafio agora é converter estes tipos para tipos que sejam válidos na linguagem Haskell e que possibilitem o <em>parse</em> pela biblioteca <code>aeson</code>.</p>\n<p>Todos os tipos do Notion ficarão no módulo <code>NotionApiTypes</code>, e alguns deles serão mostrados como exemplo nas próximas seções do texto.</p>\n<h3 id=\"fazendo-parse-de-json-com-o-aeson\">Fazendo <em>parse</em> de JSON com o <code>aeson</code></h3>\n<p>A biblioteca <code>aeson</code>, em conjunto com a <code>aeson-casing</code>, permite transformar <em>strings</em> com JSON para tipos próprios no Haskell. Os tipos, por sua vez, devem ser instâncias da classe <code>FromJSON</code>, onde a implementação da função <code>parseJSON</code> pode ser tanto manual quanto derivada automaticamente (o que será utilizado geralmente).</p>\n<p>Suponha que temos este objeto em JSON que representa uma pessoa.</p>\n<pre><code class=\"language-json\">{\n  \"name\": \"John Doe\",\n  \"email\": \"john.doe@example.org\",\n  \"country\": \"US\",\n  \"phone\": null\n}\n</code></pre>\n<p>Podemos representar em Haskell da seguinte maneira.</p>\n<pre><code class=\"language-haskell\">{-# LANGUAGE DeriveGeneric #-}\n\nimport Data.Aeson\nimport GHC.Generics (Generic)\n\ndata Person = Person\n  { name :: String\n  , email :: String\n  , country :: String\n  , phone :: Maybe String\n  } deriving (Generic)\n  \ninstance Person FromJSON\n</code></pre>\n<p>Ao utilizar a extensão de linguagem <code>DeriveGeneric</code> em conjunto com o <code>deriving (Generic)</code>, o compilador ficará encarregado de implementar a função <code>parseJSON</code> automaticamente para esse tipo, poupando bastante tempo do desenvolvedor. Entretanto, isso pode ser um empecilho em alguns casos.</p>\n<p>Suponha que uma das propriedades do objeto JSON fosse <code>country_of_origin</code> (em <em>snake_case</em>) ao invés de <code>country</code>, como faríamos para informar o compilador que <code>country_of_origin</code> deve virar <code>countryOfOrigin</code> para respeitar as convenções de nome do Haskell? Pior ainda, e se fosse necessário definir os atributos do tipo <code>Person</code> com um prefixo <code>person</code> para evitar ambiguidade (o que é comum neste caso do Notion), como fazer?</p>\n<p>O primeiro caso é bem simples e pode ser resolvido sem muitas modificações com a biblioteca <code>aeson-casing</code>, que permite a transformação das chaves das propriedades.</p>\n<pre><code class=\"language-haskell\">{-# LANGUAGE DeriveGeneric #-}\n\nimport Data.Aeson\nimport Data.Aeson.Casing\nimport GHC.Generics (Generic)\n\ndata Person = Person\n  { name :: String\n  , email :: String\n  , countryOfOrigin :: String\n  , phone :: Maybe String\n  } deriving (Generic)\n\ninstance Person FromJSON where\n  parseJSON = genericParseJSON $ aesonDrop 0 snakeCase\n</code></pre>\n<p>A função <code>aesonDrop</code> permite que se especifique o número de caracteres a serem removidos no início da chave e qual a transformação que deve ser aplicada, que neste caso é a <code>snakeCase</code>. Deste modo, a chave <code>country_of_origin</code> virará a <code>countryOfOrigin</code>. Usa-se a <code>aesonDrop</code> ao invés da <code>aesonPrefix snakeCase</code> por conta de um detalhe que será explicado em seguida.</p>\n<p>O segundo caso também é facilmente resolvido com a <code>aeson-casing</code>.</p>\n<pre><code class=\"language-haskell\">{-# LANGUAGE DeriveGeneric #-}\n\nimport Data.Aeson\nimport Data.Aeson.Casing\nimport GHC.Generics (Generic)\n\ndata Person = Person\n  { personName :: String\n  , personEmail :: String\n  , personCountryOfOrigin :: String\n  , personPhone :: Maybe String\n  } deriving (Generic)\n  \ninstance Person FromJSON where\n  parseJSON = genericParseJSON $ aesonPrefix snakeCase\n</code></pre>\n<p>A função <code>aesonPrefix</code> permite que seja aplicado um transformador logo após remover um prefixo existente, que nesse caso por convenção da biblioteca são todos os caracteres em minúsculo no começo da chave. Assim que o primeiro caractere maiúsculo é encontrado, apenas o resto da <em>string</em> sofre a transformação. Este é o motivo de não ter sido utilizado o <code>aesonPrefix</code> no exemplo anterior, pois <code>countryOfOrigin</code> viraria <code>of_origin</code> apenas e daria erro no momento do <em>parse</em> por ser uma chave que não existe no objeto.</p>\n<p>Em alguns casos também pode ser interessante renomear as propriedades, o que pode ser feito com <em>functors</em>, sendo a maneira manual de escrever o <code>parseJSON</code>.</p>\n<pre><code class=\"language-haskell\">import Data.Aeson\n\ndata Person = Person\n  { personName :: String\n  , personEmail :: String\n  , personCountry :: String\n  , personPhone :: Maybe String\n  }\n\ninstance Person FromJSON where\n  parseJSON = withObject \"Person\" $ \\v -> Person\n    &#x3C;$> v .: \"name\"\n    &#x3C;*> v .: \"email\"\n    &#x3C;*> v .: \"country_of_origin\"\n    &#x3C;*> v .?: \"phone\"\n</code></pre>\n<p>Essa sintaxe com o <em>functor</em> pode parecer um pouco confusa numa primeira vez, mas ela seria equivalente a fazer isto abaixo.</p>\n<pre><code class=\"language-haskell\">instance Person FromJSON where\n  parseJSON = withObject \"Person\" $ \\v -> do\n    name &#x3C;- v .: \"name\"\n    email &#x3C;- v .: \"email\"\n    country &#x3C;- v .: \"country_of_origin\"\n    phone &#x3C;- v .?: \"phone\"\n    \n    pure $ Person name email country phone\n</code></pre>\n<p>O argumento <code>v</code> é do tipo <code>Object</code>, que por sua vez permite que valores sejam extraídos a partir de chaves com os operadores <code>.:</code> e <code>.?:</code> para chaves opcionais.</p>\n<h3 id=\"modelando-os-tipos-dos-blocos-do-notion\">Modelando os tipos dos blocos do Notion</h3>\n<p>Com essa base da biblioteca <code>aeson</code>, já é possível partir para alguns casos mais avançados, como o polimorfismo dos blocos a partir da propriedade <code>type</code>. Vamos começar definindo dois blocos iniciais, os mesmos citados como exemplo anteriormente, o parágrafo e título 1.</p>\n<pre><code class=\"language-haskell\">data Block = BlockParagraph { paragraph :: Text }\n  | BlockHeading1 { heading1 :: Heading }\n  deriving (Generic)\n</code></pre>\n<p>Aqui, ao invés de usarmos os atalhos da <code>aeson-casing</code>, precisaremos fazer algumas configurações do <em>parser</em> manualmente por meio de opções.</p>\n<pre><code class=\"language-haskell\">blockOptions :: Options\nblockOptions = defaultOptions \n  { sumEncoding = TaggedObject { tagFieldName = \"type\", contentsFieldName = undefined }\n  , constructorTagModifier = snakeCase . fixBlockType . drop 5\n  , fieldLabelModifier = snakeCase . fixBlockType\n  }\n</code></pre>\n<p>Explicando cada atributo, temos:</p>\n<ul>\n<li><code>sumEncoding</code>: especifica detalhes adicionais sobre o tipo soma. Estamos informando o <code>aeson</code> que a diferenciação do tipo soma se dará por <em>tags</em> no objeto. Por padrão, o <code>aeson</code> utiliza a chave <code>tag</code>, mas como o Notion utiliza a chave <code>type</code>, temos que especificá-la.</li>\n<li><code>constructorTagModifier</code>: faz a transformação do nome dos tipos no Haskell para os valores da propriedade <code>type</code> no JSON. Queremos que <code>BlockHeading1</code> seja o equivalente de <code>heading_1</code> no JSON, e isto é feito com a composição de funções.</li>\n<li><code>fieldLabelModifier</code>: faz a transformação do nome dos atributos no Haskell para as chaves do objeto no JSON, o que a biblioteca <code>aeson-casing</code> faz por baixo dos panos.</li>\n</ul>\n<p>Em especial, temos a função <code>fixBlockType</code> responsável por substituir <code>heading1</code> e seus derivados por <code>heading_1</code>, pois é um caso que a <code>snakeCase</code> não trata.</p>\n<p>Tendo as opções, pode-se passá-las para o <code>parseJSON</code> da seguinte maneira.</p>\n<pre><code class=\"language-haskell\">instance FromJSON Block where\n  parseJSON = genericParseJSON blockOptions\n</code></pre>\n<p>Os demais tipos como <code>Text</code> e <code>Heading</code> usados nos blocos foram omitidos para fins de simplificação, mas estão disponíveis no repositório do tutorial.</p>\n<h3 id=\"modelando-os-tipos-das-respostas-da-api\">Modelando os tipos das respostas da API</h3>\n<p>Iremos utilizar dois <em>endpoints</em> da API do Notion: <a href=\"https://developers.notion.com/reference/retrieve-a-page\"><em>retrieve a page</em></a> e <a href=\"https://developers.notion.com/reference/get-block-children\"><em>retrieve block children</em></a>. Em relação ao primeiro <em>endpoint</em>, temos os seguintes tipos de retorno:</p>\n<pre><code class=\"language-haskell\">data Page = Page\n  { pageId :: String\n  , pageProperties :: PageProperties\n  , pageCreatedTime :: String\n  } deriving (Show)\n  \ninstance FromJSON Page where\n  parseJSON = genericParseJSON $ aesonPrefix snakeCase\n</code></pre>\n<p>O atributo <code>pageProperties</code> é um mapa de <code>String</code> para <code>Property</code> (no TypeScript), e dá acesso a todos os tipos de propriedades que uma página tem, e elas são polimórficas, similar aos blocos. Como só temos interesse na propriedade do tipo <code>title</code>, que tem sempre a chave <code>Name</code>, pode-se simplificar o tipo como abaixo.</p>\n<pre><code class=\"language-haskell\">newtype PageProperties = PageProperties { propName :: TitleProperty }\n  deriving (Generic)\n\ninstance FromJSON PageProperties where\n  parseJSON = genericParseJSON $ aesonPrefix pascalCase\n  \nnewtype TitleProperty = TitleProperty { tpTitle :: [RichTextItem] }\n  deriving (Generic)\n  \ninstance FromJSON TitleProperty where\n  parseJSON = genericParseJSON $ aesonPrefix snakeCase\n</code></pre>\n<p>Por sua vez, o segundo <em>endpoint</em> nos retorna este tipo abaixo.</p>\n<pre><code class=\"language-haskell\">data PageBlocks = PageBlocks\n  { results :: [Block]\n  , nextCursor :: Maybe String\n  , hasMore :: Bool\n  } deriving (Generic)\n  \ninstance FromJSON PageBlocks where\n  parseJSON = genericParseJSON $ aesonDrop 0 snakeCase\n</code></pre>\n<p>O atributo <code>nextCursor</code> retorna um cursor de paginação alfanumérico que pode ser utilizado para obter os próximos blocos, caso eles existam (<code>hasMore == True</code>).</p>\n<h2 id=\"fazendo-as-requisições-na-api\">Fazendo as requisições na API</h2>\n<p>Tendo modelado todos os tipos necessários do Notion, podemos partir para a criação das funções responsáveis por fazer as requisições HTTPS para o Notion. Voltando ao módulo <code>NotionApi</code>, podemos começar definindo a função <code>retrieveNotionPage</code>.</p>\n<p>A função <code>retrieveNotionPage</code> permitirá obter detalhes de uma página a partir de seu ID, e a resposta será utilizada para obter as propriedades da página, como o título.</p>\n<pre><code class=\"language-haskell\">import Network.HTTP.Conduit\nimport Network.HTTP.Simple\nimport NotionApiTypes (Page, PageBlocks)\n\nretrieveNotionPage :: String -> IO Page\nretrieveNotionPage pageToFetch = do\n  initialRequest &#x3C;- parseRequest $ baseUrl ++ \"/pages/\" ++ pageToFetch\n  let request\n        = setRequestMethod \"GET\"\n        $ addRequestHeader \"Notion-Version\" apiVersion\n        $ setRequestBearerAuth apiKey initialRequest\n  \n  response &#x3C;- httpJSON request\n  return $ responseBody response\n</code></pre>\n<p>O <code>http-conduit</code> funciona como uma biblioteca de cliente HTTP qualquer em outra linguagem, tal qual o <a href=\"https://square.github.io/okhttp/\">OkHttp</a>, <a href=\"https://ktor.io/\">Ktor</a> ou o <a href=\"https://axios-http.com/\">Axios</a>. O fluxo segue o do protocolo: deve-se criar uma requisição para a URL que deseja, com o método e cabeçalhos necessários e então esperar pela resposta, que por sua vez será transformada nos tipos do Haskell pela função <code>httpJSON</code>. Note que o retorno da função é um <code>IO Page</code>, então é necessário chamar a função no mesmo contexto.</p>\n<p>Ao utilizar a função <code>httpJSON</code>, o <code>http-conduit</code> já se encarrega de fazer a integração com o <code>aeson</code> e fazer o parse para o tipo do retorno informado no cabeçalho da função, que, neste caso, deve implementar a <code>FromJSON</code>.</p>\n<p>De maneira similar, pode-se escrever a função <code>retrieveBlockChildren</code>, só alterando o retorno para <code>IO PageBlocks</code> e a URL do <em>endpoint</em> de <em>retrieve block children</em>.</p>\n<pre><code class=\"language-haskell\">retrieveBlockChildren :: String -> Maybe String -> IO PageBlocks\nretrieveBlockChildren blockToFetch startCursor = do\n  initialRequest &#x3C;- parseRequest $ baseUrl ++ \"/blocks/\" ++ blockToFetch ++ \"/children\"\n  let request\n        = setRequestMethod \"GET\"\n        $ setRequestQueryString (blockChildrenParams startCursor)\n        $ addRequestHeader \"Notion-Version\" apiVersion\n        $ setRequestBearerAuth apiKey initialRequest\n\n  response &#x3C;- httpJSON request\n  return $ responseBody response\n</code></pre>\n<p>A diferença é que esta função permite a passagem de um parâmetro extra, o cursor de início, usado para a paginação. Desta forma, é criado um <em>query parameter</em> de <code>?start_cursor=valor</code> quando <code>startCursor</code> não for <code>Nothing</code>.</p>\n<p>Para facilitar obter todos os blocos de uma página sem ter que lidar com a paginação, podemos criar uma função que fará isso para nós com uma abordagem recursiva.</p>\n<pre><code class=\"language-haskell\">retrieveAllBlockChildren :: String -> IO [Block]\nretrieveAllBlockChildren bId = do\n  first &#x3C;- retrieveBlockChildren bId Nothing\n  retrieveAllBlockChildren' bId (nextCursor first) (pure $ results first)\n\nretrieveAllBlockChildren' :: String -> Maybe String -> IO [Block] -> IO[Block]\nretrieveAllBlockChildren' bId (Just startCursor) bks = do\n  acm &#x3C;- bks\n  crr &#x3C;- retrieveBlockChildren bId (Just startCursor)\n  retrieveAllBlockChildren' bId (nextCursor crr) (pure $ acm &#x3C;> results crr)\nretrieveAllBlockChildren' _ Nothing bks = bks\n</code></pre>\n<p>O intuito é que somente <code>retrieveAllBlockChildren</code> seja exportado do módulo. Esta função de entrada irá começar a recursão através de uma segunda função auxiliar. Basicamente o que é feito é obter os blocos anteriores no acumulador, obter os blocos da página atual e concatená-los. Esta nova lista de blocos é então passada para a chamada recursiva até chegar em uma resposta da API em que <code>nextCursor</code> seja <code>Nothing</code>.</p>\n<h2 id=\"convertendo-os-blocos\">Convertendo os blocos</h2>\n<p>Em um novo módulo <code>Converter</code>, criaremos as funções responsáveis por converter o <code>Page</code> e <code>PageBlocks</code> para a AST <code>Pandoc</code>.</p>\n<h3 id=\"convertendo-o-tipo-comum-de-texto\">Convertendo o tipo comum de texto</h3>\n<p>Começando em uma parte crucial, vamos criar as funções responsáveis por percorrer o tipo <code>[RichTextItem]</code> do Notion para criar os blocos <code>Inline</code> do Pandoc, responsáveis pela formatação, tal como o negrito, itálico etc.</p>\n<p>Cada item do tipo <code>RichTextItem</code> possui um objeto que informa quais as formatações aplicadas no pedaço do texto do item. O objeto tem o seguinte tipo.</p>\n<pre><code class=\"language-haskell\">data AnnotationResponse = AnnotationResponse\n  { bold :: Bool\n  , italic :: Bool\n  , strikethrough :: Bool\n  , underline :: Bool\n  , mono :: Bool -- originalmente `code` na API\n  , color :: String\n  }\n</code></pre>\n<p>Vamos criar uma função que nos ajude a “anotar” um dado texto com as anotações nos tipos do Pandoc, ou seja, <code>Inline</code> (<a href=\"https://hackage.haskell.org/package/pandoc-types-1.23.1/docs/Text-Pandoc-Definition.html#t:Inline\">referência</a>). A ideia é ser uma função recursiva que a cada propriedade como <code>True</code> encontrada, aplique tal formatação e defina-a como <code>False</code>, chamando a função novamente até que nenhuma formatação necessite ser aplicada.</p>\n<pre><code class=\"language-haskell\">{-# LANGUAGE OverloadedStrings #-}\nmodule Converter where\n\nimport qualified NotionApiTypes as N\nimport qualified Text.Pandoc.Definition as P\n\nannotate :: String -> N.AnnotationResponse -> P.Inline\nannotate txt an | N.bold an = P.Strong [annotate txt (an { N.bold = False })]\n                | N.italic an = P.Emph [annotate txt (an { N.italic = False })]\n                | N.strikethrough an = P.Strikeout [annotate txt (an { N.strikethrough = False })]\n                | N.underline an = P.Underline [annotate txt (an { N.underline = False })]\n                | N.mono an = P.Code P.nullAttr (T.pack txt)\n                | otherwise = P.Str (T.pack txt)\n</code></pre>\n<p>Os textos também podem ter <em>links</em>, então é necessário criar um utilitário para isso.</p>\n<pre><code class=\"language-haskell\">linkify :: Maybe String -> P.Inline -> P.Inline\nlinkify Nothing txt = txt\nlinkify (Just href) txt = P.Link P.nullAttr [txt] (T.pack href, \"\")\n</code></pre>\n<p>O tipo <code>RichTextItem</code> por sua vez é um tipo soma e pode ser tanto um <code>TextRichText</code> quanto um <code>EquationRichText</code>. Podemos agora escrever uma função auxiliar para converter.</p>\n<pre><code class=\"language-haskell\">parseText :: N.Text -> [P.Inline]\nparseText txt = parseInline $ N.txRichText txt\n\nparseInline :: [N.RichTextItem] -> [P.Inline]\nparseInline = map parseRichTextItem\n\nparseRichTextItem :: N.RichTextItem -> P.Inline\nparseRichTextItem (N.TextRichText txt href an _) = linkify href $ annotate txt an\nparseRichTextItem (N.EquationRichText eq _ _ _) = P.Math P.InlineMath $ T.Pack eq\n</code></pre>\n<p>Com isso já é possível converter qualquer texto formatado do Notion para a formatação <code>Inline</code> do Pandoc, que é utilizada em praticamente todos os blocos. A implementação do <code>parseRichTextItem</code> para o tipo <code>EquationRichText</code> ignora a formatação nas anotações por conta que na maioria dos casos os documentos gerados pelo Pandoc não suportam tal recurso, gerando equações parecidas com o LaTeX diretamente.</p>\n<h3 id=\"convertendo-os-blocos-1\">Convertendo os blocos</h3>\n<p>Agora que é possível converter os textos, podemos começar a implementar a conversão dos blocos. Começando pelos tipos mais simples, é fácil utilizar o <em>pattern matching</em> do Haskell para fazer as conversões diretamente, como no código abaixo.</p>\n<pre><code class=\"language-haskell\">notionToPandoc :: N.Block -> P.Block\nnotionToPandoc (N.BlockParagraph pr) = P.Para (parseText pr)\nnotionToPandoc (N.BlockHeading h1) = P.Header 1 P.nullAttr (parseHeading h1)\n-- Alguns blocos omitidos para simplificação.\n-- Casos especiais: bulleted list item e numbered list item.\nnotionToPandoc (N.BlockBulletedListItem bli) = P.Plain (parseText bli)\nnotionToPandoc (N.BlockNumberedListItem nli) = P.Plain (parseText nli)\n-- Outros blocos omitidos.\nnotionToPandoc _ = P.Para [P.Str \"Unsupported block\"]\n</code></pre>\n<p>Aqui vale uma observação em relação aos blocos de lista com marcadores e lista numerada. O Notion não possui um tipo que agrupa esses itens em um único bloco, eles são colocados sequencialmente na lista de blocos. Para isso, na conversão direta dos blocos, esses items são transformados em texto apenas, pois o texto será usado internamente no tipo de <code>BulletList</code> do Pandoc. É necessário, então, uma outra função que fará a conversão da lista e que possa tratar desses casos em particular.</p>\n<pre><code class=\"language-haskell\">nToP :: [N.Block] -> [P.Block]\nnToP [] = []\nnToP ((N.BlockBulletedListItem bli) : bs) = blocks\n  where\n    notionBlock = N.BlockBulletedListItem bli\n    firstItem = notionToPandoc notionBlock\n    nextItems = map ((\\b -> [b]) . notionToPandoc) $ takeWhile isBli\n    finalItems = drop (length nextItems) bs\n    blocks = P.BulletList ([firstItem] : nextItems) : nToP finalItems\nnToP bs = notionToPandoc (head bs) : nToP (drop 1 bs)\n</code></pre>\n<p>A ideia é que a função <code>nToP</code> seja recursivamente chamada para cada bloco na lista, onde ela irá converter o primeiro bloco e concatenar em uma lista com os demais da chamada seguinte. Entretanto, se o primeiro item da lista atual for um bloco do tipo <code>BlockBulletedListItem</code>, será feito a conversão desse bloco e de todos os blocos seguintes na lista que forem desse mesmo tipo, que por sua vez serão adicionados dentro de um <code>BulletList</code> do Pandoc. Os demais itens da lista que não forem desse tipo serão convertidos normalmente pelo último <em>pattern matching</em>.</p>\n<p>Para isso funcionar, é necessário uma função auxiliar que também faz uso do <em>pattern matching</em> para retornar se um dado bloco é do tipo <code>BlockBulletedListItem</code>. Isto é feito pois não há uma maneira mais direta de verificar em tempo de execução se um bloco é de dado tipo.</p>\n<pre><code class=\"language-haskell\">isBli :: N.Block -> Bool\nisBli (N.BlockBulletedListItem _) = True\nisBli _ = FalsefindNextBli _ = []\n</code></pre>\n<p>De modo análogo, pode-se converter os blocos do tipo <code>BlockNumberedListItem</code> também.</p>\n<h3 id=\"criando-a-ast\">Criando a AST</h3>\n<p>Com a conversão dos blocos implementada, podemos partir para a etapa final da criação da AST.</p>\n<pre><code class=\"language-haskell\">createAst :: N.Page -> [N.Block] -> P.Pandoc\ncreateAst page bks = P.Pandoc meta children\n  where\n    meta = createMeta pageProperties\n    titleProp = N.propName $ N.pageProperties page\n    slugH1 = slugify $ T.pack $ stripPlainText $ N.tpTitle titleProp\n    header1 = P.Header 1 (slugH1, [], []) (parseInline $ N.tpTitle titleProp)\n    children = header1 : createChildren bks\n</code></pre>\n<p>A função <code>createAst</code> faz uso de duas funções auxiliares, uma responsável por criar os metadados do documento e outra por converter os blocos.</p>\n<pre><code class=\"language-haskell\">createMeta :: N.Page -> P.Meta\ncreateMeta (N.Page _ properties date) = P.Meta { P.unMeta = fromList metadata }\n  where\n    titleProp = N.propName properties\n    title = T.pack $ stripPlainText $ N.tpTitle titleProp\n    metadata = [\n      (\"title\", P.MetaInlines [P.Str title]),\n      (\"date\", P.MetaInlines [P.Str $ T.pack date])\n      ]\n\ncreateChildren :: [N.Block] -> [P.Block]\ncreateChildren = nToP\n</code></pre>\n<p>A função de criação da AST, além de converter os blocos, também adiciona um cabeçalho de nível 1 no começo da lista, que sempre será o título da página no Notion, obtido das propriedades. Tendo a AST do tipo <code>Pandoc</code>, fica fácil converter e criar um arquivo Markdown.</p>\n<h2 id=\"criando-o-markdown\">Criando o Markdown</h2>\n<p>Em um novo módulo <code>Formats</code>, vamos implementar uma função auxiliar para escrever a conversão do tipo <code>Pandoc</code> em um arquivo Markdown no disco.</p>\n<pre><code class=\"language-haskell\">module Formats where\n\nimport Text.Pandoc\nimport qualified Data.Text as T\n\nmdOptions :: WriterOptions\nmdOptions = def { writerExtensions = githubMarkdownExtensions }\n\nsaveMarkdown :: String -> Pandoc -> IO ()\nsaveMarkdown fileName doc = do\n  result &#x3C;- runIO $ writeMarkdown mdOptions doc\n  md &#x3C;- handleError result\n  writeFile fileName $ T.unpack md\n</code></pre>\n<p>A função <code>saveMarkdown</code> executa no contexto <code>IO</code> a conversão da AST para ou o conteúdo final do arquivo ou um erro de conversão. Caso tudo tenha dado certo, o conteúdo é escrito no arquivo especificado por <code>fileName</code>. As opções <code>mdOptions</code> utilizam as extensões do formato <em>GitHub Flavored Markdown</em>, comumente adotado por diversos sites.</p>\n<h2 id=\"juntando-tudo\">Juntando tudo</h2>\n<p>No módulo <code>Main</code>, agora torna-se fácil juntar tudo e fazer a conversão propriamente dita.</p>\n<pre><code class=\"language-haskell\">module Main (main) where\n\nimport NotionApi\nimport Converter\nimport Formats\n\ntutorialPageId :: String\ntutorialPageId = \"7b85035517424c72ab58af014fecf6bc\"\n\nmain :: IO ()\nmain = do\n  page &#x3C;- retrieveNotionPage tutorialPageId\n  blocks &#x3C;- retrieveAllBlockChildren tutorialPageId\n\n  let ast = createAst page blocks\n  saveMarkdown \"page.md\" ast\n</code></pre>\n<p>A função <code>main</code> segue o passo-a-passo determinado anteriormente e escreve corretamente o arquivo em Markdown. Como um exemplo, pode-se observar abaixo a conversão do início do texto deste tutorial:</p>\n<pre><code class=\"language-markdown\"># Convertendo páginas do Notion para Markdown usando Haskell\n\nO [Notion](https://notion.so/) possui um ótimo editor WYSIWYG \n(*What You See Is What You Get*), mas por baixo dos panos todos \nos blocos são armazenados individualmente e o serviço não oferece\numa API que permita recuperar o conteúdo inteiro de uma página em\nparticular em formatos mais abertos, tais como o \n[Markdown](https://www.markdownguide.org/getting-started/).\n</code></pre>\n<h2 id=\"conclusão\">Conclusão</h2>\n<p>O sistema de tipos do Haskell é uma ferramenta poderosa que possibilitou a implementação de um conversor com um código com uma ótima legibilidade e com uma ótima performance. Ao ter uma AST do Pandoc, torna-se possível converter para qualquer formato que a biblioteca suporte, possibilitando um acesso fácil ao conteúdo da página no formato desejado no momento. Apesar de possuir ainda alguns recursos a serem implementados para um melhor aproveitamento da página, como usar as propriedades da página para gerar o <em>frontmatter</em> do Markdown, a abordagem se mostra como promissora para usos futuros.</p>\n<h2 id=\"bibliografia\">Bibliografia</h2>\n<ul>\n<li>ZAKHARYASCHEV, Ivan. Resposta para “<em>Haskell :: Aeson :: parse ADT based on field value</em>”. Stack Overflow, disponível <a href=\"https://stackoverflow.com/a/29286213\">neste link</a>.</li>\n<li>O’SULLIVAN, Bryan. <em>Data.Aeson</em>. Hackage, disponível <a href=\"https://hackage.haskell.org/package/aeson-2.2.1.0/docs/Data-Aeson.html\">neste link</a>.</li>\n<li>RADEMACHER, Andrew. <em>Data.Aeson.Casing</em>. Hackage, disponível <a href=\"https://hackage.haskell.org/package/aeson-2.2.1.0/docs/Data-Aeson.html\">neste link</a>.</li>\n<li>YAO, William. <em>A cheatsheet to JSON handling with Aeson</em>. WilliamYaoh.com, disponível <a href=\"https://williamyaoh.com/posts/2019-10-19-a-cheatsheet-to-json-handling.html\">neste link</a>.</li>\n<li>PANDOC. <em>Using the Pandoc API</em>. Pandoc.org, disponível <a href=\"https://pandoc.org/using-the-pandoc-api.html#a-simple-example\">neste link</a>.</li>\n<li>PANDOC. <em>Pandoc Filters</em>. Pandoc.org, disponível <a href=\"https://pandoc.org/filters.html#a-simple-example\">neste link</a>.</li>\n<li>MACFARLANE, John. <em>Text.Pandoc.Definition</em>. Hackage, disponível <a href=\"https://hackage.haskell.org/package/pandoc-types-1.23.1/docs/Text-Pandoc-Definition.html#t:Caption\">neste link</a>.</li>\n<li>SCHOOL OF HASKELL. <em>Parsing JSON with Aeson</em>. School of Haskell, disponível <a href=\"https://www.schoolofhaskell.com/school/starting-with-haskell/libraries-and-frameworks/text-manipulation/json\">neste link</a>.</li>\n<li>UFABC. <em>Grupo de estudos em Haskell da UFABC</em>. Pesquisa UFABC, disponível <a href=\"https://haskell.pesquisa.ufabc.edu.br/\">neste link</a>.</li>\n</ul>","date_published":"2024-03-12T00:00:00.000Z","date_modified":"2024-03-12T00:00:00.000Z","tags":["Programação"],"language":"pt-BR"},{"id":"gerando-imagens-do-open-graph-automaticamente-com-o-satori","url":"https://alessandrojean.github.io/post/gerando-imagens-do-open-graph-automaticamente-com-o-satori","title":"Gerando imagens do Open Graph automaticamente com o Satori","summary":"Uma breve introdução a mais nova biblioteca da Vercel que permite a criação de imagens a partir de um componente React.","content_html":"<div><p><strong>Nota:</strong></p><p>Este artigo foi escrito quando o NuxtJS v3 ainda estava no estado de <em>release candidate</em>, fazendo com que muitas etapas do processo tivessem de ser feitas de modo manual, como depender de um módulo customizado.</p><p>Atualmente, há um módulo de terceiros criado justamente para este intuito, o <a href=\"https://github.com/harlan-zw/nuxt-og-image\">nuxt-og-image</a>, que também usa o Satori e torna o processo muito mais simples e evita complicações desnecessárias, tais como escrever o <em>template</em> como um componente React dentro de um projeto Vue.js. Recomendo atualmente usar este módulo.</p></div>\n<p>Compartilhamento em redes sociais são uma parte importante de todo acesso que os sites possuem, e disponibilizar uma imagem como parte dos atributos do Open Graph acaba se tornando cada vez mais primordial.</p>\n<p>Pensando em facilitar o processo de geração destas imagens com base em algum conteúdo fixo, a Vercel acabou disponibilizando recentemente o <a href=\"https://github.com/vercel/satori\">Satori</a>, uma biblioteca que permite a criação de arquivos SVG a partir de um componente React.</p>\n<p>Apesar de ser mais ligado ao ecossistema do Next.js e, por consequência, ao React, você pode utilizá-lo em qualquer projeto, seja por usar o JSX ou TSX, ou utilizar a sintaxe de objetos do React.</p>\n<figure><img src=\"https://alessandrojean.github.io/posts/2022/2022-10-17-gerando-imagens-do-open-graph-automaticamente-com-o-satori/fea56e91-350e-4495-8a69-21bf4c37f91f.png\" alt=\"Um exemplo criado no Vercel OG Image Playground\"><figcaption>Um exemplo criado no </figcaption></figure>\n<h2 id=\"conteúdo\">Conteúdo</h2>\n<ol>\n<li><a href=\"#um-exemplo-simples\">Um exemplo simples</a></li>\n<li><a href=\"#utilizando-o-tailwind-css\">Utilizando o Tailwind CSS</a></li>\n<li><a href=\"#como-criei-a-gera%C3%A7%C3%A3o-das-imagens-no-nuxtjs\">Como criei a geração das imagens no NuxtJS</a></li>\n<li><a href=\"#conclus%C3%B5es\">Conclusões</a></li>\n<li><a href=\"#refer%C3%AAncias\">Referências</a></li>\n</ol>\n<h2 id=\"um-exemplo-simples\">Um exemplo simples</h2>\n<p>O uso da biblioteca é bem simples, mas tem algumas obrigatoriedades. Se você pretende escrever algum texto, é necessário que você providencie <em>Buffers</em> para as fontes que deseja que sejam utilizadas, portanto, você precisará fazer alguma requisição extra ou possuir os arquivos localmente. No lado do servidor, torna-se relativamente simples esta última opção.</p>\n<pre><code class=\"language-ts\">import { readFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport type { SatoriOptions } from 'satori'\n\ntype FontOptions = SatoriOptions['fonts'][number]\n\nasync function loadFonts(): FontOptions[] {\n  const fontsPath = join(__dirname, 'fonts')\n  \n  return [{\n    name: 'Inter',\n    data: await readFile(join(fontsPath, 'Inter-Regular.otf')),\n    weight: 400,\n    style: 'normal',\n  }]\n}\n</code></pre>\n<p>Tendo as fontes carregadas, usar o Satori é bem fácil. A chamada da função requer dois parâmetros: um objeto <code>ReactNode</code> (ou o JSX/TSX direto se você configurou no seu transpilador), e as opções de renderização, onde será passado as fontes também.</p>\n<pre><code class=\"language-tsx\">import satori from 'satori'\n\nasync function createSvg(title: string): string {\n  const component = (\n    &#x3C;div\n      style={{\n        height: '100%',\n        width: '100%',\n        display: 'flex',\n        backgroundColor: 'white',\n        alignItems: 'center',\n        justifyContent: 'center'\n      }}\n    >\n      { title }\n    &#x3C;/div>\n  )\n\n  const options: SatoriOptions = {\n    width: 800,\n    height: 400,\n    fonts: await loadFonts()\n  }\n\n  return await satori(component, options)\n}\n</code></pre>\n<p>Com o SVG, basta utilizar alguma biblioteca como o Sharp para converter para um formato aceito pelas especificações do Open Graph, seja PNG ou JPG.</p>\n<pre><code class=\"language-tsx\">import { writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport sharp from 'sharp'\n\nasync function saveImage(svg: string) {\n  const imagePath = join(__dirname, 'image.png')\n  const image = await sharp(Buffer.from(svg))\n    .png({ quality: 90 })\n    .toBuffer()\n\n  await writeFile(imagePath, image)\n}\n</code></pre>\n<h2 id=\"utilizando-o-tailwind-css\">Utilizando o Tailwind CSS</h2>\n<p>Convenientemente, o Satori também suporta alguns dos utilitários do Tailwind CSS. Para utilizá-los, basta aplicar as classes nos elementos através do atributo <code>tw</code> ao invés do <code>className</code>.</p>\n<pre><code class=\"language-tsx\">const component = (\n  &#x3C;div tw=\"bg-white w-full h-full flex items-center justify-center font-bold text-4xl\">\n    Hello, world!\n  &#x3C;/div>\n)\n</code></pre>\n<p>Apesar de poder utilizar o Tailwind permitir e facilitar muitas abordagens, atente-se que o Satori possui restrições de quais propriedades podem ser utilizadas no CSS. Para mais informações, consulte a <a href=\"https://github.com/vercel/satori#css\">documentação</a> no GitHub.</p>\n<h2 id=\"como-criei-a-geração-das-imagens-no-nuxtjs\">Como criei a geração das imagens no NuxtJS</h2>\n<p>A criação das imagens deste site é feita a partir de um módulo customizado para o NuxtJS. O procedimento que acabei implementando é utilizar no ambiente de <em>prerender</em> o hook <code>prerender:generate</code> para obter informações da rota que acabou de ser gerada com seu HTML.</p>\n<p>Por sua vez, utilizo o pacote <code>open-graph-scraper</code> para fazer o <em>parse</em> desse HTML e me retornar todas as informações do Open Graph já declaradas previamente na página. É possível então extrair informações como o título, a descrição, a seção e data de publicação, repassados para o Satori no meio do template.</p>\n<p>Apesar do Vite suportar a sintaxe do JSX/TSX, eu não consegui configurar corretamente a transpilação, então acabei optando por criar os objetos manualmente mesmo. Cada rota gera no final uma imagem no formato PNG, e a <em>tag</em> <code>&#x3C;meta></code> de <code>og:image</code> em ambiente de <em>prerender</em> ou produção aponta para ela. O nome do arquivo é gerado automaticamente a partir do <code>pathname</code> da rota, substituindo todas as barras por traços.</p>\n<figure><img src=\"https://alessandrojean.github.io/posts/2022/2022-10-17-gerando-imagens-do-open-graph-automaticamente-com-o-satori/teste.webp\" alt=\"Imagem gerada automaticamente para este post, seguindo o template que eu defini.\"><figcaption>Imagem gerada automaticamente para este post, seguindo o </figcaption></figure>\n<p>Adicionalmente eu fiz o processo ser pulado se é encontrada alguma <em>tag</em> <code>&#x3C;meta></code> de <code>og-image:skip</code> com valor <code>true</code>. Ela é útil em casos onde eu quero definir alguma imagem customizada para alguma rota ou post.</p>\n<h2 id=\"conclusões\">Conclusões</h2>\n<p>O Satori abre várias possibilidades de automação da geração de imagens de compartilhamento. Apesar de ser criado em mente para ser usado em um ambiente Next.js ou React, nada impede de ser utilizado em outros <em>frameworks</em>, como o NuxtJS ou Vue.js. Inclusive, imagino que eventualmente deva surgir alguma biblioteca inspirada baseada no Vue, e que tornará seu uso no NuxtJS muito mais fácil. Quem sabe em breve não teremos um <code>@nuxtjs/og-image</code> 😁.</p>\n<p>Ainda preciso melhorar as imagens geradas do site, mas acredito que os resultados iniciais foram muito satisfatórios e estou realmente feliz com eles. Só de não ter que ter o trabalho de gerar uma imagem manualmente para cada novo post já tira uma etapa a mais do processo de publicação e me deixa realmente aliviado.</p>\n<h2 id=\"referências\">Referências</h2>\n<ul>\n<li>“<a href=\"https://vercel.com/blog/introducing-vercel-og-image-generation-fast-dynamic-social-card-images\"><em>Introducing OG Image Generation: Fast, dynamic social card images at the Edge</em></a>”</li>\n<li><a href=\"https://og-playground.vercel.app/\">Vercel OG Image Playground</a></li>\n<li><a href=\"https://github.com/vercel/satori\">Repositório no GitHub do Satori</a></li>\n</ul>","date_published":"2022-10-17T00:00:00.000Z","date_modified":"2022-10-17T00:00:00.000Z","tags":["Programação"],"language":"pt-BR"},{"id":"o-desafio-de-usar-a-api-oficial-do-notion-com-nuxtjs-v3","url":"https://alessandrojean.github.io/post/o-desafio-de-usar-a-api-oficial-do-notion-com-nuxtjs-v3","title":"O desafio de usar a API oficial do Notion com NuxtJS v3","summary":"Como tentar utilizar a API oficial do Notion acabou gerando uma dor de cabeça extra por conta do NuxtJS v3 e como consegui contornar este problema.","content_html":"<p>Este texto pretende ser uma continuação direta do meu <a href=\"https://alessandrojean.github.io/post/utilizando-o-notion-como-um-cms\">texto anterior</a> onde explico um pouco como reescrevi o código do site para utilizar o Notion como o CMS dos artigos.</p>\n<p>Recapitulando um pouco, eu estava usando a API não oficial do Notion disponibilizada pela equipe da Splitbee. Originalmente ela foi criada quando a API oficial ainda estava fechada para testes e possui algumas limitações, como ser obrigatório que seus bancos de dados sejam públicos. Apesar de funcionar bem, sendo sincero, depois de um tempo eu fiquei um pouco receoso de utilizar e também percebi que por conta do <em>cache</em> configurado nela, novas alterações e páginas demoravam um pouco para aparecer lá, então aconteceu algumas vezes de eu precisar alterar alguma coisa e dar um <em>rebuild</em> no site e as alterações não estarem lá.</p>\n<p>Muito bem, com essas limitações da API da Splitbee, resolvi me aventurar em utilizar a API oficial, apesar dos problemas mencionados anteriormente. Comecei seguindo o guia disponibilizado no site de desenvolvedores do Notion e criei uma integração, o que me gerou uma chave de acesso. Também ativei a integração nos bancos de dados e páginas que ela deveria ter acesso.</p>\n<p>Fazer as chamadas a API em um ambiente Node.js se torna relativamente fácil já que existe um cliente oficial, o <code>@notionhq/client</code>, que já possui todas as tipagens em TypeScript, o que facilitou bastante o trabalho. Entretanto, consumir a estrutura de dados retornada se tornou um pouco trabalhoso.</p>\n<h2 id=\"a-tão-temida-recursividade\">A tão temida recursividade</h2>\n<p>A versão atual da API não possui nenhum <em>endpoint</em> que retorna todos os blocos que uma página possui, já que é paginada e requer algumas chamadas para obter tudo o que você precisa. Não bastasse esse problema, o conceito de blocos do Notion permite que alguns tipos deles possam ter outros blocos dentro, e novamente, a API não retorna eles diretamente neste <em>endpoint</em>. Para cada bloco que possui “filhos”, você precisa chamar outro <em>endpoint</em> individualmente.</p>\n<p>Eu poderia ter abordado esta obtenção dos dados com um algoritmo recursivo, mas acabei optando por utilizar uma fila. A cada resposta da API eu percorro a lista de blocos retornados e os que tem filhos eu adiciono o <code>id</code> deles na fila. Enquanto a fila não está vazia, o ciclo se repete, fazendo mais chamadas a API e adicionando mais elementos na fila.</p>\n<p>O <em>endpoint</em> de blocos retorna um vetor de objetos. Para facilitar um pouco o acesso a eles posteriormente, no final eu optei por transformar em uma tabela <em>hash</em> igual à API não oficial.</p>\n<pre><code class=\"language-ts\">async function fetchBlocksFromPage(pageId: string) {\n  // Obtém a primeira página de blocos da API.\n  const firstPage = await notion.blocks.children.list({\n    block_id: page.id\n  })\n\n  const blocks = firstPage.results as BlockWithContent[]\n  let nextCursor = firstPage.next_cursor\n\n  // Enquanto há uma próxima página, novas chamadas são feitas.\n  while (nextCursor) {\n    const result = await notion.blocks.children.list({\n      block_id: page.id,\n      start_cursor: next_cursor\n    })\n\n    // No fim todos os blocos no primeiro nível estarão em blocks.\n    blocks.push(...(result.results as BlockWithContent[]))\n    nextCursor = result.next_cursor\n  }\n\n  // Lista de blocos com filhos a serem visitados.\n  const blocksWithChildren = [...blocks.entries()]\n    .filter(([_, block]) => block.has_children)\n    .map(([i, block]) => ({ i, id: block.id }))\n\n  // Enquanto há blocos a serem visitados.\n  while (blocksWithChildren.length > 0) {\n    // Desenfileira o primeiro bloco da fila.\n    const { i, id: current } = blocksWithChildren.shift()\n    \n    const result = await notion.blocks.children.list({\n      block_id: current\n    })\n\n    const children = result.results as BlockWithContent[]\n    \n    const newBlocksToVisit = [...children.entries()]\n      .filter(([_, block]) => block.has_children)\n      .map(([i, block]) => ({ i: blocks.length + i, id: block.id }))\n\n    blocksWithChildren.push(...newBlocksToVisit)\n    blocks.push(...children)\n    blocks[i].content = children.map((child) => child.id)\n  }\n\n  // Transforma a lista de blocos em uma \"tabela hash\".\n  return Object.fromEntries(blocks.map((b) => [b.id, b]))\n}\n</code></pre>\n<h3 id=\"uma-informação-sobre-as-tipagens\">Uma informação sobre as tipagens</h3>\n<p>Aqui vale a pena ressaltar sobre o tipo <code>BlockWithContent</code>. Por padrão, nas tipagens do <code>@notionhq/client</code>, os blocos do tipo <code>BlockObjectResponse</code> não possuem uma propriedade <code>content</code>, então eu criei um tipo novo baseado nele com esta nova propriedade.</p>\n<pre><code class=\"language-ts\">export type BlockWithContent = BlockObjectResponse &#x26; { content?: string[] }\n</code></pre>\n<p>Outra observação importante talvez, mas nas versões atuais do cliente do Notion, por algum motivo os tipos não são exportados e não podem ser importados diretamente. Para poder acessá-las, precisei importar diretamente da pasta <code>build/src</code> do pacote. Provavelmente não é o método mais ideal, mas funciona enquanto eles não atualizarem o pacote para corrigir isso.</p>\n<pre><code class=\"language-ts\">import type * as NotionApi from '@notionhq/client/build/src/api-endpoints'\n</code></pre>\n<p>Para facilitar o acesso nos demais pontos do código, também exportei para não ter que ficar fazendo a importação de dentro do pacote do cliente em vários lugares, até para centralizar tudo.</p>\n<pre><code class=\"language-ts\">export * from '@notionhq/client/build/src/api-endpoints'\n</code></pre>\n<h3 id=\"a-velocidade-de-resposta\">A velocidade de resposta</h3>\n<p>Você já deve ter imaginado, mas a quantidade excessiva de chamadas a API do Notion obviamente iria ter algum efeito colateral. Dependendo do tamanho e complexidade da página que você está querendo obter os blocos, a API do site (disponível somente durante o <em>prerender</em> e em desenvolvimento local) demora um certo tempo. Artigos extensos como o  “Lendo mangás no Kindle” em alguns casos chegam a levar de 10 a 15 segundos para carregar.</p>\n<p>Isto não é tanto um problema no meu caso já que o site é estático e a API só é chamada durante o <em>build</em> e <em>prerender</em>, mas para uma aplicação real com <em>Server Side Rendering</em>, isto poderia ser bastante problemático. O ideal neste outro caso seria configurar algum mecanismo de <em>cache</em>.</p>\n<p>Além disso, muito provavelmente a API do Notion possui algum <em>rate limiting</em> configurado, mas não cheguei a esbarrar nele ainda. Pode ser um eventual problema futuro que terei que solucionar implementando um <em>rate limiting</em> localmente também.</p>\n<h2 id=\"consumindo-os-arquivos-de-mídia\">Consumindo os arquivos de mídia</h2>\n<p>Não vou me adentrar no tópico da renderização dos blocos, pois só precisei fazer algumas alterações no <code>NotionRenderer.vue</code>, já que a base já estava pronta. Entretanto, os blocos de imagem e vídeo acabaram se tornando uma dor de cabeça bem chata.</p>\n<p>Para quaisquer arquivos hospedados no Notion, a API retorna uma URL com expiração de acesso de uma hora. Isso acaba se tornando um problema, pois se durante o <em>build</em> as URLs temporárias forem utilizadas, em pouquíssimo tempo elas ficarão indisponíveis e os usuários não poderão mais vê-las. A solução para isso seria extrair essas imagens no processo de <em>prerender</em> e copiá-las para a pasta <code>public</code>, assim elas farão parte do <em>build</em> do site.</p>\n<p>O <a href=\"https://v1.image.nuxtjs.org/\">Nuxt Image</a> é um módulo oficial do NuxtJS que já se encarrega de fazer isso, porém este suporte a pré-renderização ainda não foi adicionado na <code>v1</code>, compatível com o NuxtJS v3. A solução então seria por enquanto eu criar meu próprio módulo local, responsável em alguma maneira de extrair as imagens dos artigos e copiá-las para a pasta <code>public</code>.</p>\n<p>Eu demorei cerca de uns dois dias tentando diversas abordagens de como fazer isso, e sempre acaba esbarrando em alguma limitação do <a href=\"https://nitro.unjs.io/\">Nitro</a> ou do <a href=\"https://github.com/unjs/h3\">h3</a>, pacotes que o NuxtJS usa por baixo dos panos para o servidor e para o <em>prerender</em>. Era frustrante sempre que eu parecia estar fazendo progresso, algo do NuxtJS ou dessas bibliotecas não tinha sido completamente implementado, como diversos <em>hooks</em> que estão documentados, mas não são chamados em nenhum lugar.</p>\n<p>A solução que eu acabei encontrando foi disponibilizar um <em>event handler</em> customizado no módulo que força rotas da API no servidor a implementar também um método que devolve as imagens de uma dada resposta que devem ser salvas. Assim, a cada página que o Nitro visita durante o <em>prerender</em>, as imagens são extraídas e adicionadas numa fila global ao processo. Quando cada página é terminada de ser renderizada, as imagens são copiadas localmente. Nesta etapa eu inclusive aproveitei para utilizar o <a href=\"https://sharp.pixelplumbing.com/\">Sharp</a> e as convertê-las para <code>webp</code>.</p>\n<pre><code class=\"language-ts\">export function addToQueue(result: ExtractorResult) {\n  (global.mediaExtractorQueue as ExtractorResult[])?.push(result)\n}\n\nexport function defineExtractorEventHandler&#x3C;T = any>(args: ExtractorEventHandlerArgs&#x3C;T>): EventHandler&#x3C;T> {\n  const { handler, extract } = args\n\n  return defineEventHandler(async (event) => {\n    const result = await handler(event)\n    const extracted = await extract(result)\n\n    // TODO: Check if is the prerender env.\n    addToQueue(extracted)\n\n    return result\n  })\n}\n</code></pre>\n<p>No lado do cliente, eu faço uma verificação do ambiente atual e transformo a URL do arquivo bloco. Em casos de desenvolvimento, exibo a imagem diretamente da URL temporária do Notion, mas em produção eu mapeio para a imagem copiada. Todas as imagens são salvas no formato <code>[id-do-bloco]-[data-de-modificação].webp</code>, então é fácil o cliente poder criar a URL correta já que ele tem tudo o que é preciso a partir das propriedades do bloco.</p>\n<p>Como vantagem de ter criado este módulo próprio, também consegui copiar os vídeos, o que não seria possível se eu estivesse utilizando o Nuxt Image. Futuramente se precisar copiar outros arquivos que o Notion deixa serem hospedados, como PDFs, também será facilmente possível com menores alterações no código.</p>\n<h2 id=\"conclusões\">Conclusões</h2>\n<p>Consumir a API oficial do Notion é relativamente simples se você deixar um pouco de lado a paginação e quantidade excessiva de chamadas necessárias. Os problemas que eu esbarrei foram por conta do ecossistema do NuxtJS v3 que, no momento, ainda se encontra em <em>release candidate</em>, então muitos dos módulos oficiais ainda não foram atualizados. Provavelmente se você é mais adepto a utilizar outros frameworks como o <a href=\"https://nextjs.org/\">Next.js</a>, terá uma facilidade bem maior, pois ele te oferecerá um ecossistema mais estável.</p>\n<p>Minha próxima aventura agora será tentar gerar as imagens do Open Graph automaticamente, similar a como o GitHub faz quando você compartilha um repositório no Twitter e uma imagem com o título, descrição e outras coisas é gerada automaticamente.</p>\n<p>Te vejo numa próxima e obrigado por ler! ✌🏻</p>","date_published":"2022-10-15T00:00:00.000Z","date_modified":"2022-10-15T00:00:00.000Z","tags":["Programação"],"language":"pt-BR"},{"id":"utilizando-o-notion-como-um-cms","url":"https://alessandrojean.github.io/post/utilizando-o-notion-como-um-cms","title":"Utilizando o Notion como um CMS","summary":"Uma breve análise de como consegui fazer do Notion a ferramenta que uso para escrever e editar os artigos e outras seções deste site estático.","content_html":"<p>Uma das coisas que pessoalmente me causava um pouco de cansaço quando eu utilizava o Jekyll como o gerador das duas versões anteriores do meu site era ter que lidar com todo o processo de publicação manualmente por <em>commits</em> e arquivos <a href=\"https://pt.wikipedia.org/wiki/Markdown\">Markdown</a> no repositório no GitHub.</p>\n<p>Não me entenda mal, eu pessoalmente gosto muito da linguagem Markdown e da facilidade que ela proporciona para escrever e focar no conteúdo, mas ao longo do tempo neste projeto em específico eu fui sentindo falta de uma facilidade maior de visualizar o resultado e usar um editor <a href=\"https://pt.wikipedia.org/wiki/WYSIWYG\">WYSIWYG</a> (<em>What You See Is What You Get</em>). Tudo bem que existem ótimos programas para isso focados em Markdown, como o <a href=\"https://typora.io/\">Typora</a>, mas nunca consegui me adaptar direito.</p>\n<p>Um dia desses eu estava navegando no Twitter e me deparei com um <em>tweet</em> do <a href=\"https://twitter.com/adamwathan\">Adam Wathan</a>, um dos criadores do <a href=\"https://tailwindcss.com\">Tailwind CSS</a>, comentando sobre a tentativa dele de usar o Notion como o CMS do <a href=\"https://adamwathan.me/\">site dele</a>, e que infelizmente ele não conseguiu seguir dado a algumas limitações e a forma com que a API oficial e pública do Notion é estruturada.</p>\n<blockquote cite=\"https://x.com/adamwathan/status/1483114773235609607\"><p>Brincando um pouco com usar o Notion como um CMS para o meu site pessoal para reduzir a burocracia de publicação. É bem divertido, mas toda a abstração em ‘blocos’ e as coisas de paginação fazem ser um trabalho enorme só para extrair um conteúdo básico escrito pela API.</p><cite>— Adam Wathan, Criador do Tailwind CSS</cite></blockquote>\n<p>Um dos principais problemas é não haver (até o momento) um <em>endpoint</em> que retorne todos os blocos de uma vez ou, ainda melhor, retorne toda a página como um Markdown direto que possa ser utilizado para gerar o HTML final da página no site. Eu também cheguei a cogitar utilizar a <a href=\"https://developers.notion.com/\">API oficial do Notion</a>, mas esbarrei nos exatos mesmos problemas: também achei muito excessivo a quantidade de chamadas a API que eu deveria fazer para conseguir obter todos os blocos de uma determinada página.</p>\n<blockquote cite=\"https://x.com/adamwathan/status/1483174899141287940\"><p>Certo, estou desistindo disso, é muito a se preocupar com o número de chamadas a API, <em>rate limiting</em>, transformar a estrutura de dados complexa que recebo de volta etc. Ainda gosto do Notion como Notion porém.</p><cite>— Adam Wathan, Criador do Tailwind CSS</cite></blockquote>\n<p>Felizmente consegui contornar este problema.</p>\n<h2 id=\"uma-alternativa-a-api-oficial\">Uma alternativa a API oficial</h2>\n<p>Um certo tempo antes do pessoal do Notion disponibilizar a API pública, a equipe da <a href=\"https://splitbee.io/\">Splitbee</a> criou uma API que utiliza os conceitos de <em>serverless functions</em> e permite acessar os conteúdos de tabelas e páginas públicas, a <a href=\"https://github.com/splitbee/notion-api-worker\">notion-api-worker</a>, que está disponível para uso gratuito.</p>\n<p>A API da Splitbee é muito mais prática de ser consumida e não esbarra nos problemas de paginação e nem de quantidade de requisições, ela retorna todas as páginas de uma tabela de uma vez, assim como todos os blocos de uma página.</p>\n<pre><code class=\"language-json\">[\n  {\n    \"id\": \"e649eca2-7309-46fc-9ca2-c246f1108dec\",\n    \"Tags\": [\"notion\", \"cms\", \"nuxtjs\", \"vue.js\", \"site\"],\n    \"Area\": \"Programação\",\n    \"Created at\": \"2022-10-11\",\n    \"Description\": \"Uma breve análise de como consegui fazer do Notion a ferramenta que uso para escrever e editar os artigos e outras seções deste site estático.\",\n    \"Slug\": \"utilizando-o-notion-como-um-cms\",\n    \"Name\": \"Utilizando o Notion como um CMS\"\n  }\n]\n</code></pre>\n<p>O retorno é exatamente igual às propriedades que você configura no seu banco de dados do Notion, incluindo os exatos mesmos nomes, usados como as chaves do objeto.</p>\n<figure><img src=\"https://alessandrojean.github.io/posts/2022/2022-10-11-utilizando-o-notion-como-um-cms/Untitled.png\" alt=\"Propriedades da minha tabela de posts.\"><figcaption>Propriedades da minha tabela de posts.</figcaption></figure>\n<p>Certo, então com este <em>endpoint</em> é relativamente fácil criar uma página que consuma os resultados e crie uma lista com todos os posts marcados como públicos (utilizando a propriedade <code>Public</code> que eu criei na tabela). Esta parte eu consegui fazer rapidamente no site sem maiores problemas de fato. Em compensação, a parte de montar a página de cada post foi bem mais complicada.</p>\n<h2 id=\"montando-as-páginas-a-partir-dos-blocos\">Montando as páginas a partir dos blocos</h2>\n<p>Se por um lado o retorno do <em>endpoint</em> de páginas da tabela foi bem fácil de ser consumido, o dos blocos de uma página se tornou um verdadeiro pesadelo inicialmente para mim. A resposta que a API do Splitbee retorna é um objeto que é uma espécie de <code>HashMap</code> que relaciona o UUID de cada bloco com seu conteúdo. Seria relativamente fácil de consumir, mas como há alguns tipos especiais de blocos que possuem outros blocos dentro, você acaba tendo que utilizar alguma abordagem recursiva, já que o <code>HashMap</code> no fim acaba se tornando uma <a href=\"https://pt.wikipedia.org/wiki/%C3%81rvore_(estrutura_de_dados)\">árvore</a>.</p>\n<pre><code class=\"language-json\">{\n\t\"e649eca2-7309-46fc-9ca2-c246f1108dec\": {\n    \"role\": \"reader\",\n    \"value\": {\n      \"id\": \"e649eca2-7309-46fc-9ca2-c246f1108dec\",\n      \"version\": 301,\n      \"type\": \"page\",\n      \"properties\": {},\n      \"content\": [\n        \"87dada78-c288-4a57-8dd0-c1dd219bbbfd\",\n        \"5238f6e5-7fdd-4e9c-8f33-0e9d095aa7c7\",\n        \"4c34f0e6-9d84-4032-af4a-a60df04a44af\",\n        \"a5fbddb1-a537-40b4-b991-837e3dc881bc\",\n        \"025eed20-014b-4ed4-8d3c-c7013a05bfcc\",\n        \"255b9a9b-b8dd-404f-82a1-7f6dfb14d0cc\",\n        \"a9036174-b836-4a78-aa2b-65a2ac6b8a85\",\n        \"04d9f5ae-4d69-4d2e-a5d9-240839a45d2a\",\n        \"56044e6e-6885-4d20-90bc-1b6544cc935c\",\n        \"2a5fe49d-a944-4c57-b3d8-a69876dfc5cd\",\n        \"e60e0fae-7fe3-4106-ae76-b60d4502b32c\",\n        \"ce9a602c-4a9c-4956-a2a3-32a94656382f\",\n        \"7ecb5844-c15e-4df0-b318-77b1da7c4906\",\n        \"f78130bb-271b-48d3-b36b-9671e8d18860\"\n      ],\n      \"created_time\": 1665539280000,\n      \"last_edited_time\": 1665541500000,\n      \"parent_id\": \"dcfb4c07-20c3-43b6-9c0c-ee5f13822d80\",\n      \"parent_table\": \"collection\",\n    }\n  },\n}\n</code></pre>\n<p>Eu tentei evitar ao máximo ter que montar manualmente a página, com certeza alguém já deve ter feito isso antes e eu não precisaria reinventar a roda. De fato, já existe um componente para Vue.js chamado <code>vue-notion</code>, criado pelo <a href=\"https://github.com/janniks\">janniks</a>, que inclusive já tem suporte para Nuxt JS e foi originalmente inspirado no <code>react-notion</code>, criado pela Splitbee.</p>\n<p>Maravilha, então, é só utilizá-lo com algumas estilizações de CSS pelo Tailwind e eu teria acabado rapidamente o desenvolvimento das páginas, certo?</p>\n<p>Infelizmente não 😢. O <code>vue-notion</code> ainda não é compatível com o Vue.js v3 e muito menos com o Nuxt JS 3, que trouxeram diversas modificações em suas APIs. Gostaria que fosse só esse o problema, mas ele também acaba utilizando o <a href=\"https://prismjs.com/\">Prism.js</a> para os blocos de código. Não que o Prism.js seja ruim, mas eu já fiquei um pouco cansado dele desde as outras versões do site e também queria testar algo novo, o <a href=\"https://shiki.matsu.io/\">Shiki</a>, que utiliza o <a href=\"https://github.com/microsoft/vscode-oniguruma\">vscode-oniguruma</a> e o <a href=\"https://github.com/Microsoft/vscode-textmate\">vscode-textmate</a> por baixo dos panos.</p>\n<p>Certo, então depois de muito resistir, acabei realmente criando a página manualmente, incorporando partes do código do <code>vue-notion</code> e adaptando-as para o meu gosto e para a nova <a href=\"https://vuejs.org/guide/extras/composition-api-faq.html\">API de composição do Vue.js</a>. Isso acabou dando um certo trabalho e algumas dores de cabeça devido à recursividade e algumas má-estruturações da API do Notion também.</p>\n<h2 id=\"a-recursividade-da-montagem\">A recursividade da montagem</h2>\n<p>Eu nunca tinha desenvolvido algum componente no Vue.js que utilizava recursividade, e muito menos sabia que você pode usar o mesmo componente dentro dele mesmo. Entender inicialmente o código do <code>NotionRenderer.vue</code>, o componente raiz que monta a página, foi complicado no começo justamente por conta disso.</p>\n<pre><code class=\"language-vue\">&#x3C;template>\n  &#x3C;NotionBlock v-bind=\"pass\" v-if=\"blockMap &#x26;&#x26; value\">\n    &#x3C;NotionRenderer\n      v-for=\"(contentId, contentIndex) in value.content\"\n      v-bind=\"pass\"\n      :key=\"contentId\"\n      :level=\"level + 1\"\n      :content-id=\"contentId\"\n      :content-index=\"contentIndex\"\n    />\n  &#x3C;/NotionBlock>\n&#x3C;/template>\n</code></pre>\n<p>Para entender o funcionamento, é necessário pensar como uma estrutura de árvore funciona. O que o componente faz é criar um bloco para si próprio, e para cada folha que este nó atual possui, é criado outra instância do <code>NotionRenderer.vue</code> dentro de seu corpo, usando como nó raiz desta vez o nó folha atual do <em>loop</em>, e assim suscetivamente para cada bloco, até que a árvore seja completamente consumida. Tenho que admitir, é um código bastante inteligente, mas que realmente é difícil de se compreender numa primeira análise.</p>\n<p>Por sua vez, o componente <code>NotionBlock.vue</code> é o encarregado de renderizar cada tipo de bloco diferente, seja ele o bloco de parágrafo, o de cabeçalho, o de código e por aí vai.</p>\n<pre><code class=\"language-vue\">&#x3C;template>\n  &#x3C;div v-if=\"isType('page')\">\n    &#x3C;BlockPage v-bind=\"pass\">\n      &#x3C;slot />\n    &#x3C;/BlockPage>\n  &#x3C;/div>\n  &#x3C;BlockHeader\n    v-else-if=\"isType(['header', 'sub_header', 'sub_sub_header'])\"\n    v-bind=\"pass\"\n\t/>\n  &#x3C;BlockCallout v-else-if=\"isType('callout')\" v-bind=\"pass\" />\n  &#x3C;BlockCode v-else-if=\"isType('equation')\" v-bind=\"pass\" />\n  &#x3C;BlockText v-else-if=\"isType('text')\" v-bind=\"pass\" />\n  &#x3C;!-- E por aí vai, pra cada outro tipo de bloco existente. -->\n&#x3C;/template>\n</code></pre>\n<p>Note que blocos que possuem outros blocos dentro de seu corpo, como o do tipo página ou lista, disponibilizam o <code>&#x3C;slot /></code>, que será acessado por cada um de seus blocos interiores pela recursividade. Por conta disso, é bem fácil acabar esbarrando em problemas de recursão infinita durante o desenvolvimento, causando um pouco de estresse.</p>\n<p>Cada componente de bloco individual possui um código específico para se adequar as necessidades de poder renderizá-lo. Deixo como exemplo o de parágrafo.</p>\n<pre><code class=\"language-vue\">&#x3C;template>\n  &#x3C;p v-if=\"properties\" :class=\"['notion-text', blockColorClass()]\">\n    &#x3C;BlockTextRenderer :text=\"title\" v-bind=\"pass\" />\n  &#x3C;/p>\n  &#x3C;div v-else class=\"notion-blank\">&#x26;nbsp;&#x3C;/div>\n&#x3C;/template>\n</code></pre>\n<p>De maneira similar ao <code>NotionBlock.vue</code>, o <code>BlockTextRenderer.vue</code> também percorre cada item da propriedade <code>title</code> do bloco para gerar o texto final, adicionando as <em>tags</em> necessárias como <code>&#x3C;strong></code>, <code>&#x3C;code></code>, dentre outras.</p>\n<h2 id=\"um-problema-memorável\">Um problema memorável</h2>\n<p>Não sei o porquê, mas o Notion não cria listas como um bloco com seus itens dentro.</p>\n<pre><code class=\"language-yaml\">bloco-de-lista: \n  Tipo: Super bloco de lista\n  Conteúdo: ['item-1', 'item-2', 'item-3']\nitem-1:\n  Tipo: Bloco de lista\n  Texto: Item 1\n  Conteúdo: ['item-1-1']\nitem-1-1:\n  Tipo: Bloco de lista\n  Texto: Item 1.1\nitem-2:\n  Tipo: Bloco de lista\n  Texto: Item 2\nitem-3:\n  Tipo: Bloco de lista\n  Texto: Item 3\n</code></pre>\n<p>Ao invés disso, esta é a estrutura, sem o “super bloco”:</p>\n<pre><code class=\"language-yaml\">item-1:\n  Tipo: Bloco de lista\n  Texto: Item 1\n  Conteúdo: ['item-1-1']\nitem-1-1:\n  Tipo: Bloco de lista\n  Texto: Item 1.1\nitem-2:\n  Tipo: Bloco de lista\n  Texto: Item 2\nitem-3:\n  Tipo: Bloco de lista\n\tTexto: Item 3\n</code></pre>\n<p>Isso acaba gerando um problema semântico no HTML gerado, pois são criados vários <code>&#x3C;ul></code> com um item só dentro seguidos um dos outros ao invés de um <code>&#x3C;ul></code> só com todos os itens.</p>\n<pre><code class=\"language-html\">&#x3C;ul>\n  &#x3C;li>Item 1&#x3C;/li>\n  &#x3C;ul>\n    &#x3C;li>Item 1.1&#x3C;/li>\n  &#x3C;/ul>\n&#x3C;/ul>\n&#x3C;ul>\n  &#x3C;li>Item 2&#x3C;/li>\n&#x3C;/ul>\n&#x3C;ul>\n  &#x3C;li>Item 3&#x3C;/li>\n&#x3C;/ul>\n</code></pre>\n<p>A maneira que eu consegui contornar isso foi percorrer a árvore de blocos e encontrar nós do tipo lista que eram seguidos um dos outros e transformar o primeiro no “super bloco” e retirar os demais do bloco raiz do tipo página, passando a responsabilidade da renderização destes itens internos do tipo lista para o <code>BlockList.vue</code></p>\n<pre><code class=\"language-vue\">&#x3C;ul\n  v-else-if=\"isTopLevel &#x26;&#x26; type === 'bulleted_list_group'\"\n  class=\"notion-list notion-list-disc\"\n>\n  &#x3C;li>&#x3C;BlockTextRenderer :text=\"title\" v-bind=\"pass\" />&#x3C;/li>\n  &#x3C;BlockNestedList v-if=\"value.content\" v-bind=\"pass\">\n    &#x3C;slot />\n  &#x3C;/BlockNestedList>\n  &#x3C;template v-for=\"childId in properties.content\" :key=\"childId\">\n    &#x3C;li>\n      &#x3C;BlockTextRenderer\n        :text=\"blockMap[childId].value.properties.title\"\n        v-bind=\"pass\"\n      />\n    &#x3C;/li>\n    &#x3C;ul v-if=\"blockMap[childId].value.content\">\n      &#x3C;NotionRenderer\n        v-for=\"(contentId, contentIndex) in blockMap[childId].value.content\"\n        v-bind=\"pass\"\n        :key=\"contentId\"\n        :level=\"level + 1\"\n        :content-id=\"contentId\"\n        :content-index=\"contentIndex\"\n      />\n    &#x3C;/ul>\n  &#x3C;/template>\n&#x3C;/ul>\n</code></pre>\n<p>Após corrigir, o HTML gerado ficou semanticamente correto.</p>\n<pre><code class=\"language-html\">&#x3C;ul>\n  &#x3C;li>Item 1&#x3C;/li>\n  &#x3C;ul>\n    &#x3C;li>Item 1.1&#x3C;/li>\n  &#x3C;/ul>\n  &#x3C;li>Item 2&#x3C;/li>\n  &#x3C;li>Item 3&#x3C;/li>\n&#x3C;/ul>\n</code></pre>\n<h2 id=\"limitação-de-linguagens-no-bloco-de-código\">Limitação de linguagens no bloco de código</h2>\n<p>Outra limitação que encontrei é que o Notion não tem algumas linguagens de programação como opção em seu bloco de código. Ao contrário do Markdown onde você pode especificar manualmente uma nova opção, isso não é possível no Notion.</p>\n<p>A solução que eu encontrei foi novamente percorrer a árvore de blocos atrás dos blocos de código e efetuar alguns testes usando <a href=\"https://pt.wikipedia.org/wiki/Express%C3%A3o_regular\">Expressões Regulares</a> para substituir o valor de <code>lang</code>. Por exemplo, para o HTML específico do Vue, eu busco por blocos que tenham <code>lang</code> como HTML ou JavaScript e testo a expressão <code>/v-if|v-for|v-bind/</code>. Se a expressão for encontrada, eu substituo <code>lang</code> por <code>vue-html</code>, assim o Shiki consegue fazer seu trabalho corretamente.</p>\n<p>Isso abre portas para adicionar novas linguagens também, como a linguagem de <em>tags</em> de legendas Advanced Substation Alpha do <a href=\"https://github.com/aegisub/aegisub\">Aegisub</a>, que utilizo em um dos meus artigos. Na verdade, é possível adicionar qualquer nova linguagem, existente ou não, já que o Shiki consegue ler arquivos JSON de gramática no formato TextMate, assim como o VSCode.</p>\n<pre><code class=\"language-ass\">{\\t($start, $end, \\fscy150)}\n</code></pre>\n<h2 id=\"o-que-ainda-está-faltando\">O que ainda está faltando</h2>\n<p>O renderizador de blocos do Notion está quase perfeito, mas ainda faltam alguns tipos de blocos e algumas outras questões a serem tratadas, como o acesso a mídias. O Notion permite com que imagens sejam acessadas diretamente, mas parece que há um tempo máximo para que a validade de acesso do link expire. O ideal seria armazenar estas mídias juntamente ao repositório durante a geração do HTML, mas ainda não consegui pensar numa maneira ideal para isso.</p>\n<p>Eu acabei me animando tanto quanto ao resultado que até acabei convertendo a página de Sobre e de Projetos para usar o Notion também, me dando uma liberdade ainda maior para editar o conteúdo delas também sem precisar mexer com <em>commits</em> diretamente.</p>\n<p>Após escrever ou editar páginas, eu ainda preciso dar um <em>trigger</em> manual na <em>action</em> que configurei no repositório no GitHub, mas acredito que consiga um dia automatizar isso também. Além disso, também preciso adicionar uma URL de <em>preview</em> para posts em rascunho.</p>\n<p>De todo modo, eu espero que utilizar o Notion como o CMS do site me dê uma maior facilidade para poder escrever e me anime mais em relação a isso. Escrever inicialmente parece dar um trabalho enorme dependendo do assunto, mas sempre acaba me ajudando a distrair (temporariamente) de problemas maiores, então é uma coisa que acaba me ajudando.</p>\n<p>O intuito desse artigo não era ser um tutorial nem nada do tipo, e sim apenas um breve relato, que talvez tenha até se estendido demais. Entretanto, se lhe foi útil de alguma maneira, fico feliz.</p>\n<p>Até uma próxima ✌🏻</p>\n<div><p><strong>Nota:</strong></p><p><strong>Atualização:</strong> Este texto foi escrito quando o site estava utilizando a API não oficial do Notion disponibilizada pela equipe da Splitbee. Apesar de no texto eu mencionar sobre as dificuldades que a API oficial impõe, eu resolvi me aventurar e começar a utilizá-la para não depender de deixar a tabela e as páginas públicas, me dando uma liberdade e privacidade maior para os rascunhos. Você pode ler um pouco mais sobre o processo de transição <a href=\"https://alessandrojean.github.io/post/o-desafio-de-usar-a-api-oficial-do-notion-com-nuxtjs-v3\">neste outro artigo</a> que eu escrevi.</p></div>","date_published":"2022-10-11T00:00:00.000Z","date_modified":"2022-10-11T00:00:00.000Z","tags":["Programação"],"language":"pt-BR"},{"id":"o-processo-de-reescrita-do-site","url":"https://alessandrojean.github.io/post/o-processo-de-reescrita-do-site","title":"O processo de reescrita do site","summary":"Algumas notas e observações do processo de reescrita do site.","content_html":"<p>Depois de alguns anos com o site parado e até fora do ar, resolvi tomar coragem e tentar refazer do zero utilizando algum outro <em>framework</em> que não fosse o Jekyll. Por ter mais familiaridade com o Vue.js, acabei optando pelo NuxtJS em conjunto com o Tailwind CSS, que tenho utilizado cada vez mais em meus projetos no lugar no Bulma.</p>\n<p>Eu só tinha usado o Tailwind em alguns projetos menores, então resolvi arriscar usar aqui também, e ainda bem que fiz esta escolha, tive uma grata surpresa. Assim como os criadores do <em>framework</em> alegam na descrição, realmente me senti mais produtivo em não ter que mexer em nenhuma linha de CSS fora o básico durante a configuração. Ainda tenho um pouco de dificuldades aqui e ali quanto a tentar transformar tudo em componentes o máximo que conseguir para evitar repetição de códigos, mas sinto que eventualmente conseguirei</p>\n<p>No novo site eu queria dar mais destaque e fazer parecer mais uma espécie de portfólio e currículo, com algumas informações de conhecimentos e cursos. Ao mesmo tempo, também queria trazer de volta alguns dos textos que tinha escrito nas versões anteriores do blog. Acabei retirando o de como ler mangás no Kindle por enquanto pois tive algumas mudanças de pensamento e gostaria de revisitar e tentar evitar ao máximo remeter a pirataria, dando exemplos com quadrinhos que tenham licença da Creative Comics ou sejam de código aberto, como o <a href=\"https://www.peppercarrot.com/pt/\">Pepper &#x26; Carrot</a>.</p>\n<p>Outra experiência que resolvi testar foi utilizar o Notion como o “banco de dados” dos posts. Tinha visto um dos criadores do Tailwind comentar sobre em um <em>tweet</em> e achei a proposta muito interessante. Infelizmente a API do Notion não retorna o corpo inteiro da página em um formato mais comum como HTML ou até mesmo Markdown, mas sim em uma espécie de AST com os nós de cada bloco. Existem alguns pacotes no NPM como o <code>vue-notion</code>, mas infelizmente ele não está atualizado ainda para o Vue.js v3 e nem para o NuxtJS 3, o que acaba causando diversos erros de compilação.</p>\n<blockquote cite=\"https://x.com/adamwathan/status/1483114773235609607\"><p>Brincando um pouco com usar o Notion como um CMS para o meu site pessoal para reduzir a burocracia de publicação. É bem divertido, mas toda a abstração em ‘blocos’ e as coisas de paginação fazem ser um trabalho enorme só para extrair um conteúdo básico escrito pela API.</p><cite>— Adam Wathan, Criador do Tailwind CSS</cite></blockquote>\n<p>Como eu não quis esperar com que uma atualização fosse lançada e também não queria utilizar o Prism, acabei por incorporar uma parte de seu código já adaptando a nova API de composição do Vue.js. O resultado acabou saindo bastante satisfatório, e ainda consegui aproveitar para testar um outro pacote de <em>syntax highlighting</em>, o Shiki, que utiliza os códigos do VSCode por baixo dos panos. A vantagem é que é possível utilizar qualquer tema do VSCode também, como meu querido Nord.</p>\n<p>Ainda há algumas modificações que desejo fazer no site, como colocar uma parte de projetos com alguns sites que fiz para conhecidos, mas isso será feito lentamente. Quanto aos artigos, não sei se pretendo voltar a escrever, mas ter essa facilidade de usar o Notion talvez acabe me animando um pouco por um período, vamos ver.</p>","date_published":"2022-10-07T00:00:00.000Z","date_modified":"2022-10-07T00:00:00.000Z","tags":["Cotidiano"],"language":"pt-BR"}]}