Trello Atalho para Localizar Cards Rapidamente via Navegador Chrome

CHROME > Settings > Search Engine > Manage search engines and site search > Site search > Add > Add search engine

O Trello é amplamente conhecido pela sua versatilidade tanto na forma liberal de utilização dos cartões quanto na formação de URI dos painéis e cartões do KANBAN.

Com um simples HASH e uma URL específica é possível localizar um cartão ou um painel simplesmente digitando na barra de endereço do navegador, porém esta tarefa repetitiva de digitar a URL pode se tornar rapidamente cansativa.

É aí que entra este recurso do Google Chrome onde usar a barra de endereço do navegador como Search Engine pode ser customizada.

Depois da configuração acima ser efetivada, vá para o navegador Chrome e na barra de endereço basta digitar t <espaço> e em seguida o HASH do seu cartão <enter>

Para quem precisa de orientação mais detalhada segue abaixo a sequência de images para encontrar o caminho da configuração.

Agora é só curtir!

Stream Editor sed Substituir texto \n por Quebra de Linha via Linha de Comando

#linux
sed -i 's/\\n/\n/g' linefeed.sql

#macOS
sed -i '' 's/\\n/\n/g' linefeed.sql

Hoje novamente me deparei com um bloco de texto de linha única, ou seja as quebras de linha foram geradas como string texto \n e não como o caracter linefeed ou carriage return como eu esperava.

Bom, não importa, isso acontece eventualmente então fazer a substituição manual via comando sed é a forma mais rápida de alcançar este objetivo.

Explicando rapidamente o comandos acima…

sed é o acrônimo para Stream Editor

-i significa in place, ou seja, vai alterar o próprio arquivo de entrada

's/\\n/\n/g' é um regex pattern, s para substituir, \\n conteúdo a ser substituído, \n conteúdo a ser aplicado, g troca global ou múltiplas ocorrências.

As barras / funcionam apenas como delimitadores do regex pattern.

linefeed.txt é o nome do arquivo de entrada, ou seja o nosso arquivo alvo 

Observe que no ambiente MacOS temos uma ligeira diferença onde um parâmetro adicional surge antes do regex pattern. Deu muiiiiiiito trabalho pra descobrir isso!

Criando o arquivo com o conteúdo necessário para simular o problema.

echo 'CREATE TABLE usuario (\\n id BIGINT(20) NOT NULL AUTO_INCREMENT,\\n nome VARCHAR(255) NOT NULL,\\n login VARCHAR(50) NOT NULL,\\n email VARCHAR(255) NOT NULL,\\n senha VARCHAR(50) NOT NULL,\\n telefone_fixo VARCHAR(50) DEFAULT NULL,\\n telefone_celular VARCHAR(50) DEFAULT NULL,\\n timezone VARCHAR(100) DEFAULT NULL,\\n ativo TINYINT(3) UNSIGNED NOT NULL,\\n created_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6),\\n updated_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),\\n PRIMARY KEY (id),\\n UNIQUE KEY ixu_login (login),\\n KEY idx_usuario_created_at (created_at),\\n KEY idx_usuario_updated_at (updated_at)\\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;' > linefeed.sql

Listando e conferindo que temos apenas uma única linha no arquivo.

cat linefeed.sql

Para os amantes do editor vi podemos fazer essa mesma substituição com a seguinte sequência de comandos.

vi linefeed.sql
:1,$ s/\\n/\r/g <enter>
:wq

Observe que neste comando o conteúdo a ser aplicado deve ser \r e não \n

É claro que você poderia se livrar de toda essa parafernália de comandos e simplesmente utilizar um editor de textos que tenha um suporte esperto, como Notepad++ ou VsCode, e simplesmente Localizar e Substituir strings por LF CR.

Mas fica aqui a minha pergunta. Por que você iria querer isto?

Thanks for watching!!!

MySQL Montar Script SQL CREATE TABLE a partir de uma Tabela Existente

SHOW CREATE TABLE usuario;

