1up4developers logo

1up4developers

Nadando contra o Waterfall. tail -f /mind/realworld >> /blog

Alguns Truques Com O REPL Do Clojure

| | Comments


O REPL é uma das ferramentas mais úteis para se programar em Clojure. Se você está chegando do Ruby ou do Python está mais do que acostumado a usar o IRB ou o modo interativo do Python. Veremos no decorrer do capítulo que o REPL é bem mais do que um prompt da linguagem, que serve apenas para que instruções sejam testadas.

Existem alguns atalhos e funções auxiliares que tornam o uso do REPL bem mais produtivo. Por mais que escrever diretamente no REPL não seja tão confortável quando no seu editor preferido, algumas vezes isso acaba sendo necessário.

Qual é mesmo o nome daquela função?

As funções da biblioteca padrão do Clojure vem com um texto explicativo, onde você pode se situar sobre como utilizá-las.

Podemos pesquisar alguma palavra que estiver dentro desses textos para encontrar a função que queremos, mas não lembramos o nome. Para isso, usamos find-doc, seguido da palavra ou trecho de texto relacionado ao que queremos.

Vamos supor que eu esteja procurando algo sobre sockets. Basta digitar (find-doc “socket”) no REPL.

(find-doc "socket")
; -------------------------
; clojure.tools.nrepl/connect
; ([& {:keys [port host transport-fn], :or {transport-fn
;  transport/bencode, host "localhost"}}])
;  Connects to a socket-based REPL at the given host (defaults to
;  localhost) and port, returning the Transport (by default clojure.

; e mais um monte de coisas

No nosso exemplo, encontramos a função connect, que está no namespace clojure.tools.nrepl.

Se você lembra de alguma parte do nome da função, então pode usar a função apropos, passando como parâmetros o trecho do nome ou uma expressão regular. Não se preocupe com expressões regulares agora, pois veremos esse assunto em detalhes mais para frente.

Vamos supor que eu esteja manipulando vetores e não lembre o nome da função, mas saiba que a estrutura chama-se vector:

(apropos "vector")
; (vector-of vector vector? vector-zip)

E agora você pode usar a função doc para ver a documentação daquela que mais se parecer com o que você estiver procurando:

(doc vector?)
; -------------------------
; clojure.core/vector?
; ([x])
;   Return true if x implements IPersistentVector

Existe uma variação de apropos chamada apropos-better, que informa também o namespace da função quando ela não estiver dentro do namespace clojure.core ou dentro do namespace em que você estiver no momento:

(apropos-better "vector")
; (vector vector-of vector? clojure.zip/vector-zip)

Um pouco de Bash na sua vida

Quando você usa o REPL por dentro do Leiningen, alguns atalhos já conhecidos pelos usuários de Bash estão disponíveis, mesmo para quem está usando o Leiningen no Windows.

O primeiro deles é a tecla TAB, que exibe os nomes de funções que começam com o que você já digitou.

Por exemplo, vou digitar map e pressionar TAB

(map
; map           map-indexed   map?          mapcat        mapv

Outra combinação que agiliza bastante o trabalho é a combinação Control L, ou Command L se você estiver usando MacOS, que limpa os resultados das expressões anteriores e mantém apenas a expressão que você estiver digitando no momento.

Existe também a combinação Control R, ou Command R, que completa o que você estiver digitando usando o histórico de comandos do REPL. Pressionando essa combinação mais de uma vez vai alternar entre todas as combinações já utilizadas que contenham o texto que você já digitou.

Usar as setas para cima ou para baixo permite que você navegue nos comandos utilizados recentemente.

Recuperando os últimos resultados

Existem também símbolos especiais que guardam os resultados das últimas expressões e exceções. Eles são 1, 2 e 3 para os valores e e para a última exceção, ou erro, que ocorreu:

(+ 1 2)
; 3

(* 2 4)
; 8

(/ 8 2)
; 4

(println "Resultados anteriores:" *1 *2 *3)
; Resultados anteriores: 4 8 3

(/ 1 0)
; ArithmeticException Divide by zero

(println "Último erro:" *e)
; Último erro: #<ArithmeticException java.lang.ArithmeticException:
;   Divide by zero>

Consultando o código fonte

Algumas vezes é bom ter acesso ao código fonte de determinada função ou macro para que possamos entender melhor como ela funciona. Enquanto eu escrevia este artigo, fiz isso constantemente para descobrir como as coisas funcionam por baixo dos panos.

Infelizmente, nem sempre é simples ir até o site onde o código fonte do Clojure está disponível e procurar o arquivo em que aquela função está definida.

Pior ainda quando a versão que está lá é diferente da versão que você está usando no momento. E fica ainda pior quando você não tem acesso ao código fonte da biblioteca que estiver utilizando.

Para nos ajudar, existe a macro source, que recebe como parâmetro o nome da função, sem aspas, e exibe o respectivo código fonte, quando possível.

Existem casos em que isso não é possível, como quando você tentar ler o fonte de uma forma especial ou de um código que foi compilado utilizando AOT (veremos isso em detalhes mais para frente).

Vamos exibir o código fonte da função +, responsável por somar dois ou mais números:

(source +)
; (defn +
;   "Returns the sum of nums. (+) returns 0. Does not auto-promote
;   longs, will throw on overflow. See also: +'"
;   {:inline (nary-inline 'add 'unchecked_add)
;    :inline-arities >1?
;    :added "1.2"}
;   ([] 0)
;   ([x] (cast Number x))
;   ([x y] (. clojure.lang.Numbers (add x y)))
;   ([x y & more]
;      (reduce1 + (+ x y) more)))

Note que temos acesso a todos os detalhes internos da função +, incluindo sua documentação e mais algumas informações que são úteis para o compilador ou para alguma função que gere documentação automaticamente.

Ao tentarmos ver o código fonte de uma forma especial ou de algum código escrito nativamente em Java, receberemos uma mensagem de que o código fonte não foi encontrado:

(source Thread/sleep)
; Source not found

Há muito mais recursos no REPL do Clojure, inclusive no que diz respeito a integração com o seu editor preferido, mas vou deixar isso para outro artigo.

Sowing the Seeds of Code

| | Comments


Seguindo a filosofia de tirar a bunda da cadeira, tive a oportunidade de falar em excelentes eventos esse ano, conhecer muita gente interessante e dar os primeiros passos no sentido de disseminar a linguagem Clojure no Brasil e organizar comunidades em São Paulo e no Rio de Janeiro.

Coding Dojo

Em Maio tive a honra de ter sido convidado a apresentar um coding dojo de Clojure na Das Dad, que acabou tendo uma aceitação bem maior do que eu esperava.

Tirando o fato do trânsito de São Paulo ter me derrubado e me feito chegar atrasado, conseguimos dar uma boa olhada na linguagem e atiçar a curiosidade. Quem quiser dar uma olhada no que foi feito, o código está no GitHub.

Anhanguera

Ainda em Maio, Alexandre Borba e eu fomos convidados a palestrar na unidade de Osasco da Anhanguera Educacional. Borba apresentou os conceitos de Coding Dojo, Katas e o formato Randori para os alunos, enquanto eu apresentei conceitos de orientação a objetos com JavaScript.

Os organizadores do evento prepararam um espaço para os palestrantes atrás do palco que era muito melhor do que qualquer camarim que eu já tenha visto nas vezes em que toquei por aí.

Alguns alunos perguntaram sobre o uso de JavaScript no mercado de trabalho, o que me deu a certeza de que uma palestra menos técnica e mais focada na realidade deles teria sido melhor aproveitada.

2º ENCATEC

O pessoal da Adapti, empresa júnior encubada no CEUNES-UFES, em São Mateus-ES, me convidou para o Segundo Encontro Norte-Capixaba de Tecnologia.

Lá tive o prazer de assistir a palestra da Professora Mariella Berger, do projeto IARA, que apresentou o “famoso carro que atropelou a Ana Maria Braga” e o projeto da Universidade Federal do Espírito Santo que junta automatização, robótica, inteligência artificial, sistema de visão e mais um monte de coisas bacanas usando soluções de código aberto.

Tive a oportunidade também de dividir espaço com meus ídolos Álvaro Justen, o Turicas, que falou sobre software livre; Ricardo Valeriano, que fez uma apresentação hardcore sobre Paralelismo e Concorrência com Ruby; e Luiz Rocha apresentando conceitos de sistemas distribuídos.

Eu falei sobre as aplicações de programação funcional em ambientes corporativos, visto que os alunos da UFES “aprendem” programação funcional e Haskell logo no primeiro semestre de uma maneira bem deficiente.

Assim como aconteceu na Anhanguera, saí com a impressão de que um tema menos técnico teria sido melhor aproveitado pelos alunos. O fato dos alunos serem pagos com horas de atividades extra-curriculares também fez com que muitos estivessem ali obrigados, estando pouco interessados nos temas apresentados.

TDC SP

Em Julho tivemos a etapa de São Paulo do The Developers Conference, evento itinerante organizado pela GlobalCode.

Eu tive a oportunidade de apresentar a primeira palestra da trilha de JavaScript e HTML5, na Quarta-feira, e apresentei vários recursos menos conhecidos do JavaScript, além de ter sorteado uma cópia do meu livro.

Como eu utilizei o manjadíssimo recurso de usar animais para explicar herança, um dos presentes comentou depois, no Twitter, que acha idiota a analogia com animais, visto que ele nunca utilizou animais nos sistemas que já ajudou a desenvolver. Eu achei que seria desnecessário dizer, mas o problema aí mora no fato do colega não perceber que eu estava explicando um conceito de uma forma que qualquer pessoa possa entender. Uma vez que você entenda o conceito, você pode extrapolá-lo para a sua realidade, seja utilizando herança para definir Pessoas Físicas e Jurídicas, seja para definir classes fiscais e naturezas de operação da forma que for mais conveniente.

O ponto negativo ficou por conta da organização, que atrasou o início das palestras em pouco mais de uma hora, não apresentou justificativas nem um pedido de desculpas aos presentes.

No Domingo, último dia do evento, fui mentor de Clojure no Coding Dojo que aconteceu na trilha de Ruby. Ao mesmo tempo e na mesma sala havia máquinas com Clojure, Scala e Ruby para resolver o mesmo problema. Os participantes saiam de uma máquina e iam para outra, onde podiam ver as diferenças e características de cada uma das linguagens.

Fiquei feliz ao perceber que o Clojure causou uma boa impressão e, novamente, despertou o interesse.

QCon SP

O grande evento do ano para desenvolvedores, infelizmente, caiu nos mesmos dias do RubyConf, evento da Locaweb voltado para a comunidade Ruby e Rails. Ouvi coisas muito boas sobre o RubyConf desse ano, e foi uma pena que eu não estive por lá.

Como acontece todos os anos, o nível das palestras foi muito bom e, pela primeira vez, tive a honra de ter sido convidado para falar sobre Clojure na trilha “Fronteiras do Desenvolvimento”.

Para minha surpresa, a sala ficou lotada. Muita gente em pé, muita gente interessada e várias pessoas vieram falar comigo após a palestra para tirar dúvidas ou mesmo para contar suas experiências ao adotar Clojure em suas empresas.

Não tenho dúvidas de que, profissionalmente falando, esse foi o ponto alto do ano para mim. Só tenho a agradecer aos presentes e à organização pelo interesse e pela oportunidade.

O ponto negativo, e até certo ponto divertido, ficou por conta da novelinha criada por um dos palestrantes ao falar mal de SOA. Os representantes de um dos patrocinadores, que vendem consultoria no assunto, ao se sentirem ofendidos, iniciaram um festival de resmungos passivo-agressivos como se um bom diálogo fosse construído dessa forma. O evento em si e a organização nada tiveram a ver com isso, mas foi interessante acompanhar algumas reações e ver quem as pessoas realmente são quando o calo dói.

(clj-sp)

Entre um evento e outro, percebendo o interesse crescente na linguagem Clojure, resolvi chamar os desenvolvedores das listas Clojure Brasil e Clojure BR para participar do (clj-sp), o Grupo de Usuários Clojure no Brasil. Somos o primeiro grupo do tipo no Brasil e, por mais estranho que pareça, nosso primeiro encontro foi no Rio de Janeiro.

Organizado pelo nosso amigo Giuliani, sete desenvolvedores se reuniram num bar da Lapa, o equivalente carioca da Vila Madalena e trocamos experiências sobre o uso de programação funcional em geral e Clojure em particular em nossos respectivos trabalhos.

Dois dias depois tivemos o primeiro encontro em São Paulo, nas dependências da iMasters, contando com uma apresentação minha sobre a sintaxe do Clojure, Konrad Scorciapino falando sobre Datalog e Jonas Fagundes compartilhando sua experiência com Clojure em startups.

O auditório da iMasters ficou pequeno para tanta gente interessada e eu fiquei impressionado como apareceu muito mais gente do que eu estava esperando.

Dia 26 de Setembro, última Quinta-feira do mês, teremos o segundo encontro. Caso você tenha interesse, as informações estão em [http://www.meetup.com/clj-sp/].

O que vem por aí

  • 7masters JavaScript Já 25 de Setembro será dia de 7masters de JavaScript na iMasters. Eu e mais seis especialistas vamos apresentar lightining talks de sete minutos sobre algum assunto bacana relacionado à linguagem. Acesse [setemasters.imasters.com.br/edicoes/javascript/] e compareça.

  • DevDay Em outubro vou falar novamente sobre JavaScript no DevDay, um evento muito bacana voltado para desenvolvedores de software que vai acontecer em Belo Horizonte.

Vai ser uma chance bacana de conhecer vários desenvolvedores que eu admiro e aprender com quem realmente conhece do assunto.

  • Programação funcional E, finalizando, estou escrevendo meu segundo livro, mais focado em programação funcional e como isso pode salvar a sua pele no seu trabalho ou nos seus projetos pessoais. É um livro bem mais denso e especializado que o primeiro, mas estou tentando manter o mesmo tom didático e fácil de assimilar.

Esse ano está sendo o ano da colheita de tudo o que eu plantei em 2012 e, enquanto isso, estou me preparando para colher mais frutos no ano que vem. Vamos ver no que dá.

[Off-Topic] Transforme Seu Roteador Wireless Em Um NAS, MediaServer UPnP/DLNA E BitTorrent Client Com OpenWRT

| | Comments


TL;DR OpenWRT é uma distro Linux embarcável em roteadores que permite customizar e instalar serviços sem a necessidade de compilar um novo firmware. Em outras palavras, é um “firmware on steroids” para roteadores.

Mas por quê?

  • Economia. Apenas substituindo o firmware do roteador é possível adicionar funcionalidade e recursos, economizando uma grana preciosa com dispositivos semelhantes, como AppleTV, NAS dedicado, etc.
  • Funcionalidade. Provavelmente o roteador fica ligado 100% do tempo na sua casa, sendo utilizado apenas para compartilhar a internet. Praticamente um servidor disponível 24 horas por dia sem utilizaçao! Não mais…
  • Diversão. Substituir o firmware do seu roteador por uma distro baseada em Linux e configurar todos os serviços manualmente… é diversão pura!
  • Por que eu quero (e posso)!

Instalação

Atenção: existe a possibilidade (embora pequena) de algo sair muito errado e você ganhar um peso de papel. Prossiga por sua conta e risco… ou se tiver coragem!

O primeiro passo é descobrir o modelo do seu roteador e verificar a compatibilidade nesta página: http://wiki.openwrt.org/toh/start. Se não encontrar o modelo ou houver um aviso de não-compatibilidade, sinto muito mas não vai rolar para você.

Se seu roteador for compatível, haverá o target da imagem e um link para uma wiki com um how-to específico do modelo, instruções de instalação, resolução de problemas, etc. Procure pelo nome da release que você deverá baixar daqui http://downloads.openwrt.org. A imagem tem o formato openwrt-TARGET-generic-MODELO-VERSAO-squashfs-factory.bin

Por exemplo, meu roteador é um TP-Link WR1043ND. Procurando na página http://wiki.openwrt.org/toh/start, o target compatível com meu roteador é ar71xx:

Acessei a página de downloads, naveguei pelos diretórios da release, depois target e encontrei o arquivo openwrt-ar71xx-generic-tl-wr1043nd-v1-squashfs-factory.bin para download.

Quando baixar a imagem, é só fazer a atualização de firmware normalmente no seu roteador pelo admin:

Confirme e reze muito! Aguarde o roteador atualizar e reiniciar.

Deste ponto em diante, será necessário conectar no roteador com cabo, para setar uma senha de root e configurar o wifi. Este processo é relativamente trivial, basta utilizar a interface web do admin, sem segredo…

Uma vez definida a senha e o wifi configurado, é possível acessar seu roteador via ssh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ ssh root@192.168.1.1
root@192.168.1.1's password:

BusyBox v1.19.4 (2013-03-14 11:28:31 UTC) built-in shell (ash)
Enter 'help' for a list of built-in commands.

  _______                     ________        __
 |       |.-----.-----.-----.|  |  |  |.----.|  |_
 |   -   ||  _  |  -__|     ||  |  |  ||   _||   _|
 |_______||   __|_____|__|__||________||__|  |____|
          |__| W I R E L E S S   F R E E D O M
 -----------------------------------------------------
 ATTITUDE ADJUSTMENT (12.09, r36088)
 -----------------------------------------------------
  * 1/4 oz Vodka      Pour all ingredients into mixing
  * 1/4 oz Gin        tin with ice, strain into glass.
  * 1/4 oz Amaretto
  * 1/4 oz Triple sec
  * 1/4 oz Peach schnapps
  * 1/4 oz Sour mix
  * 1 splash Cranberry juice
 -----------------------------------------------------
root@OpenWrt:~#

Se você chegou até aqui, parabéns! Você teve coragem. E felizmente a parte difícil já passou…

Nota: dependendo do seu roteador, a versão do OpenWRT pode variar. Leia a wiki do modelo do seu roteador para instruções específicas e resolução de problemas.

Importante: caso o pior aconteça (como acabar a energia no meio do processo de flash da firmware) e você não queira utilizar seu roteador como peso de papel, tente seguir os procedimento de “debriking” em http://wiki.openwrt.org/doc/howto/generic.debrick

Configurando o NAS

Para o NAS, vamos montar as partições do HD externo e configurar uma partição de swap pois o roteador provavelmente não tem memória interna suficiente para dar conta do recado.

Neste exemplo, vou usar meu HD de 1TB com uma partição formatada em ext4 e outra partição de 1GB formatada como linux+swap. Não vou usar FAT32 nem NTFS e nem recomendo! A idéia aqui é deixar o HD plugado eternamente no roteador e acessá-lo pela rede.

Instalando os pacotes necessários

Para nossa sorte, o OpenWRT conta com um gerenciador de pacotes que facilita (e muito) a instalação das libs e módulos necessários para montar o HD externo e compartilhá-lo na rede via Samba ou NFS.

Logue no roteador via ssh e instale os seguintes pacotes:

1
2
root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg install kmod-usb-storage block-mount kmod-fs-ext4

O ultimo pacote vai depender do sistema de arquivos do seu HD. Por exemplo, kmod-fs-ext2, etc. Veja os módulos disponíveis em http://wiki.openwrt.org/doc/howto/storage

Verifique se as partições já foram detectadas rodando blkid:

1
2
3
root@OpenWrt:~# blkid
/dev/mtdblock2: TYPE="squashfs"
/dev/sda1: LABEL="MEDIA" UUID="1ecad5f1-0000-0000-000f-e8b7f6ac651d" TYPE="ext4"

Montando as partições do HD

Agora vamos configurar o fstab para montar as partições automaticamente quando o roteador for ligado. Edite o arquivo /etc/config/fstab com o seguinte (use o vi):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
config global automount
        option from_fstab 1
        option anon_mount 0

config global autoswap
        option from_fstab 1
        option anon_swap 0

config mount
        option target   /mnt/media
        option device   /dev/sda1
        option fstype   ext4
        option options  rw,sync
        option enabled  1
        option enabled_fsck 0

config swap
        option device   /dev/sda2
        option enabled  1

No meu caso, vou montar a partição /dev/sda1 em /mnt/media, então é necessário criar o diretório de destino, ativar e iniciar o serviço fstab:

1
2
3
root@OpenWrt:~# mkdir /mnt/media
root@OpenWrt:~# /etc/init.d/fstab enable
root@OpenWrt:~# /etc/init.d/fstab start

Pronto, seu HD externo pode ser acessado em /mnt/media e o swap foi montado na segunda partição.

Compartilhando na rede

Vou utilizar NFS para compartilhar a partição na rede, mas você também pode utilizar Samba seguindo http://wiki.openwrt.org/doc/howto/cifs.server

1
2
root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg install nfs-kernel-server

Edite (ou crie) o arquivo /etc/exports com o conteúdo:

1
/mnt/media 192.168.1.0/255.255.255.0(rw,sync,no_subtree_check)

Ative e inicie os serviços necessários:

1
2
3
4
root@OpenWrt:~# /etc/init.d/portmap start
root@OpenWrt:~# /etc/init.d/portmap enable
root@OpenWrt:~# /etc/init.d/nfsd start
root@OpenWrt:~# /etc/init.d/nfsd enable

Pronto! O server (roteador) está configurado. Para montar esta partição no client, por exemplo Ubuntu, siga:

1
2
3
$ sudo apt-get install nfs-common
$ sudo mkdir /media/nas
$ sudo mount -t nfs 192.168.1.1:/mnt/media /media/nas

Groovy! Seu NAS foi montado no seu desktop em /media/nas. Pode compartilhar seus arquivos a vontade!

Configurando o MediaServer (minidlna)

Logue no roteador e instale os pacotes necessários:

1
2
root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg install minidlna

Para configurar, basta editar o arquivo /etc/config/minidlna como segue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
config minidlna config
  option 'enabled' '1'
  option port '8200'
  option interface 'br-lan'
  option friendly_name 'OpenWrt DLNA Server'
  option db_dir '/mnt/media/minidlna/db'
  option log_dir '/mnt/media/minidlna/log'
  option inotify '1'
  option enable_tivo '0'
  option strict_dlna '0'
  option presentation_url ''
  option notify_interval '900'
  option serial '12345678'
  option model_number '1'
  option root_container '.'
  list media_dir '/mnt/media'
  option album_art_names 'Cover.jpg/cover.jpg/AlbumArtSmall.jpg/albumartsmall.jpg/AlbumArt.jpg/albumart.jpg/Album.jpg/album.jpg/Folder.jpg/folder.jpg/Thumb.jpg/thumb.jpg'

Ative e inicie o serviço:

1
2
root@OpenWrt:~# /etc/init.d/minidlna enable
root@OpenWrt:~# /etc/init.d/minidlna start

Pronto! O MediaServer está configurado e compartilhando seus arquivos via DLNA na rede. Aqui na minha SmartTV aparece assim:

MiniDLNA na Samsung SmartTV

Configurando o BitTorrent client (transmission)

Vamos instalar os pacotes:

1
2
root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg install transmission-daemon

E configurar o serviço editando /etc/config/transmission e alterando as opções a seguir (mantenha todas as outras configs):

1
2
3
4
5
6
7
8
9
10
11
12
13
config transmission
  option enabled 1
  option config_dir '/mnt/media/transmission/config'
  option download_dir '/mnt/media/transmission/complete'
  option incomplete_dir '/mnt/media/transmission/incomplete'
  option incomplete_dir_enabled true
  option ratio_limit 2.0000
  option ratio_limit_enabled true
  option rpc_authentication_required false
  option rpc_password ''
  option rpc_username ''
  option speed_limit_up 5
  option speed_limit_up_enabled true

Ative e inicie o serviço:

1
2
root@OpenWrt:~# /etc/init.d/transmission enable
root@OpenWrt:~# /etc/init.d/transmission start

O Transmission roda como um daemon, sendo controlado remotamente. Para isso, será necessário adicionar a seguintes regra de farewall, adicionando no final do arquivo /etc/config/firewall:

1
2
3
4
5
config rule
        option src *
        option proto tcp
        option dest_port 9091
        option target ACCEPT

Reinicie o firewall executando /etc/init.d/firewall restart

Para gerenciar seus torrents, instale a extensão .torrent to Transmission no Chrome, baixe o Transmission Remote GUI para Windows, Mac e Linux ou ainda o Remote Transmission para Android.

Transmission GUI

Caso seu roteador tenha espaço disponível, você pode instalar o pacote transmission-web para gerenciar seus torrents diretamente do navegador.

Ao adicionar um .torrent pelo GUI, o mesmo será baixado diretamente no seu roteador!

Conclusão

Embora relativamente complicado, todo procedimento para instalação e configuração não é nada diferente do que estamos acostumados a fazer diariamente no Linux, como devops, seja configurando uma VPS ou montando um NAS na rede.

A liberdade que o OpenWRT oferece é imensa. O repositório é recheado com pacotes bem úteis e muito fáceis de configurar. Sem falar na economia comparando com um aparelho como AppleTV (que não ofecererá tantos recursos) ou outros media servers do mercado.

E aí, tem coragem??? Poste sua experiência nos comentários! :)

Referências

[QuickTips] Do Wordpress Para Octopress/Jekyll No GitHub Pages

| | Comments


Quando migramos nosso blog do Wordpress para o GitHub Pages, escrevi um email para nossos autores com instruções resumidas para configurar e postar com Octopress/Jekyll. Percebi que dando um tapa nesse email, poderia publicá-lo aqui no blog como um guia rápido e talvez incentivar outros blogueiros a fazer o mesmo.

Por que GitHub Pages?

Corte de custos! Manter o blog no Wordpress requer um hosting, um banco de dados e um domínio. Reduzimos as despesas apenas para o registro de domínio (por enquanto).

Performance! GitHub Pages é estático, e conteúdo estático é servido naturalmente mais rápido.

Desafio! Estavamos “acostumados” ao Wordpress. Aprender Jekyll e a postar “commitando em um projeto” permite que tenhamos novas idéias, ou no pior dos casos, aprendamos novas tecnologias.

Requisitos

Para utilizar o GitHub Pages, crie um repositório com o nome usuariogithub.github.io, incluindo o “github.io”. O GitHub gerencia este repositório e publica o conteúdo estático no endereço http://usuariogithub.github.io

Agora, para gerar o conteúdo estático vamos usar o Octopress.

Instalação

Basta clonar o repositório do Octopress localmente:

1
$ git clone git@github.com:imathis/octopress.git

instalar as gems necessárias e em seguida rodar a rake para configuração:

1
$ rake setup_github_pages

e informar o seu repositório do GitHub Pages:

git@github.com:username/username.github.io.git

Pronto! Os remoting points do projeto serão configurados para seu repositório do GitHub, como segue:

1
2
3
4
5
6
$ git remote -v

octopress git@github.com:imathis/octopress.git (fetch)
octopress git@github.com:imathis/octopress.git (push)
origin     git@github.com:username/username.github.io.git (fetch)
origin     git@github.com:username/username.github.io.git (push)

Postando

Para criar um novo post, basta rodar a rake:

1
$ rake new_post["o titulo do seu post"]

o que vai criar o arquivo source/_posts/2013-09-17-o-titulo-do-seu-post.markdown. Escreva o conteúdo do seu post normalmente em Markdown (recomendo utilizar o Markup Editor) e execute:

1
$ rake generate

para gerar o site estático no diretório _deploy. Caso queira dar um preview no que será publicado, basta rodar:

1
$ rake preview

e acessar no browser http://localhost:4000.

Publicando

Quando terminar seu post, basta rodar:

1
$ rake deploy

para publicar o site no seu repositório do GitHub Pages.

Pronto! Não se esqueça de subir os fontes do site (branch source), commitando suas alterações e executando o classico git push.

Migrando

Caso já tenha um site publicado no Wordpress, você pode seguir este guia para importar todo o conteúdo na estrutura do Jekyll:

How to Migrate from WordPress to Jekyll Running on Github

Referências

Introdução a Linux Control Groups (CGroups)

| | Comments


Em tempos de Metodologias Àgeis, iniciativas como DevOps, adoção de Cloud Computing e derivados (SaaS, IaaS e PaaS), aplicações que demorariam meses, senão anos para estar na www, hoje em questão de dias, e por que não horas, é possível estar disponíveis ao usuário final.

Com a necessidade de ter os aplicativos de forma mais rápida em produção, a adoção e criação de PaaS (Platform as A Service) tem sido a nova “onda do verão” e tecnologias como LXC, Docker e CGroups atuam como o cerne dessa “wave”.

O que são CGroups?

CGroups é uma feature do Kernel que provê mecanismos para organização de Processos em forma de grupos e limita recursos de máquina como Consumo de CPU, memória e I/O para estes.

Curioso pra ver como funciona?

Situação de Exemplo

Para este exemplo teremos duas aplicações Sinatra e nosso objetivo será dedicar um grupo para cada aplicação limitando o consumo de memória para cada uma elas.

Para rodar o exemplo estarei utilizando um Ubuntu 12.04 64 bits.

Pré-Requisitos

Antes de mais nada precisamos instalar algumas dependências:

1
sudo apt-get install cgroup-bin libcgroup1

Com a instalação dos pacotes acima veremos que um novo filesystem foi montado em /sys/fs/cgroup

1
2
3
4
5
6
7
8
9
ls -al /sys/fs/cgroup

drwxr-xr-x 7 root root 140 Aug  6 09:38 .
drwxr-xr-x 6 root root   0 Aug  6 09:37 ..
drwxr-xr-x 6 root root   0 Aug  6 09:38 cpu
drwxr-xr-x 6 root root   0 Aug  6 09:38 cpuacct
drwxr-xr-x 6 root root   0 Aug  6 09:38 devices
drwxr-xr-x 6 root root   0 Aug  6 09:38 freezer
drwxr-xr-x 6 root root   0 Aug  6 09:38 memory

CGroups estão organizados por subsistemas conhecidos também como “resource controllers” responsáveis por gerenciar memória, cpu, dispositivos, entre outras coisas. Na organização acima cada diretório representa um Resource Controller.

CGConfig Service

Para gerenciar CGroups iremos utilizar a utilitário cgconfig instalado como o pacote libcgroup1. É interessante checar se o serviço está rodando antes de continuar :

1
sudo service cgconfig status

Caso não esteja inicie o serviço

1
sudo service cgconfig start

Existem duas formas de configurar CGroups com cgconfig, diretamente no arquivo de configuração /etc/cgconfig.conf’ ou via linha de comando, que será o meio que iremos utilizar.

Criando Grupos

Para criar um grupo, utilizamos o comando cgcreate passando como argumento quais controllers estarão associados a ele.

1
2
sudo cgcreate -g cpu,cpuacct,devices,memory,freezer:/sinatra1
sudo cgcreate -g cpu,cpuacct,devices,memory,freezer:/sinatra2

O argumento /sinatra* indica o caminho relativo do grupo dentro de cada Resource Controller. Ex : /sys/fs/cgroup/<resource_controller>/

Executando programas em um Grupo

Para executar determinado processo em um grupo utilizamos o comando cgexec passando como argumentos quais controllers estarão associados ao processo e o caminho do grupo que ele estará associado.

1
2
sudo cgexec -g *:/sinatra1 sh -c 'cd <path_to_sinatra1> && exec rackup -p 4567 -D'
sudo cgexec -g *:/sinatra2 sh -c 'cd <path_to_sinatra2> && exec rackup -p 4568 -D'

O asterisco (*) acima significa que o processo estará associado a todos os controllers.

Para checar a hierarquia criada:

1
2
3
4
ps xawf -eo pid,cgroup,args | grep ruby
 1476              \_  5:freezer:              \_ grep --color=auto ruby
 1418  5:freezer:/sinatra1?4:memo /usr/bin/ruby1.9.1 /usr/local/bin/rackup -p 4567 -D
 1454  5:freezer:/sinatra2?4:memo /usr/bin/ruby1.9.1 /usr/local/bin/rackup -p 4568 -D

Para setar os valores em determinado controller utilizamos o comando cgset. No caso abaixo estamos limitando o consumo de memória para o grupo sinatra1 em 256MB e para o grupo sinatra2 em 128MB.

1
2
sudo cgset -r memory.limit_in_bytes='256M' sinatra1
sudo cgset -r memory.limit_in_bytes='128M' sinatra2

Para checar a alteração:

1
2
cat /sys/fs/cgroup/memory/sinatra1/memory.limit_in_bytes
cat /sys/fs/cgroup/memory/sinatra2/memory.limit_in_bytes

Conclusão

O intuito deste artigo foi demonstrar um dos possíveis usos de CGroups. Caso a aplicação sinatra1 cair por estouro de memória ou alguma outra falha que não seja a destruição da máquina, a aplicação sinatra2 continuará funcionando.

Há mais a se explorar, poderíamos inserir limitação de I/O, consumo de banda, entre outras coisas. Poderíamos até criar nossa própria implementação de LXC, mas isso é assunto para um próximo encontro.

Os links abaixo exploram mais detalhes sobre o assunto :

Divirtam-se!

Extendendo Ruby Com C - Só Um Aperitivo

| | Comments


Extender Ruby em C não é complicado. É claro, você deve ao menos ter o conhecimento básico da linguagem C.

Vamos criar uma extensão que retorna uma simples String.

Primeiramente criamos o diretório onde estará nossa extensão :

1
$ mkdir <your_path>/1up4dev

Crie um arquivo chamado 1up4dev.c e dentro dele inclua o header “ruby.h”

1
#include <ruby.h>

Tudo em Ruby relaciona-se com o tipo VALUE. Para nosso exemplo, vamos criar um VALUE m1up4dev representando um módulo.

1
VALUE m1up4dev;

E para representar uma classe, abaixo deste módulo, a qual chamaremos de Talker, criaremos uma VALUE cTalker:

1
VALUE cTalker;

Nossa classe Talker precisa fazer algo, vamos adicionar uma simples função que retorna uma String.

1
2
3
4
static VALUE say_yeah(VALUE self){
  const char *sentence= "YEAH YEAH!";
  return rb_str_new2(sentence);
}

Na função say_yeah, VALUE self representa o objeto associado a função, sentence a String de retorno e a função rb_str_new2 converte o *char em uma Ruby String.

Para deixar esse código acessível no mundo Ruby, criaremos uma função chamada ‘Init_1up4dev’. Por convenção estas funções sempre começam com o prefixo ‘Init_’.

1
2
3
4
5
void Init_1up4dev(){
  m1up4dev = rb_define_module("1up4dev");
  cTalker = rb_define_class_under(m1up4dev, "Talker", rb_cObject);
  rb_define_singleton_method(cTalker, "say_yeah", say_yeah, 0);
}

A função ‘rb_define_module’ define um módulo no topo da hierarquia. Algo como:

1
2
module 1up4dev
end

A função ‘rb_define_class_under’ define uma classe abaixo de um módulo ou outra classe. Isso irá gerar:

1
2
3
4
5
module 1up4dev
  class Talker

  end
end

A função ‘rb_define_singleton_method’ é responsável por criar um método singleton em uma classe ou módulo, neste caso ele estará atrelado a class Talker.

Para rodar nosso exemplo, crie um arquivo chamado ‘extconf.rb’ contendo:

1
2
require 'mkmf'
create_makefile('1up4dev')

Executando o script, irá ser gerado um arquivo Makefile para executar o build da extensão.

1
$ ruby extconf.rb

Compile e instale a extensão:

1
$ make && make install

Para ver o código funcionando basta digitar o código abaixo em um ‘irb’ ou algo do gênero :

1
2
3
4
$irb(main):001:0> require '1up4dev'
true
$irb(main):002:0> 1up4dev::Talker.say_yeah
"YEAH YEAH!"

YEAH YEAH!!

Entendendo LISP, Finalmente.

| | Comments


A sintaxe invertida

Ao olhar um código LISP pela primeira vez, você se assusta.

Eu me assustei e não havia ninguém para me ajudar a entender.

Que bom que você está lendo isto para entender bem depressa e perder o medo.

Acredite ou não, o LISP não é invertido: as outras linguagens é que são inconsistentes.

Matematicamente falando, funções são expressas dessa forma:

y = f(x)

Para calcularmos o dobro de um número, teríamos:

y = dobro(21)

Note que estamos usando uma notação diferente: primeiro vem o operador dobro e, em seguida, vem o operando, ou parâmetro, 21. Chamamos isso de notação prefixa.

Já para executar um cálculo matemático, usamos a forma abaixo:

y = 21 * 2

Primeiro temos um operador 21, depois temos um operando responsável pela multiplicação e, finalmente, o segundo operando 2. Chamamos essa forma de notação infixa.

Nota: se você é um desenvolvedor Ruby, ignore essa última expressão. Em Ruby o cálculo acima utiliza internamente a notação prefixa onde 21 é um objeto, * é um método (ou uma mensagem, se preferir) e 2 é um parâmetro.

A coisa fica bagunçada quando misturamos as duas formas:

y = dobro(7 * 3)

Na expressão acima misturamos notação prefixa com infixa. Não há problema algum com isso, mas não é um bom exemplo de consistência.

Quando falamos em LISP, o primeiro item de uma lista é um operador e todos os demais são operandos.

Todo operador é uma função, macro ou forma especial, inclusive os operadores matemáticos. Não se preocupe em entender agora o que são macros ou formas especiais. Todo o resto da lista é considerado um valor, parâmetro ou operando.

Imagine agora que o símbolo + é uma função. Para calcularmos uma soma usaríamos o seguinte código:

+(1, 2)

Movendo os parênteses e removendo as vírgulas, a nossa soma inicial ficaria:

(+ 1 2)

Sabemos que dobro também é uma função. Para calcular dobro, usaríamos:

(dobro 12)

Percebam que agora temos uma regra que se aplica a todos os casos. Repetindo a expressão acima que mistura as notações infixa e prefixa usando as regras do LISP, teríamos:

(dobro (+ 7 3))

Talvez pela sua origem acadêmica e fortemente influenciada pela matemática, as implementações de LISP levam muito a sério a questão da consistência.

Os parênteses

Quando eu estava na quarta série, aprendi uma coisa chamada expressão numérica, que consistia em resolver um cálculo extenso atacando um pedaço por vez, organizadamente.

Cada pedaço desse cálculo ficava dentro de parênteses, colchetes ou chaves, dependendo do quão aninhado estivesse a expressão. Eu nunca mais vi esse tipo de hierarquia, mas era um jeito bacana de manter a organização.

Uma expressão numérica tem essa cara:

x = {1 + [3 * (5 + 7)]}

Resolvemos a expressão de dentro para fora:

x = {1 + [3 * (12)]}

x = {1 + [36]}

x = {37}

x = 37

Simples, não?

Agora vamos extrapolar o que aprendemos na quarta série para uma linguagem de programação, trocando chaves e colchetes por parênteses:

x = (1 + (3 * (5 + 7)))

Vamos substituir a nossa conhecida notação infixa pela prefixa.

x = (+ 1 (* 3 (+ 5 7)))

Pronto. Você tem uma expressão numérica com a cara do LISP, resolvendo da forma como a professora ensinou lá na quarta série: primeiro você resolve os parênteses de dentro, depois os próximos, até terminar.

Qualquer LISP que você encontrar pela frente, incluindo o Clojure, funciona exatamente dessa maneira.

Uma vantagem que isso traz é que você não precisa ficar se preocupando com precedência de operadores.

Imagine que você tem o código abaixo:

x = 3 * 2 + 1

y = 1 + 2 * 3

Os valores de x e y serão iguais? Sim, ambas as variáveis contém o número 7, mas para saber disso você precisou ler em algum outro lugar que o operador de multiplicação tem precedência sobre o operador de adição. É algo que você espera que seja assim e age como se realmente fosse.

E o que aconteceria se você estiver usando uma linguagem em que a adição tem precedência sobre a multiplicação? Ou pior ainda: os operadores são executados da esquerda para a direita conforme forem aparecendo.

No primeiro caso, x e y continuariam sendo igual, mas ambos teriam o valor 9. No segundo caso, x seria igual a 7 e y seria igual a 9.

Seria mais fácil se as expressões fossem escritas assim:

x = (3 * 2) + 1

y = 1 + (2 * 3)

Agora está claro para qualquer pessoa o que vai ser executado primeiro, independente do modo como a expressão seja interpretada pela linguagem. Pois saiba que é exatamente assim que um LISP trabalha. Usando a notação prefixa, as expressões acima ficariam:

x = (+ (* 3 2) 1)

y = (+ 1 (* 3 2))

Primeiro será executada a multiplicação, que está nos parênteses mais internos e, em seguida, será executada a adição. Tudo isso sem se preocupar com regras ocultas ou peculiaridades do compilador.

Qualquer código em qualquer dialeto LISP, mesmo com suas características particulares, fica fácil de entender se você lembrar dessas regrinha.

[QuickTips] Usando Shell Script Como Suite De Teste

| | Comments


Indo na linha do programador pragmático, às vezes, dependendo do time ou linguagem do projeto, é muito mais prático usar shell script para testar. Recentemente resolvi usar esta abordagem, e cheguei no seguinte “template” em shell script para testar uma API Server.

Dependências:

  • bash

  • curl

Foi testado no Ubuntu 13.04.

Obs: Vou embedar este gist, e pode ser que não apareça no seu reader ;D.

Obs2: Este post é uma versão pt-br deste post no coderwall Using Shell Script to test your server.

test.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  #!/bin/bash

URL=http://localhost:8080

## Unit-Testable Shell Scripts (http://eradman.com/posts/ut-shell-scripts.html)
typeset -i tests_run=0
function try { this="$1"; }
trap 'printf "$0: exit code $? on line $LINENO\nFAIL: $this\n"; exit 1' ERR
function assert {
    let tests_run+=1
    [ "$1" = "$2" ] && { echo -n "."; return; }
    printf "\nFAIL: $this\n'$1' != '$2'\n"; exit 1
}
## end

###############################################################

try "Example of GET and test for 404 status"

out=$(curl -s -w "%{http_code}" $URL)
assert "404" "$out"

try "Example of POST XML"

# Post xml (from hello.xml file) on /hello
out=$(cat test/hello.xml | curl -s -H "Content-Type: text/xml" -d @- \
  -X POST $URL/hello)
assert "Hello World" "$out"

###############################################################
echo
echo "PASS: $tests_run tests run"

Referências:

Unit-Testable Shell Scripts

Aguardo dicas, sugestões, experiências etc.

A Melhor Linguagem De Programação

| | Comments


Eu tenho a felicidade de ter um sogro que trabalha com desenvolvimento de softwares. Ele tem a experiência de já ter sido empresário e de já ter visto de quase tudo nessa área.

Atualmente ele está trabalhando em um projeto para Web e comentou que está dando preferência a uma ferramenta proprietária que eu particularmente não gosto.

Numa conversa que achei muito produtiva, nós concordamos que, independente de gostar ou não, “software é bola na rede”, onde o importante é entregar o produto, atendendo as necessidades do cliente no menor tempo possível.

Eu trabalhei com PHP por dez anos, com Delphi por seis e estou indo para oito com Java, usando diariamente cada uma dessas linguagens. Já tive experiências com C e trabalho há anos com Ruby e JavaScript, atualmente tenho me concentrado em Clojure e, gostando mais de umas e menos de outras, conheço as forças e fraquezas de cada uma delas.

No final das contas, eu gosto da analogia de que o nosso trabalho se assemelha ao de um carpinteiro/marceneiro (desculpe, mas eu não sei muito bem a diferença entre ambos). Esses profissionais usam várias ferramentas para chegar ao produto final e, ao invés de perderem tempo em fóruns e discussões, eles buscam as ferramentas adequadas a cada tipo de tarefa, buscando entregar o melhor produto no menor tempo e menor custo (entenda ‘melhor produto’ como algo totalmente subjetivo).

Linguagens de programação são meras ferramentas, assim como serrotes, limas, martelos e sei lá que outras ferramentas os profissionais da madeira usam.

Assim sendo, o que é melhor? SASS, SCSS, LESS ou CSS puro? HAML ou ERB? A melhor é aquela que trouxer menos dor de cabeça, custo e tempo de desenvolvimento. Avalie com cautela aquilo que “está na moda” ou “que é o padrão de mercado” e escolha o que for melhor para o que você precisa, usando argumentos técnicos e financeiros, e deixando a paixão de lado.

Aprenda a sua linguagem de trabalho a fundo, e procure conhecer alternativas. Ao me tornar um bom desenvolvedor Ruby, eu aprendi a escrever um código Java melhor. Ao entender LISP, eu me tornei mais produtivo em JavaScript.

Quando for escrever ou comentar algo do tipo “Porque PHP fede”, ou perguntar num fórum “O que é melhor: PL/I ou FORTRAN 66?”, procure estudar mais, entender que nem todo mundo vive a mesma realidade que você e mesmo, algumas vezes, nem todo mundo tem o interesse em aprender tanto quanto você.

Às vezes, o que o outro desenvolvedor quer é apenas entregar o trabalho, receber o pagamento e ir para casa.

P.S.: de qualquer maneira, se você quiser e puder, aprenda o máximo de linguagens que conseguir. Eu acho divertido, e profissionalmente é algo que tem me dado bons resultados.

O Que Eu Aprendi Escrevendo

| | Comments


Apesar de ter um livro e um curso publicados, eu ainda estou longe de ser considerado um escritor. Honestamente, nem ao menos sei o que é necessário para que eu, ou outra pessoa, me considere como tal.

Pretendo escrever outro livro ainda esse ano, mas ainda não tenho nada definido. Escrever um curso ou um livro é algo cansativo, mas muito gratificante. Como não dei espaço entre um e outro, acho que seria bom eu tomar um ar antes de me lançar novamente nessa empreitada.

Seguindo os próprios passos que descrevo abaixo, resolvi separar alguns pontos que considero importantes.

Escrever é um processo iterativo e incremental

O texto não nasce pronto. As vezes uma coisa ou outra está pronta na sua cabeça, mas na hora de colocar no papel a coisa muda. Você esquece algumas partes, lembra de outras, muda a ordem.

O importante é que você coloque suas ideias no papel (ou no site), e depois releia com calma. Mostre para outras pessoas, peça opinião. Eu pensei em um texto por mais de dois anos e publiquei aqui antes de adicionar no livro. O feedback dos leitores foi importantíssimo para que a versão final tivesse o mínimo de erros e o máximo de clareza possível.

Defina bem o seu público

Ao escrever sobre programação funcional em JavaScript, eu tinha bem claro quem é o leitor do 1up. Caso você não tenha definido quem será seu público e quais os requisitos necessários para que possam absorver seu conteúdo, você vai correr o risco de escrever um texto em aramaico para crianças de quarta série ou um texto de quarta série para doutores em línguas mortas.

Pior ainda é quando se tenta abraçar a todos. Seus braços são curtos para abraçar o mundo e, no final, alguma coisa vai acabar caindo no chão.

Trace uma linha

Como o Manifesto Ágil profere, responder a mudanças é mais importante do que seguir um plano, o que não quer dizer que você não precisa de um plano.

Eu costumo traçar um plano, seja como uma lista de tópicos, seja como um mindmap, e vou me guiando por ele até pegar o ritmo. Normalmente essa lista não permanece inalterada por mais de dois capítulos, mas ainda assim é importante você ter algo para te manter no caminho, por mais que esse caminho mude constantemente.

Concentre-se

Eu tenho problemas sérios de concentração, mas em algumas ocasiões consigo despejar quilos de texto ou código de uma única vez. Claro que uma revisão posterior é sempre bem vinda e necessária.

O problema são os culpados de sempre: família exigindo atenção (eles têm prioridade, não pense o contrário); Internet oferecendo todo o tipo de entretenimento; GTalk aberto e seus amigos ali, ao alcance dos dedos.

Escrever é um ato solitário. Lide com isso e concentre-se no que está fazendo.

Arranje tempo

“Eu não tenho tempo” é a desculpa preferida do procrastinador e do cara que quer que os outros acreditem que ele é muito ocupado.

Você tem tempo para conversar no GTalk, para acessar o 9gag, para ver os gols do Fantástico, mas nunca temos tempo para brincar com o filho, para conversar com a esposa (ou marido) ou para fazer aquela meia hora de esteira.

Um terço do meu livro foi escrito dentro de viagens em ônibus, táxis e aviões. Acho que produzi muito mais em uma hora de vôo até o Rio do que em uma tarde inteira jogada fora na frente do computador.

Quando você realmente quer fazer algo, o tempo aparece. Não ter tempo é uma outra forma de dizer “isso não é importante o suficiente para mim”.

‘Pronto’ é melhor do que ‘perfeito’

Depois do livro e do curso prontos e entregues, eu percebi coisas que poderia ter adicionado, frases que poderia ter mudado, assuntos que faltaram. Se existe a possibilidade de adicionar ou mudar, faça, mas não caia na armadilha de ficar polindo algo que já deveria estar em produção há tempos.

Pronto é melhor do que perfeito e, não importa o quanto você tente, seu trabalho nunca vai ficar perfeito.

Divirta-se

Principalmente, divirta-se.

Conheci muitas pessoas que sabem muito mais do assunto que estou escrevendo do que eu, pessoas que deram excelentes sugestões, ideias e me ensinaram a escrever melhor. E em tudo isso eu me diverti, aprendi, aproveitei o momento.

Não se leve tão a sério. É apenas um texto, um post, um curso, um livro. A vida é bem mais do que isso.

Abraço