Automatizar a leitura, armazenamento e visualização de ícones para seus aplicativos (parte 1, 2, 3 e final)

Em Free Icon Packs é possível baixar diversos pacotes temáticos de ícones para utilização gratuita nos seus programas/páginas (pessoais ou comerciais). Pessoalmente eu prefiro ter os ícones no meu computador. Como aqui não é Netflix ou outro serviço do gênero, vou colocar todo o conteúdo em um texto sem parte 1, parte 2 e parte 3. Nem terá a segunda temporada. Vai ser um filme e não uma série. 😉

Procedimento manual

Para baixar os ícones manualmente eu teria que:

  1. Percorrer 15 páginas, baixando 15 arquivos compactados de cada página (225 arquivos)

  2. Descompactar os 225 arquivos compactados

Bem, para a primeira etapa eu teria que clicar no pacote para abrir a página para download, ir até aparte inferior, clicar no botão para download, clicar em salvar, voltar para a página anterior. Supondo que levasse um segundo para cada etapa, seriam 5s por cada pacote. Multiplicando seriam 1125s com cliques e rolagens. Arredondando, uns 19 minutos para a tarefa (se nada ocorrer errado).

A segunda parte demandaria mais dois cliques por arquivo (botão da direita mais descompactar par a a pasta). Arredondando, mais uns 8 minutos.

Poderia baixar um por dia que nem sentiria durante os 225 dias.

Definitivamente, está fora de cogitação efetuar a tarefa manualmente.

Em vez de perder tempo em uma tarefa repetitiva, que deveria ser exclusividade do computador, vou escrever uma receita de bolo para o computador trabalhar. Assim me divirto um pouco e posso aprender alguma coisa nova.

Automatizando o processo

Poderia ter feito todas as etapas utilizando apenas uma linguagem já que todas fazem de tudo. Mas optei por uma abordagem diferente. Utilizar a linguagem que eu acharia mais adequada para cada tarefa.

Primeira etapa – Baixar os arquivos – Red

O processo é fácil. Temos que ler a página, extrair os links, baixar os arquivos e repetir o processo para a próxima página até a última. O programa poderia ser escrito com uma das mil linguagens que podem executar a tarefa, mas escolhi Red. Acho o parser de Red muito mais fácil de utilizar do que ER sem contar a sua integração com a linguagem. A leitura de um arquivo seja ele local ou na internet é o mesmo. Roda no Linux/Windows/Mac, pode ser compilada ou interpretada. O único defeito, no momento, é que só existe a versão 32 bits (estão trabalhando na versão 64bits).

Ler a página

Para ler a página e colocar o conteúdo na variável thePage (variável não é o termo correto em Red mas fica assim para facilitar) basta escrever:

thePage: read https://flaticons.net/free-icon-packs

Simples, não? E olha que REBOL, a linguagem em que Red se baseia é de 1997. Uma facilidade que existia há 26 anos (pode ser mais velha do que muitos que acessam o TabNews). A atribuição é feita pelos dois pontos (como se fosse em um CSS)

Coletar os links para cada pacote da página

Cada página necessita de um estudo para saber onde as coisas estão entre aquele monte de tags para que possamos extrair as informações desejadas. Precisei da seguinte linha para coletar todos os links para os pacotes da página e colocar em um array (também não é o termo correto utilizado por Red):