Eu sempre costumo escrever meus scripts SQL para criação de tabelas a partir do zero, normalmente utilizando alguma ferramenta de diagramação como DBDesigner por exemplo.

Mas trabalhando em um projeto recente minha tarefa era transferir algumas tabelas de um determinado banco de dados para outro, assim o SCHEMA a ser criado deveria ser exatamente o mesmo incluíndo os índices para evitar qualquer tipo de impacto na aplicação.

Ai que medo de digitar errado!!!

Pesquisando rapidamente encontrei artigos orientando o uso de um comando específico pra isso. Em um só comando o script para criação do SCHEMA sem dados incluíndo chaves primárias, chaves estrangeiras, chaves únicas e índices, e ainda atributos de CHARTSET e outros penduricalhos.

É claro que você consegue chegar ao mesmo resultado por outros meios como utilizando o MYSQLDUMP sem dados de uma única tabela, mas este comando aqui já faz tudo isso sem maiores complicações.

Enjoy Your Day!

PHP Script Transformando Linhas de um Arquivo em Cláusula SQL IN

php process-file-lines-into-sql-in-clause.php --filename=original-file-with-ids-extracted-from-spreadsheet-column --quotes=none --split=1000

Acima a sintaxe de como invocar o script, abaixo o script ele mesmo.

<?php
const HINT_FILENAME = "php process-file-lines-into-sql-in-clause.php --filename=my-text-file-name.txt";
const HINT_QUOTES = HINT_FILENAME . " --quotes=double|single|none";
const HINT_SPLIT = HINT_QUOTES . " --split=1000|none";

$options = getopt(null, ["filename:","quotes:","split:"]);

(!array_key_exists('filename',$options))
    ? die("\n*** Missing argument --filename *** ... sample of usage:\n\n" . HINT_FILENAME . "\n\n")
    : null;

(!array_key_exists('quotes',$options))
    ? die("\n*** Missing argument --quotes *** ... sample of usage:\n\n" . HINT_QUOTES . "\n\n")
    : null;

(!array_key_exists('split',$options))
    ? die("\n*** Missing argument --split *** ... sample of usage:\n\n" . HINT_SPLIT . "\n\n")
    : null;

$filename = $options['filename'];
$quotes = $options['quotes'];
$split = $options['split'];

switch ($quotes) {
  case "double":
    $quotes = '"';
    break;
  case "single":
    $quotes = "'";
    break;
  case "none":
  default:
    $quotes = "";
}

$filename_original_renamed = date('Ymd') . '-source-' . $filename;
$file_content_rows = file($filename, FILE_IGNORE_NEW_LINES);

$split = is_numeric($split) ? (int)$split : count($file_content_rows);
$chunks = array_chunk($file_content_rows, $split);

echo "\n\nYour file lines are being processed and converted to a string of SQL IN clause";
echo "\nTotal Rows=" . count($file_content_rows);
echo "\n";

$glue = (empty($quotes)) ? ',' : $quotes . ',' . $quotes;

foreach ($chunks as $chunk => $chunk_rows) {
    $chunk_label = count($chunks) > 1 ? '-chunk-' . str_pad($chunk+1, 3, '0', STR_PAD_LEFT) : '-lines-' . $split;
    $filename_processed_renamed = date('Ymd') . '-processed-' . $filename . $chunk_label;
    $stringifyEmails = $quotes . implode($glue, $chunk_rows) . $quotes;
    file_put_contents($filename_processed_renamed, $stringifyEmails);

    echo "\nFile content processed and save onto a renamed file... " . $filename_processed_renamed;
}

rename($filename, $filename_original_renamed);

echo "\n\nYour original source file has been renamed to... " . $filename_original_renamed;
echo "\n\n";

Pessoal sem delongas… Sabem quando você tem aquela planilha lotada de IDs que passaram pra você fazer um SQL?

Pois é, até dá pra formatar na mão os IDs como sendo uma lista separadas por vírgulas e eventualmente recobertos por aspas ou algo assim.

Mas e se a planilha tiver 100.000 linhas ou mais? E se o SQL que você precisa rodar for na verdade um DML? E se você precisar executar as ações em volumes menores pra não sobrecarregar o servidor? Hã?

Pra isso eu desenvolvi este super script Tabajara que resolve o seu problema, veja que legal o pocesso.

Passo 1) copie e cole a coluna de IDs num arquivo texto tipo Brackets, Notepad++, VI ou outro editor qualquer de sua preferência, e salve no disco.

1140178
1141171
1166736
1177320
1193872
1212530

Passo 2) execute o script conforme demonstrado no primeiro bloco deste artigo, logo vou explicar as opções.

php process-file-lines-into-sql-in-clause.php --filename=original-file-with-ids-extracted-from-spreadsheet-column --quotes=none --split=none

Passo 3) colete no próprio diretório os arquivos processados com conteúdo no formato SQL IN.

1140178,1141171,1166736,1177320,1193872,1212530

Passo 4) se precisar, colete seu arquivo original que foi renomeado com o termo “source“.

Explicando as opções do Script:

  • filename nome do arquivo fonte contendo os respectivos elementos, sempre 1 por linha
  • quotes deve ser informado um valor entre double|single|none para que elemento seja recoberto por aspas no caso de ser uma string
  • split deve ser informado um valor inteiro com a quantidade de registros máximos a serem gerados no arquivo processado. No caso serão gerados tantos arquivos quanto necessário que serão separados como chunks, contendo sempre a quantidade máxima exceto o último que poderá conter um volume menor.

Pronto, você está pronto para copiar a string com os IDs para serem inseridos numa cláusula SQL IN. Fique sabendo também que exatamente a mesma formatação para lista do SQL IN serve para criar um array no PHP, basta recobrir com squared brackets [] ao invés de brackets ().

Pra finalizar quero citar que o mesmo resultado pode ser alcançado com editores de texto ou IDEs com o auxílio do mouse e bastante paciência.

Eu também sei que dá pra fazer isso com o editor de texto VI usando macro substituições, mas eu nunca fiz e não sei fazer, mas eu sei quem sabe!

Mas isso é assunto para outro artigo!

GIT Branch Eliminando uma Branch no Ambiente Local e Remoto

// delete branch locally with force
git branch -D nome-da-branch-que-eu-nao-quero-mais

// delete branch on remote
git push origin --delete nome-da-branch-que-eu-nao-quero-mais

Pessoal, aqui um jeito simples de se livrar de uma branch que você não quer mais, são comandos diferentes para eliminar localmente e no servidor remoto.

Observe que localmente o -D em maiúsculo representa o FORCE. Se a branch em questão já foi sincronizada remotamente o force é requisito obrigatório. Se a branch foi criada somente localmente então você poderia utilizar uma sintaxe opcional mesmo agressiva.

// delete branch locally without force
git branch -d nome-da-branch-que-eu-nao-quero-mais

O segundo comando é para eliminar a branch no servidor remoto. Ambos os comandos podem ser utilizados de forma independente, ou seja, você pode querer eliminar a branch local apenas, ou remota apenas, ou em ambos os locais que é o mais corriqueiro.

Alternativamente tem uma outra sintaxe para eliminar a branch no servidor remoto, mas é um tanto difícil de recordar esta sintaxe, ainda mais em se tratando de um delete. Não recomendo utilizar sem conferir o manual duas vezes.

// delete branch on remote
git push origin :nome-da-branch-que-eu-nao-quero-mais

Observe também que este procedimento é bastante similar ao artigo onde descrevo o processo de renomear uma branch, os comandos a serem utilizados acabam se repetindo, mas a sequência não é a mesma.

God save Git!

PHP ARRAY Parsing String Como Array Bidimensional no Hacker Rank

//Hacker Rank Input
//1 10 11 9 8 7 6 5 2 8 12, 7 58 2 34 76 88 2 5 3 8 11, 9 65 3 8 98 36 22 87 66 99 0

/* Read and parse input */
$handle = fopen ('php://stdin', 'r');
$string_array = fgets($handle);

$array = explode(', ', $string_array);