links: parse thePage [collect [some [thru "learn-more" to "/" keep to "^""]]]

Analisando o bloco do parse:

  • some : irá executar o bloco entre colchetes uma ou mais vezes.

  • thru "learn-more" : irá procurar pela expressão learn-more e colocar o cursor logo após o último e.

  • to "/" : irá procurar a primeira barra após o cursor e posicioná-lo no local da barra.

  • keep to "^"" : irá coletar todo o conteúdo do cursor até antes da primeira aspa que encontrar e passar para o collect para montar o array com os links

O parse também já existia em REBOL há 26 anos. Legível e integrado com a linguagem e não aquele monte de símbolos das expressões regulares que ficariam mais bonitos em linguagens com APL, J, K e outras parecidas.

Ler os arquivos

Depois é só usar um loop foreach para cada link da array links, montar o endereço e e usar write/binary arquivo-local read/binary arquivo-remoto (veja novamente que não existe distinção se o arquivo é local ou remoto, read e write funcionam para os dois e mais um monte de coisas).

Próxima página

O processo é igual ao utilizado para catar os links da página.

parse thePage [thru "Previous Page" thru "href=^"" copy page to "^""]
  • coloca o cursor após Previous Page, move o cursor após “href=^”" copia todo o conteúdo até encontrar a primeira aspas. Nem preciso dizer que copiou o link para a variável page.

Agora é só voltar para ler a próxima página, se o link for válido, efetuando os mesmos procedimentos.

Final da primeira etapa

O programa final ficou com 23 linhas (incluindo duas linhas de comentário e uma em branco).

Segunda etapa – Extrair arquivos – Nim

Resolvi extrair todas as imagens dos arquivos compactados e colocar em uma base de dados (SQLite) para facilitar a futura pesquisa das imagens.

Novamente, poderia ter escolhido qualquer uma das trocentos mil e 4 linguagens disponíveis. Mas escolhi Nim. A sintaxe é bem semelhante com Python, pode gerar código para C/C++ e compilar ou JavaScipt.

O programa para criar o DB, extrair todas as imagens dos arquivos compactados e incluir no DB ficou com 40 linhas (7 são linhas em branco). Ficou assim:

Terceira etapa – Programa para visualizar as imagens – Lazarus

Aquele papo das trocentas linguagens e escolhi Lazarus (freePascal). Para criar um programa com uma interface gráfica, o maior concorrente seria o Delphi (mas aí você vai pagar beeeeeem mais caro).

Basta arrastar os componentes para a janela, informar algumas propriedades (nome do banco de dados e SQL para a seleção dos registros e algumas ligações e está pronto). Ok. Precisaria de alguns controles para selecionar os grupos ou um nome e montar a SQL para filtrar as linhas mostradas. O Basicão ficou assim:

Para não dizer que não escrevi nenhum código, escrevi o seguinte no evento onClick da imagem:

  DBImage1.CopyToClipboard;
  ShowMessage('Copiada para a área de transferência.')    

Quando o usuário clicar na imagem, ela é copiada para a área de transferência e mostra a mensagem. Depois pode ser colada em outro programa.

Fim

E era isso. Quando eu tiver um tempinho e vontade, vou incluir algumas opções de pesquisa, colorização do ícone e outras frescura.

Vejam que foram utilizadas três linguagens diferentes. Na minha humilde opinião, as que eu achava mais adequadas para cada tarefa. Nenhuma linguagem da moda.

NaN = ON ERROR RESUME NEXT

Não pretendo discutir se ovo tem ou não cabelo. Apenas mostrar alguns detelhes de que, considerar algo não numérico ou que não pode ser representado por um número como um número não é interessante para a programação e pode trazer efeitos indesejados. Na realidade, pretendo mostrar um efeito catastrófico que fica mais fácil para assimilar.

Novo automóvel

A empresa está desenvolvendo um carro super-ultra avançado que irá, sem a intervenção do motorista, efetuar frenagem para não colidir com um obstáculo evitando acidentes. Um item de segurança que você não poderá mais viver sem. Existem centenas de sensores no carro que passam informações para o programa tomar a decisão de salvar vidas e evitar danos materiais.

Lógica

Vamos desenvolver um sistema com as seguintes características:

  • Calcular distância segura

    • velocidade / raizquadrada dos dados do sensor nan – distância + 4.8 (sensor nan é conjunto de sensores discretos desenvolvidos especialmente para o veículo que retorna um valor entre 1 e 5 baseados em inúmeros fatores como velocidade do vento, qualidade do asfalto, calibragem dos peneus e mais uma centena de itens)
  • Se a distancia segura for menor ou igual a distancia do objeto, o sistema aciona o freio do carro para tentar miniizar a colisão e emite sinal sonoro para o motorista (se ele não viu o objeto, pode estar distraído).

Programa

Poderia ser em qualquer linguage que considere NaN = Number mas vamos escolher Rust que é uma linguagem segura para dar mais confiabilidade. Um candidato para o código poderia ser:

fn distancia_segura() -> f64 {
	velocidade() / f64::sqrt(sensor_nan()) - distancia() + 4.8
}

fn iniciar_frenagem() {
	println!("Iniciar frenagem");
	println!("Alertar motorista");	
}

fn main() {	
    if distancia_segura() <= distancia() {
		iniciar_frenagem();
	}
	println!("Viagem segura!");	
}

Não foi detectada nenhuma falha após testes com diversas combinações de velocidades, distâncias do objeto e valores do sensor nan. Pronto para colocar o programa no sistema do automóvel.

Passeio de uma família feliz

Um casal com três filhos entre 3 anos e 6 meses, um gato, um papagaio e um hamster (não confundir com Rammstein) resolveu comprar o carro pois as crianças distraiam o motorista nas viagens. Foi todo mundo para o carro, pegaram a estrada e, como a velocidade máxima era de 110 km/h, colocaram o piloto automático em 105 km/h (carro sem piloto automático não é moderno). Todos alegres dentro do carro, o pai olhando para trás fazendo bilu bilu para o filho de 6 meses, a mãe apartando a briga dos outros dois mas, como o carro era seguro, tudo tranquilo.

Só que o hamster roeu um dos sensores e o gato mijou em outro. Os dois era utilizados pelo sensor nan que começou a retornar -1.

Na estrada havia uma retroescavadeira que deu uma pane elétrica e parou no meio da pista. No carro, que estava a 104,7 km/h e a 5 m de distância da retroescavadeira ………

Oooops

Como assim a mais de 100 km/h e apenas 5 m de distância? O carro é seguro. O sensor nan? As crianças? O gato? O hamster? O casal? Vai acontecer uma tragédia. O que houve? O que houve? O que houve? O que houve!!!!!?????

Voltando um pouco no tempo

Como os animais estragaram o sensor nan e ninguém previu a possibilidade de retornar valores negativos aliados ao fato da linguagem trabalhar com NaN sem acusar erro, teremos um problema. Vejamos como se comprtaria a linguagem:

fn distancia() -> f64 {
	5.0 // lida do sensor
}

fn velocidade() -> f64 {
	104.7 // lida do sensor
}

fn sensor_nan() -> f64 {
	-1.0 // um sensor que deveria retornar 1 .. 5 deu problema
}

fn distancia_segura() -> f64 {
	velocidade() / f64::sqrt(sensor_nan()) - distancia() + 4.8
}

fn iniciar_frenagem() {
	println!("Iniciar frenagem");
	println!("Alertar motorista");	
}

fn main() {	
    println!("Distância  = {} m",distancia());
    println!("Valocidade = {} km/h",velocidade());
    println!("Dist.Seg.  = {} km/h",distancia_segura());
    if distancia_segura() <= distancia() {
		iniciar_frenagem();
	}
	println!("Viagem segura!");	
}

Executando o programa (você pode executar o código no playground do Rust) temos:

Distância  = 5 m
Valocidade = 104.7 km/h
Dist.Seg.  = NaN km/h
Viagem segura!
  • Como o resultado do sensor nan foi negativo o resultado de f64::sqrt(sensor_nan())foi NaN.

  • Os cálculos efetuados com NaN não geraram erro e retornaram NaN.

  • A função distancia_segura() que deveria retornar um Float retornou um NaN.

  • A expressão if distancia_segura() <= distancia() sempre irá retornar False pois NaN é um número que não é comparável com um número e retorna NaN.

Se a linguagem gerasse uma exceção no cálculo da raiz de número negativo como algumas que geram DomainError o programa iria abortar e, algum programa supervisor poderia alertar o motorista de uma falha no sistema. Da mesma forma, gerar um erro no retorno da função pois o programador queria explicitamente um Float e veio um NaN (um Float que não existe).

Me lembra claramente a instrução do BASIC ON ERROR RESUME NEXT que servia para que o programa continuasse executando a próxima instrução em caso de erro. Pelo menos era uma opção do programador. Não tenho ideia se existe uma chave para desligar a propagação automática do NaN .

Resultado

Primeira página do jornal, Cinco pessoas mortas ou feridas em tragédia com o novo carro super seguro.

Conclusão

Sim, NaN pode ser considerado prejudicial. Para uma linguagem inteligente, $\sqrt{-1} = 1i$ . Ou retorna um erro do tipo Ei, você está trabalhando com a linguagem "tal" e não com LISP. Sim, LISP retorna um número complexo e não um NaN.

Como se não bastasse, a propagação através de funções pode causar uma boa dor de cabeça para encontrar onde está o erro.

Apesar de ter sido uma brincadeira (de mau gosto) é um fato que poderia acontecer na vida real. Existem sistema para suporte à vida, que controlam carros e aviões entre outros. Até mesmo um refrigerador que poderia ficar ligado sempre causando um consumo excessivo de energia ou desligado estragando os alimentos.

É possível criar um programa para editar imagens em menos de 4 minutos (incluindo editar uma imagem)? SIM!

As vezes eu gosto de chutar o balde. Então vamos lá.

Tudo começou com dois posts com títulos mentirosos ou “superfaturados”. O primeiro foi um “Twitter clone em 15 minutos” (Elixir & CIA) e o segundo, provavelmente uma resposta para não ficar atrás foi “Build a Twitter clone em 10 minutos” (Ruby & CIA). Ou eles não sabem o que significa clone, ou não sabem o que é o Twitter ou foi apenas ma fé mesmo. Acho que foi má fé para a promoção das linguagens. O fato é que necessitam de um bom tempo a mais para ficarem prontos. Então, supondo-se um ano, o título deveria ser “Cone do Twitter em um ano e 10/15 minutos”. O terceiro foi “How to Build a To-Do List App” (Rust). Apesar de não mentir, este até foi legal pois compilei a aplicação e vou enviar de presente para todos os meus inimigos. Tem que se odiar para usar aquilo (ROTFL).

Para a tarefa do título utilizei o Lazarus que é um clone do Delphi criado pela Borland em 1995 (exato, tem 25 anos). A linguagem utilizada é FreePascal. Certamente é o melhor ambiente para a criação de GUIs para desktop (não só), compilando para Linux, Windows e MacOS (nunca usei um Mac).

Interessante salientar que o programa foi desenvolvido no Linux e usa o GIMP para a edição final das imagens. Pode até parecer enganação, mas no Linux é comum a saída de um programa servir de entrada para outro mais apto para a tarefa. Para editar o assembly de um arquivo é possível fazer, por exemplo: objdump -d executavel | vim -.

Se eu quisesse algo mais profissional, poderia clonar e compilar o LazPaint. Acho mais fácil, baixar o LazPaint (é possível baixar para 32 ou 64, Linux, Windows w Mac)

Outro aspecto muito bom do Lazarus é a conectividade com BDs. É possível conectar com um banco de dados sem escrever uma linha código. Ok, poderá ser necessário informar alguns dados como local, usuário, senha, porta, etc. Selecionar uma tabela (tipo SELECT * FROM tabela) e está pronta a aplicação CRUD. Com um clique é possível selecionar o BD como PostgreSQL, Oracle, Firebird, MariaDB, Sqlite entre outros e não precisa mudar nada no programa.

Acho que da década de 60 até hoje, o Delphi foi o maior avanço para o desenvolvimento desktop/GUI. Diversas linguagens também surgiram, com suas características específicas (mais para CLI ou web). Antigamente (bem antigamente), uma linguagem mais ou menos verbosa fazia diferença no tempo de desenvolvimento. Hoje, os editores fazem boa parte do serviço sujo. E se ele foi criado para uma determinada linguagem, é mais inteligente ainda.

Acho que vou ter que escrever mais algumas coisas sobre o Lazarus. Mas, por enquanto, é isso e mais um quilo de farinha.

Labirintos

Para brincar um pouco mais com Red, decidi criar um programinha para gerar labirintos. Por enquanto é só isso, não é um jogo ou coisa parecida.
editado: já é possível movimentar o personagem até a saída.

Ok. Achei alguns algoritmos e resolvi implementar três. Primeiro foi o Recursive Backtracking que por ser recursivo pode gerar estouro de pilha. O segundo foi o Binary Tree algorithm que pode gerar labirintos grandes sem se preocupar muito com a memória. O terceiro foi o Sidewinder algorithm que também não é recursivo mas se consegue alguns padrões interessantes alterando apenas uma variável.

Recursive backtracker
Binary tree
Sidewinder com peso 2
Sidewinder com peso 20

Os algoritmos foram convertidos de Ruby para Red com relativa facilidade (um pouco por estar meio desacostumado com Ruby e outro por não estar bem familiarizado com Red). Como incluí três algoritmos no mesmo contexto, em vez de carve_passages_from nomeei a função com o nome do algoritmo, no caso. recursive-backtracker

Ruby

N, S, E, W = 1, 2, 4, 8
DX         = { E =&gt; 1, W =&gt; -1, N =&gt;  0, S =&gt; 0 }
DY         = { E =&gt; 0, W =&gt;  0, N =&gt; -1, S =&gt; 1 }
OPPOSITE   = { E =&gt; W, W =&gt;  E, N =&gt;  S, S =&gt; N }

def carve_passages_from(cx, cy, grid)
  directions = [N, S, E, W].sort_by{rand}
  directions.each do |direction|
    nx, ny = cx + DX[direction], cy + DY[direction]
    if ny.between?(0, grid.length-1) &amp;&amp; nx.between?(0, grid[ny].length-1) &amp;&amp; grid[ny][nx] == 0
      grid[cy][cx] |= direction
      grid[ny][nx] |= OPPOSITE[direction]
      carve_passages_from(nx, ny, grid)
    end
  end
end

carve_passages_from(0, 0, grid)

Red

set [N S E W] [1 2 4 8]
DX:       #(E: 1 W: -1 N:  0 S: 0)
DY:       #(E: 0 W:  0 N: -1 S: 1)
OPPOSITE: #(E: 8 W:  4 N:  2 S: 1)

recursive-backtracker: func[cx cy /local nx ny directions][
    directions: random copy [n s e w]
    foreach direction directions [
        nx: cx + DX/:direction
        ny: cy + DY/:direction
        if all [(ny &gt;= 1) (ny = 1) (nx &lt;= width) (grid/:ny/:nx = 0)][
            grid/:cy/:cx: grid/:cy/:cx or get direction
            grid/:ny/:nx: grid/:ny/:nx or OPPOSITE/:direction
            recursive-backtracker nx ny
        ]
    ]
]

recursive-backtracker 1 1 

Tirando os detalhes da linguagem, ficou quase um por um. Algumas obervações:

  • Como blocos etc iniciam com um (1), não foi necessário incluir coisas como length-1. YMMV mas eu sempre achei que arrays e outras estruturas deveriam iniciar em 1 e não zero. Fora facilitar para a CPU, não acho natural pegar o item zero de uma lista de 10 elementos nem o nono elemento elemento (10-1) da lista para pegar o 10. Respeito se você acha o contrário, mas você está errado. 😀
  • Poderia ter feito as atribuições de nx e ny na mesma linha utilizando set [nx ny] reduce [cx + DX/:direction cy + DY/:direction]. Como Red é uma linguagem homoicônica, sem reduce nx ficara com o valor cx e ny com +.
  • Red não tem between?. Como significa apenas (n >= min) && (n <= max), basta utilizar all que retorna verdadeiro se todas as comparações resultarem verdadeiro (nem precisaria os parêntesis colocados acima e ficaria como all [(ny >= 1) (ny <= height) (nx >= 1) (nx <= width) (grid/:ny/:nx = 0)]).

As rotinas para o desenho dos labirintos ficam para uma outra vez.

Os fontes podem ser encontrados no Github.