array_walk($array, function (&$element) {
    $element = explode(' ', $element);
});

// do your job with bidimensional $array

Via de regra, quando estamos no ambiente Hacker Rank, para obtermos um array de um enunciado de um problema em PHP temos que fazer o parsing de uma string dada as características do input pelo STDIN.

As coisas podem se complicar bastante se o parsing não for bem executado, e é claro que com a pressão da competição, é o que geralmente acontece.

O problema não é do problema em si, por que depois que passa a competição e a pressão você olha pra ele e diz… PQP!

Então fica aí a dica, basta observar com calma, e principalmente observar os delimitadores. Neste caso o primeiro delimitador não é simplesmente uma vírgula, mas sim dois caracteres vírgula e espaço combinados.

# wrong delimiter applied on first level 
$array = explode(',', $string_array);

// [0]=> string(24) "1 10 11 9 8 7 6 5 2 8 12"
// [1]=> string(26) " 7 58 2 34 76 88 2 5 3 8 11"
// [2]=> string(29) " 9 65 3 8 98 36 22 87 66 99 0

Deste ponto em diante as coisas se complicam, pois apesar de termos um array unidimensional de três elementos, o próximo explode vai gerar o segundo nível com mais elementos do que o esperado por conter um espaço a mais.

# wrong quantity of elements on second level
array_walk($array, function (&$element) {
    $element = explode(' ', $element);
});

// [0]=> array(11)
// [1]=> array(12)
// [2]=> array(12)

Agora não tem mais desculpas, essa é pra não esquecer mais.

Dang it!

PHP Criando DateTime com Construtor sem Máscaras de Formatação

$string_date = '2010-08-07 08:39:00';
[$y, $m, $d] = [null, null, null];

switch(true) {
  case preg_match('/^(\d{4})-(\d{2})-(\d{2})/', $string_date, $fragments):
    list($y, $m, $d) = [$fragments[1], $fragments[2], $fragments[3]];
    break;
  case preg_match('/^(\d{4})\/(\d{2})\/(\d{2})/', $string_date, $fragments):
    list($y, $m, $d) = [$fragments[1], $fragments[2], $fragments[3]];
    break;
  case preg_match('/^(\d{2})-(\d{2})-(\d{4})/', $string_date, $fragments):
    list($d, $m, $y) = [$fragments[1], $fragments[2], $fragments[3]];
    break;
  case preg_match('/^(\d{2})\/(\d{2})\/(\d{4})/', $string_date, $fragments):
    list($m, $d, $y) = [$fragments[1], $fragments[2], $fragments[3]];
    break;
  default:
    throw new Exception('String Date supplied with unknown format');
}

if (!checkdate($m, $d, $y)) {
  throw new Exception ('String Date supplied with invalid date content');
}

try {
  $object_datetime = new DateTime($string_date);
} catch (\Exception $exception) {
  echo 'String Date supplied impossible to convert to Date';
  $object_datetime = null;
}

null !== $object_date
  ? $object_date->setTime(0,0)
  ; null;

var_dump($object_datetime);

Pessoal, este código surgiu da necessidade de se converter um campo de data fornecido como string numa integração entre sistemas.

O fato é que quando se depende de informações externas o processo pode facilmente quebrar quando as regras estabelecidas não forem estritamente respeitadas.

No meu caso um campo que deveria ser enviado no formato date Y-m-d acabou sendo enviado num formato DateTime Y-m-d H:i:s. O problema se agravou pelo fato de que apenas um dos fornecedores acabou enviando desta forma e os demais fornecedores enviavam a informação no formato acordado.

Dúvida cruel… solicitar ao fornecedor para corrigir o envio da informação? Ou flexibilizar a entrada do dados de forma inteligente?

Opção dois na veia! O fato é que todos os fornecedores continuavam enviando uma data válida pra nós, só que com formatos ou máscaras diferentes, mas ainda assim eram datas válidas.

Baseado neste preceito a solução foi substituir a criação do objeto DateTime calcada no createFromFormat a partir de máscaras previamente conhecidas por simplesmente passar a string date recebida para o construtor do DateTime.

// stop creating date based on known format, this will fail when datetime content is sent
$object_datetime = DateTime::createFromFormat('Y-m-d', $string_date);

// start creating datetime based on constructor, this will work with any valid date/time content
$object_datetime = new DateTime($string_date);

A grande sacada consiste em validar se uma data válida foi enviada, no caso fazemos esta validação para a porção Y-m-d somente. Se a validação passar neste nível a probabilidade da informação ser uma data válida inclusive na sua porção hora será muito grande.

Para exemplificar a flexibilidade de entrada desta informação, segue abaixo alguns exemplos de formatos válidos que irão funcionar com o construtor.

$object_datetime = new DateTime('2010-08');
var_dump($object_datetime); //2010-08-01 00:00:00.000000

$object_datetime = new DateTime('2010-08 08:39');
var_dump($object_datetime); //2010-08-01 08:39:00.000000

$object_datetime = new DateTime('2010-08 08:39:53');
var_dump($object_datetime); //2010-08-01 08:39:53.000000

$object_datetime = new DateTime('2010-08T08:39:53Z');
var_dump($object_datetime); //2010-08-01 08:39:53.000000

$object_datetime = new DateTime('2010-08-07');
var_dump($object_datetime); //2010-08-07 00:00:00.000000

$object_datetime = new DateTime('2010-08-07 08:39');
var_dump($object_datetime); //2010-08-07 08:39:00.000000

$object_datetime = new DateTime('2010-08-07 08:39:53');
var_dump($object_datetime); //2010-08-07 08:39:53.000000

$object_datetime = new DateTime('2010-08-07T08:39:53Z');
var_dump($object_datetime); //2010-08-07 08:39:53.000000

Considere ainda que podemos receber barras ao invés de hífen como separador que irá funcionar corretamente com todas as variações acima apresentadas.

$object_datetime = new DateTime('2010/08/07 08:39:53');
var_dump($object_datetime); //2010-08-07 08:39:53.000000

$object_datetime = new DateTime('2010/08/07T08:39:53Z');
var_dump($object_datetime); //2010-08-07 08:39:53.000000

Outra grande vantagem de se utilizar o construtor do DateTime é que ele vai funcionar com o formato nativo americano m/d/Y quando separado por barras e com o formato nativo britânico m-d-Y quando separado por hífens.

$object_datetime = new DateTime('08/07/2010 08:39:53');
var_dump($object_datetime); //2010-08-07 08:39:53.000000

$object_datetime = new DateTime('07-08-2010 08:39:53');
var_dump($object_datetime); //2010-08-07 08:39:53.000000

Lembrando que em nenhum momento tivemos que informar máscaras de formatação para efetivamente criar o objeto DateTime. Simplesmente mandamos a string date para o construtor e pronto.

E caso a informação final deva ser uma data sem a porção hora, simplesmente zeramos esta porção depois do objeto criado, assim:

$object_datetime = new DateTime('08/07/2010 08:39:53');
var_dump($object_datetime); //2010-08-07 08:39:53.000000

$object_datetime->setTime(0,0);
var_dump($object_datetime); //2010-08-07 00:00:00.000000

Depois desta demonstração de flexibilidade de formatos acredito que as vantagens de criação do DateTime utilizando o construtor tenham ficado claras.

E é claro que o código do início do artigo merece um refactoring para ser mais CleanCode e também para suportar os demais formatos aceitos pelo construtor do DateTime, mas para fins didáticos preferi mantê-lo mais simples mesmo.

E agora? Troca ou não troca o DateTime::createFromFormat pelo new DateTime constructor? Você decide…

Happy code!

GIT Init Comandos Para Iniciar um Novo Repositório com GitHub

cd easyclip
git init

git add README.md
git commit -m "first commit"

git remote add origin https://githug.com/mjaning/easyclip

git push -u origin master

git add .gitignore
git commit -m "adding gitignore"
git push origin master

Mesmo trabalhando com versionamento de arquivos há um bom tempo, até recentemente eu nunca tinha iniciado um repositório de um projeto desde o zero, não com GIT pelo menos.

Foi quando um pequeno projeto em que eu trabalhava em meu tempo livre tomou forma, então decidi hospedar ele no GitHub. Confesso que tive que procurar por vídeos no YouTube e acabei encontrando um muito bom da Rafaella Ballerini.

Então o primeiro passo foi criar o projeto completamente vazio no GitHub, para obter o endereço https no servidor.

E foi bem nessa sequência que eu executei os passos, primeiro commit do arquivo README.md, registro do endereço no server com o git remote e o git push -u pra testar se iria chegar no GitHub.

Em seguida, antes do commit dos arquivos do projeto eu preparei o .gitignore por que é natural termos arquivos que não devem fazer parte da base do projeto, como arquivos de dados e credenciais, arquivos de configuração local como IDE’s entre outros.

Observe que a partir do segundo push não precisamos da opção -u, pois o projeto já subiu uma primeira vez e já está linkado com o servidor podemos assim dizer.

Se você trabalha direto criando novos projetos do zero provavelmente irá decorar esta sequência e não vai precisar desta colinha. Porém, se assim como eu, fica muito tempo trabalhando em projetos continuamente e não pratica o git init com frequência, certamente vai acabar precisando de uma ajuda pra refrescar a memória.

Be my guest!

GIT Stash Salvando Também os Arquivos Não Rastreados

git status
// Untracked files: new_file.php

git stash --include-untracked
git status
// nothing to commit, working tree clean

git stash pop
git status
// Untracked files: new_file.php

Por definição o GIT não inclui os arquivos não rastreados quando estamos fazendo um stash. Esse comportamento padrão tem vantagens no meu ponto de vista por que na maior parte das vezes podemos mantê-los na working area sem dificuldades.

Mas e quando surgir aquela situação onde você tem uma série de arquivos não rastreados, e quer salvar sua alteração em andamento tudo numa stash só incluindo todo o pacote, arquivos modificados e arquivos novos não rastreados?

Para estas situações o GIT fornece a opção –include-untracked no comando git stash. Opcionalmente você pode utilizar o shorthand -u

git stash -u

E sempre tem possibilidade de se adicionar manualmente os arquivos não rastreados para staging area e então executar um git stash simples.

git add new_file.php
git stash

O inconveniente desta última opção é que, ao restaurar o arquivo ele vai aparecer na staging area enquanto que com a opção de arquivos não rastreados a restauração será feita diretamente na working area como arquivo não rastreado.

Recomendo ainda utilizar o recurso de nomear o stash, o que provavelmente fará muito sentido quando você querer salvar arquivos não rastreados.

Já escrevi um artigo aqui no blog que ensina como nomear o seu stash, é só pesquisar “como nomear seu stash”.

Voilà!

PHP Array Comparando Variáveis Escalares Combinadas num IF Único

$expected_email = "myself@email.com";
$expected_birthdate = "25/12/0001";

$received_email = "not matching email";
$received_birthdate = "not matching date";

if ([$received_email, $received_birthdate] != [$expected_email, $expected_birthdate]) {
    echo 'Expected combination Email/Birthdate does not match';
}

Olha só que legal, nunca tinha pensado em montar um IF como esses comparando uma lista de variáveis escalares com uma outra lista também de variáveis escalares.

Basicamente isto é uma comparação de arrays e o match se dá pela comparação de forma posicional, ou seja, índice 0 com índice 0 e índice 1 com índice 1.

Caso precise comparar inclusive os tipos de dados basta adicionar mais um igual a operador.

if ([$received_email, $received_birthdate] !== [$expected_email, $expected_birthdate]) {
    echo 'Expected combination Email/Birthdate does not match';
}

Qual a vantagem desse código sobre um código convencional? Não sei dizer muito bem qual seria…

if ($received_email != $expected_email || $received_birthdate != $expected_birthdate) {
    echo 'Expected combination Email/Birthdate does not match';
}

O mais importante é que funciona conforme o projetado.

Happy PHP!