Este livro de bolso tem como objetivo, abordar conceitos avançados sobre a linguagem em questão. Existem diversos recursos disponíveis para você começar a programar com Typescript, mas este em especial vai te levar um pouco alem para quem esta desejando ter suas habilidades polidas.
Uma linguagem nasce com um propósito dado a diversos fatores e o principal é a necessidade de expandir as coisas que você desenvolve e foi para isto que esta linguagem foi criada. Com recursos que muitos procuravam no Javascript, porque Typescript é Javascript com tipos e transpilado. Sua compilação produz um código que tem total compatibilidade com especificações já existentes em diversos motores de execução.
Não vamos abordar configurações especificar por considerar que este guia seja, avançado ou seja você deve saber como isto funciona para dar continuidade. Entregaremos uma experiência bastante profunda para estudo e reflexão, contando com padrões de projeção, desenvolvimento solido e segurança de suas aplicações.
Ao começarmos a projetar uma aplicação o mais importante em todas as etapas é você saber contar uma historia envolvendo um ator, sendo ela de pequeno, médio ou grande porte. Esta historia pode ser contada de acordo com os fatos que se passam no desenvolvimento de um ecosistema inteiro. Nós daremos inicio em uma aplicação por Javascript por ser assim que o Typescript deu inicio para finalizarmos uma aplicação simples ou avançada. Todo desenvolvimento, tem uma linha de raciocinio dado a possibilidade de sua inferencia lógica. E não estamos falando da inferencia que ocorre do lado da compilação da linguagem.
Saber decidir quais algoritimos utilizar e quais padrões arquiteturais você pode investir contra a projeção é importante. Como por exemplo uma ferramenta para busca de texto em arquivos ou fluxos de dados, devemos começar por seu teste, sabendo o que esperar de sua interface. Lembrando que você não deve despejar tudo para fora, você deve contar a historia passo após passo até chegar em seu nível mais avançado sabendo o que exatamente quer pensando em casos que contemple até mesmo iterações de uso da mesma.
Você não deve ser groceiro e implementar algo pobre quando tem a devida oportunidade, para isto você deve práticar todos os dias, disciplinas que entreguem maior aptidão no uso de suas ferramentas. Desenhos são formas interessantes de se conectar com a aplicação que você vai desenvolver, mas normalmente tomam tempo, então salvo a dada cituação nós vamos mostrar como fazer o uso adequado de todos os recursos presentes em Javascript e Typescript por sua vez.
Você deve pensar no seguinte quando esta desenvolvendo uma aplicação e a quantidade de pessoas presente no projeto conta e a fragmentação das tarefas devem estar bem claras para você dar continuidade, estimar tempo é um tarefa extremamente dificil então optamos sempre por estimar dado a sua complexidade. Você deve ter isto bem claro em sua mente, então vamos a primeira tarefa.
app.js
'use strict';
const assert = require('assert');
assert.ok(search('foo', 'foobar'));
Neste ponto eu poderia estimar que a complexidade desta tarefa é de um ponto com o limite de oito pontos. O que devemos fazer e completar a criação do código acima com uma implementação que cubra os requisitos da asserção. Lembrando que temos em mente a busca de texto que pode ser muito mais do que o teste atual. Mas precisamos iniciar a projeção e conforme evoluirmos com ela, cobriremos um cenario maior que o atual. Executando este código nós teremos uma exceção presente no nosso código que é o esperado.
'use strict';
const assert = require('assert');
function search(term, text) {
return (new RegExp(term)).test(text);
}
assert.ok(search('foo', 'foobar'));
Com este código nós temos a primeira fase do nosso código completa, então podemos seguir executando o código e vendo que o teste passou, não vamos permanecer com esta implementação por muito tempo, porque devemos passar de fase o mais rápido possível. Temos uma função de busca que retorna um booleano e agora teremos uma que retorna a quantidade de vezes que o termo aparece no texto em inteiro.
'use strict';
const assert = require('assert');
function search(term, text) {
return (new RegExp(term)).test(text);
}
assert.ok(search('foo', 'foobar'));
assert.equal(times('baz', 'bazfoobarbaz'), 2);
Com isto feito podemos seguir, e devemos alcançar 3 funções principais para o nosso programa que são:
- Search
- Times
- Match
É tudo o que precisamos para o comportamento básico de nossa aplicação.
'use strict';
const assert = require('assert');
function search(term, text) {
return (new RegExp(term)).test(text);
}
assert.ok(search('foo', 'foobar'));
function times(term, text) {
return text.match((new RegExp(term, 'g'))).length;
}
assert.equal(times('baz', 'bazfoobarbaz'), 2);
Com isto nós temos duas de nossas funções implementadas, note que todas elas fazem o uso de RegExp para retornar o comportamento esperado por seus testes. Perceba que padrões estão surgindo para nós tirarmos vatagem de suas repetições.
'use strict';
const assert = require('assert');
function search(term, text) {
return (new RegExp(term)).test(text);
}
assert.ok(search('foo', 'foobar'));
function times(term, text) {
return text.match((new RegExp(term, 'g'))).length;
}
assert.equal(times('baz', 'bazfoobarbaz'), 2);
assert.deepEqual(match('baz', 'foobarbaz'), 'foobarbaz');
Os pontos de complexidade foram um para cada uma das tarefas, por ser bastante simples de cobrir os casos de uso, mas isto não significa que a tua aplicação esta bem testada. Para garantirmos o funcionamento de uma aplicação nós devemos ir além do esperado e cobrir casos com os devidos erros que ela deve retornar.
'use strict';
const assert = require('assert');
function search(term, text) {
return (new RegExp(term)).test(text);
}
assert.ok(search('foo', 'foobar'));
function times(term, text) {
return text.match((new RegExp(term, 'g'))).length;
}
assert.equal(times('baz', 'bazfoobarbaz'), 2);
function match(term, text) {
return text.match(new RegExp(term)).input;
}
assert.equal(match('baz', 'foobarbaz'), 'foobarbaz');
Após falharmos o código passou, e temos aqui um primeiro ponto de refatoração do código que deve ser feito apenas quando tudo esta passando. Até aqui nada de muito complexo foi encontrado mas temos uma quantidade de código consideravel.
Tendo em vista que nossa aplicação esta completamente mapeada por onde devemos partir para chegarmos ao objetivo de termos uma aplicação de busca completa por texto alguns avanços foram feitos, considerando que este guia avança para mostrar uma nova perspectiva de como construir aplicações seguras e robustas. Este é o código atual que vai receber varias novas implemntações mas esta é a completude do mesmo.
'use strict';
const assert = require('assert');
function thrownArgumentException(text, term) {
if (typeof term !== 'string' || typeof text !== 'string') {
throw new Error('Each argument, must be a string');
}
}
function search(term, text) {
thrownArgumentException(term, text);
return (new RegExp(term)).test(text);
}
assert.ok(search('foo', 'foobar'));
assert.throws(() => {
search(1, 10);
}, {
name: 'Error',
message: 'Each argument, must be a string'
});
function times(term, text) {
thrownArgumentException(term, text);
return text.match((new RegExp(term, 'g'))).length;
}
assert.equal(times('baz', 'bazfoobarbaz'), 2);
assert.throws(() => {
times(1, 10);
}, {
name: 'Error',
message: 'Each argument, must be a string'
});
function match(term, text) {
thrownArgumentException(term, text);
return text.match(new RegExp(term)).input;
}
assert.equal(match('baz', 'foobarbaz'), 'foobarbaz');
assert.throws(() => {
match(1, 10);
}, {
name: 'Error',
message: 'Each argument, must be a string'
});
class Text {
constructor(content) {
this.content = content;
this.setContent = this.setContent.bind(this);
this.getContent = this.getContent.bind(this);
this.search = this.search.bind(this);
this.times = this.times.bind(this);
this.match = this.match.bind(this);
}
setContent(value) {
this.content = value;
return this;
}
getContent() {
return this.content;
}
search(term) { return search(term, this.getContent()); }
times(term) { return times(term, this.getContent()); }
match(term) { return match(term, this.getContent()); }
}
let txt = new Text('foobarbaz');
assert.ok(txt instanceof Text);
assert.equal(txt.content, 'foobarbaz', 'Text content not settled');
assert.ok(txt.setContent('foobarbazbuzz') instanceof Text);
assert.equal(txt.getContent(), 'foobarbazbuzz');
assert.ok(txt.search('buzz'));
txt.setContent('fuzzbarfuzzbuzzfuzz');
assert.equal(txt.times('fuzz'), 3);
assert.equal(txt.match('buzz'), 'fuzzbarfuzzbuzzfuzz');
Aqui nos temos algumas verificações de entrada, implementação de uma classe de texto para usufluirmos de funções mais polidas mesmo que seja considerado por alguns que ela não seja uma boa abordagem, mas lembrando que estamos usando para estudar afundo quais os pros e contras de se utilizar uma aplicação escrita em Typescript. Temos que práticar desta forma para ter total controle e visão do que estamos fazendo. Alguns padrões de projeção estão surgindo para termos uma qualidade maior no reuso.
Você deve começar tudo em um mesmo lugar, mesmo que fique gigante o seu arquivo até alcançar uma versão estavel do objetivo final, isto só vai beneficiar o seu conhecimento do que esta acontecendo e se adaptar a diferentes tipos de ambientes entregando maior agilidade. Dado este fato, nós podemos estimar esta entrega por volto dos oito pontos de complexidade.
Neste ponto vamos definir dois tipos de possibilidades que são as seguintes:
- Atraveçar os arquivos em busca do termo procurado
- Entrada de dados através de uma canalização padrão
Estamos em um ponto que é importante projetar nossos recursos para cobrir mais casos de uso que só estarão mais visiveis conforme a evolução do programa, com isto é simples de visualizar qual o panorama final.
Nesta implementação estamos utilizando recursos do Node.js e o ideal é que possamos contar também com uma implementação que possa ser executada utilizando o Deno, que deixaremos para o futuro do programa quando todos os recursos estiverem prontos para o usuário final.
'use strict';
const assert = require('assert');
function thrownArgumentException(text, term) {
if (typeof term !== 'string' || typeof text !== 'string') {
throw new Error('Each argument, must be a string');
}
}
function search(term, text) {
thrownArgumentException(term, text);
return (new RegExp(term)).test(text);
}
assert.ok(search('foo', 'foobar'));
assert.throws(() => {
search(1, 10);
}, {
name: 'Error',
message: 'Each argument, must be a string'
});
function times(term, text) {
thrownArgumentException(term, text);
return text.match((new RegExp(term, 'g'))).length;
}
assert.equal(times('baz', 'bazfoobarbaz'), 2);
assert.throws(() => {
times(1, 10);
}, {
name: 'Error',
message: 'Each argument, must be a string'
});
function match(term, text) {
thrownArgumentException(term, text);
return text.match(new RegExp(term)).input;
}
assert.equal(match('baz', 'foobarbaz'), 'foobarbaz');
assert.throws(() => {
match(1, 10);
}, {
name: 'Error',
message: 'Each argument, must be a string'
});
class Text {
constructor(content) {
this.content = content;
this.setContent = this.setContent.bind(this);
this.getContent = this.getContent.bind(this);
this.search = this.search.bind(this);
this.times = this.times.bind(this);
this.match = this.match.bind(this);
}
setContent(value) {
this.content = value;
return this;
}
getContent() {
return this.content;
}
search(term) { return search(term, this.getContent()); }
times(term) { return times(term, this.getContent()); }
match(term) { return match(term, this.getContent()); }
}
let txt = new Text('foobarbaz');
assert.ok(txt instanceof Text);
assert.equal(txt.content, 'foobarbaz', 'Text content not settled');
assert.ok(txt.setContent('foobarbazbuzz') instanceof Text);
assert.equal(txt.getContent(), 'foobarbazbuzz');
assert.ok(txt.search('buzz'));
txt.setContent('fuzzbarfuzzbuzzfuzz');
assert.equal(txt.times('fuzz'), 3);
assert.equal(txt.match('buzz'), 'fuzzbarfuzzbuzzfuzz');
const isTTY = process.stdin.isTTY;
const { Transform } = require('stream');
const args = process.argv.slice(2);
function textMatchContentTransformFactory() {
const opts = {
transform(raw, encoding, callback) {
let text = new Text(raw.toString());
if (text.search(args[0])) {
this.push(Buffer.from(text.match(args[0])));
}
callback();
}
};
return new Transform(opts);
}
if (!isTTY) {
process.stdin.pipe(textMatchContentTransformFactory()).pipe(process.stdout);
}
O comportamento puro da leitura de uma entrada foi completo através de uma linha de processamento, com isto podemos estimar que esta tarefa tenha em torno de três pontos de complexidade para verificar se o tipo de entrada não é TTY recolher argumentos da linha de comando e depois transformar a entrada para algo que possamos manipular com as funções presentes em nossa implementação passada e de fato fazer o que estavamos imaginando verificando se o termo esta presente, e depois extraindo esta entrada. Como previsto varios comportamentos podem ser inseridos para melhorar nossa aplicação, mas isto vai ser abordado quando portarmos nossa aplicação para Typescript ou seja o melhor esta por vir.
Não devemos ter apego com as nossas aplicações, mas toda mudança deve ser introduzida com uma boa reflexão.
Com o nosso programa funcionando de forma estavél na versão que paramos, vamos a parte mais interessante do proceso que é executar a refatoração para o Typescript. Nós só vamos precisar ajustar a classe que criamos mudando nome e declarando uma variavel no escopo dela para que possamos utilizar nosso programa mas como isto é apenas parte de nossas ações, para que possamos de fato utilizar padrões mais sofisticados aqui esta nossas mudanças.
app.ts
'use strict';
const assert = require('assert');
function thrownArgumentException(text, term) {
if (typeof term !== 'string' || typeof text !== 'string') {
throw new Error('Each argument, must be a string');
}
}
function search(term, text) {
thrownArgumentException(term, text);
return (new RegExp(term)).test(text);
}
assert.ok(search('foo', 'foobar'));
assert.throws(() => {
search(1, 10);
}, {
name: 'Error',
message: 'Each argument, must be a string'
});
function times(term, text) {
thrownArgumentException(term, text);
return text.match((new RegExp(term, 'g'))).length;
}
assert.equal(times('baz', 'bazfoobarbaz'), 2);
assert.throws(() => {
times(1, 10);
}, {
name: 'Error',
message: 'Each argument, must be a string'
});
function match(term, text) {
thrownArgumentException(term, text);
return text.match(new RegExp(term)).input;
}
assert.equal(match('baz', 'foobarbaz'), 'foobarbaz');
assert.throws(() => {
match(1, 10);
}, {
name: 'Error',
message: 'Each argument, must be a string'
});
class TextContent {
content: string;
constructor(content: string) {
this.content = content;
this.setContent = this.setContent.bind(this);
this.getContent = this.getContent.bind(this);
this.search = this.search.bind(this);
this.times = this.times.bind(this);
this.match = this.match.bind(this);
}
setContent(value) {
this.content = value;
return this;
}
getContent() {
return this.content;
}
search(term) { return search(term, this.getContent()); }
times(term) { return times(term, this.getContent()); }
match(term) { return match(term, this.getContent()); }
}
let txt = new TextContent('foobarbaz');
assert.ok(txt instanceof TextContent);
assert.equal(txt.content, 'foobarbaz', 'TextContent content not settled');
assert.ok(txt.setContent('foobarbazbuzz') instanceof TextContent);
assert.equal(txt.getContent(), 'foobarbazbuzz');
assert.ok(txt.search('buzz'));
txt.setContent('fuzzbarfuzzbuzzfuzz');
assert.equal(txt.times('fuzz'), 3);
assert.equal(txt.match('buzz'), 'fuzzbarfuzzbuzzfuzz');
const isTTY = process.stdin.isTTY;
const { Transform } = require('stream');
const args = process.argv.slice(2);
function textMatchContentTransformFactory(filePath='') {
const opts = {
transform(raw, encoding, callback) {
let text = new TextContent(raw.toString());
if (text.search(args[0])) {
if (!!filePath) console.log(filePath);
this.push(Buffer.from(text.match(args[0])));
}
callback();
}
};
return new Transform(opts);
}
const fs = require('fs');
const path = require('path');
function traverse(dirPath, dirs=[]) {
let dir = fs.readdirSync(dirPath, {
withFileTypes: true
});
let nestedDirs = dir.filter(curr => curr.isDirectory() &&
!(curr.name.indexOf('.') === 0));
let nestedFiles = dir.filter(curr => curr.isFile() &&
!(curr.name.indexOf('.') === 0));
for (let file of nestedFiles) {
let curr = path.resolve(dirPath, file.name);
let currStream = fs.createReadStream(curr);
currStream.pipe(textMatchContentTransformFactory(curr)).pipe(process.stdout);
}
for (let entrypoint of nestedDirs) {
let curr = path.resolve(dirPath, entrypoint.name);
traverse(curr);
}
}
if (!isTTY) {
process.stdin.pipe(textMatchContentTransformFactory()).pipe(process.stdout);
} else if (isTTY) {
// traverse directories
traverse(process.cwd());
}
Neste ponto, devemos analisar quais as próximas refatorações importantes a serem feitas, que podem ser as seguintes:
- Separar os testes
- Optimizar o uso de memoria e processamento de dados
- Implementar um algoritimo de busca mais eficiente
- Devolver o numero da linha que ocorre o padrão de texto que estamos buscando
- Sumarizar o texto na ocasião em especifico quando o padrão aparece
Nesta primeira refatoração nós separamos um arquivo para o programa e outro para os testes, e este mesmo padrão se replica para novas mudanças.
app.ts
'use strict';
function thrownArgumentException(text, term) {
if (typeof term !== 'string' || typeof text !== 'string') {
throw new Error('Each argument, must be a string');
}
}
export function search(term, text): boolean {
thrownArgumentException(term, text);
return (new RegExp(term)).test(text);
}
export function times(term, text) {
thrownArgumentException(term, text);
return text.match((new RegExp(term, 'g'))).length;
}
export function match(term, text) {
thrownArgumentException(term, text);
return text.match(new RegExp(term)).input;
}
export class TextContent {
content: string;
constructor(content: string) {
this.content = content;
this.setContent = this.setContent.bind(this);
this.getContent = this.getContent.bind(this);
this.search = this.search.bind(this);
this.times = this.times.bind(this);
this.match = this.match.bind(this);
}
setContent(value) {
this.content = value;
return this;
}
getContent() {
return this.content;
}
search(term) { return search(term, this.getContent()); }
times(term) { return times(term, this.getContent()); }
match(term) { return match(term, this.getContent()); }
}
const isTTY = process.stdin.isTTY;
const { Transform } = require('stream');
const args = process.argv.slice(2);
function textMatchContentTransformFactory(filePath='') {
const opts = {
transform(raw, encoding, callback) {
let text = new TextContent(raw.toString());
if (text.search(args[0])) {
if (!!filePath) console.log(filePath);
this.push(Buffer.from(text.match(args[0])));
}
callback();
}
};
return new Transform(opts);
}
const fs = require('fs');
const path = require('path');
function traverse(dirPath, dirs=[]) {
let dir = fs.readdirSync(dirPath, {
withFileTypes: true
});
let nestedDirs = dir.filter(curr => curr.isDirectory() &&
!(curr.name.indexOf('.') === 0));
let nestedFiles = dir.filter(curr => curr.isFile() &&
!(curr.name.indexOf('.') === 0));
for (let file of nestedFiles) {
let curr = path.resolve(dirPath, file.name);
let currStream = fs.createReadStream(curr);
currStream.pipe(textMatchContentTransformFactory(curr)).pipe(process.stdout);
}
for (let entrypoint of nestedDirs) {
let curr = path.resolve(dirPath, entrypoint.name);
traverse(curr);
}
}
if (!isTTY) {
process.stdin.pipe(textMatchContentTransformFactory()).pipe(process.stdout);
} else if (isTTY && !module.parent) {
// traverse directories
traverse(process.cwd());
}
app.test.ts
'use strict';
const assert = require('assert');
import { search } from './app';
import { times } from './app';
import { match } from './app';
import { TextContent } from './app';
describe('Text Content Search', () => {
it('should search for a term', () => {
assert.ok(search('foo', 'foobar'));
});
it('should throws arguments exception for search', () => {
assert.throws(() => {
search(1, 10);
}, {
name: 'Error',
message: 'Each argument, must be a string'
});
});
});
describe('Text Content Times', () => {
it('should have times of a term', () => {
assert.equal(times('baz', 'bazfoobarbaz'), 2);
});
it('should throws arguments exception for times', () => {
assert.throws(() => {
times(1, 10);
}, {
name: 'Error',
message: 'Each argument, must be a string'
});
});
});
describe('Text Content Match', () => {
it('should have match of term', () => {
assert.equal(match('baz', 'foobarbaz'), 'foobarbaz');
});
it('should throws arguments exception for match', () => {
assert.throws(() => {
match(1, 10);
}, {
name: 'Error',
message: 'Each argument, must be a string'
});
});
});
describe('Text Content', () => {
let txt = null;
before(() => {
txt = new TextContent('foobarbaz');
});
it('should be an instance of', () => {
assert.ok(txt instanceof TextContent);
});
it('should be an content', () => {
assert.ok(txt.content, 'foobarbaz', 'TextContent content not settled');
});
it('should be set a content', () => {
assert.ok(txt.setContent('foobarbazbuzz') instanceof TextContent);
});
it('should be get a content', () => {
assert.equal(txt.getContent(), 'foobarbazbuzz');
});
it('should be search by a term in content', () => {
assert.ok(txt.search('buzz'));
});
it('should be have times of term occured on the content', () => {
txt.setContent('fuzzbarfuzzbuzzfuzz');
assert.equal(txt.times('fuzz'), 3);
});
it('should be match by term on the content', () => {
assert.equal(txt.match('buzz'), 'fuzzbarfuzzbuzzfuzz');
});
});
Com as mudanças feitas podemos seguir para as próximas implementações. A optimização foi feita através da utilização do buffer, como fonte para nossas manipulações, com isto nós podemos analisar que foram utilizados em torno de seis pontos de complexidade para ela ser concluida.
app.test.ts
'use strict';
const assert = require('assert');
import { search } from './app';
import { times } from './app';
import { match } from './app';
import { TextContent } from './app';
describe('Text Content Search', () => {
it('should search for a term', () => {
assert.ok(search('foo', 'foobar'));
});
it('should throws arguments exception for search', () => {
assert.throws(() => {
search(1, 10);
}, {
name: 'Error',
message: 'Each argument, must be a string'
});
});
});
describe('Text Content Times', () => {
it('should have times of a term', () => {
assert.equal(times('baz', 'bazfoobarbaz'), 2);
});
it('should throws arguments exception for times', () => {
assert.throws(() => {
times(1, 10);
}, {
name: 'Error',
message: 'Each argument, must be a string'
});
});
});
describe('Text Content Match', () => {
it('should have match of term', () => {
assert.equal(match('baz', 'foobarbaz'), 'foobarbaz');
});
it('should throws arguments exception for match', () => {
assert.throws(() => {
match(1, 10);
}, {
name: 'Error',
message: 'Each argument, must be a string'
});
});
});
describe('Text Content', () => {
let txt = null;
before(() => {
txt = new TextContent('foobarbaz');
});
it('should be an instance of', () => {
assert.ok(txt instanceof TextContent);
});
it('should be an content', () => {
assert.ok(txt.content, 'foobarbaz', 'TextContent content not settled');
});
it('should be set a content', () => {
assert.ok(txt.setContent('foobarbazbuzz') instanceof TextContent);
});
it('should be get a content', () => {
assert.equal(txt.getContent(), 'foobarbazbuzz');
});
it('should be search by a term in content', () => {
assert.ok(txt.search('buzz'));
});
it('should be have times of term occured on the content', () => {
txt.setContent('fuzzbarfuzzbuzzfuzz');
assert.equal(txt.times('fuzz'), 3);
});
it('should be match by term on the content', () => {
assert.equal(txt.match('buzz'), 'fuzzbarfuzzbuzzfuzz');
});
});
describe('Text Content processing buffer', () => {
let txtBuff = null;
before(() => {
txtBuff = new TextContent(Buffer.from('foobarbaz'));
});
it('should be an instance of', () => {
assert.ok(txtBuff instanceof TextContent);
});
it('should be an content', () => {
assert.ok(txtBuff.content, Buffer.from('foobarbaz'), 'TextContent content not settled');
});
it('should be a buffer', () => {
assert.ok(txtBuff.isBuffer(Buffer.from('bar')));
});
it('should be search by a term in content as buffer', () => {
assert.ok(txtBuff.search(Buffer.from('bar')));
});
it('should be have times of term occured on the content as buffer', () => {
txtBuff.setContent(Buffer.from('fuzzbarfuzzbuzzfuzz'));
assert.equal(txtBuff.times(Buffer.from('fuzz')), 3);
});
it('should be match by term on the content as buffer', () => {
assert.equal(txtBuff.match(Buffer.from('buzz')), 'fuzzbarfuzzbuzzfuzz');
})
});
app.ts
'use strict';
function thrownArgumentException(text, term) {
if (typeof term !== 'string' || typeof text !== 'string') {
throw new Error('Each argument, must be a string');
}
}
export function search(term, text): boolean {
thrownArgumentException(term, text);
return (new RegExp(term)).test(text);
}
export function times(term, text) {
thrownArgumentException(term, text);
return text.match((new RegExp(term, 'g'))).length;
}
export function match(term, text) {
thrownArgumentException(term, text);
return text.match(new RegExp(term)).input;
}
export class TextContent {
content: any;
constructor(content: any) {
this.content = content;
this.setContent = this.setContent.bind(this);
this.getContent = this.getContent.bind(this);
this.isBuffer = this.isBuffer.bind(this);
this.search = this.search.bind(this);
this.times = this.times.bind(this);
this.match = this.match.bind(this);
}
setContent(value) {
this.content = value;
return this;
}
getContent() {
return this.content;
}
isBuffer(value) {
return value.constructor.toString().indexOf('Buffer') > -1;
}
search(term) {
if (this.isBuffer(term) && this.isBuffer(this.getContent())) {
return this.getContent().indexOf(term) > -1;
}
return search(term, this.getContent());
}
times(term, t=-1, o=0) {
if (this.isBuffer(term) && this.isBuffer(this.getContent())) {
let out = o;
let curr = this.getContent().indexOf(term, t + 1);
if (curr > -1) {
out += 1;
return this.times(term, t=curr + 1, out);
}
return out;
}
return times(term, this.getContent());
}
match(term) {
if (this.isBuffer(term) && this.isBuffer(this.getContent())) {
let out = Buffer.from('');
if (this.search(term)) {
out = this.getContent();
}
return out;
}
return match(term, this.getContent());
}
}
const isTTY = process.stdin.isTTY;
const { Transform } = require('stream');
const args = process.argv.slice(2);
function textMatchContentTransformFactory(filePath='') {
const opts = {
transform(raw, encoding, callback) {
let text = new TextContent(raw);
if (!!args.length && text.search(Buffer.from(args[0]))) {
if (!!filePath) console.log(filePath);
this.push(text.match(Buffer.from(args[0])));
}
callback();
}
};
return new Transform(opts);
}
const fs = require('fs');
const path = require('path');
function traverse(dirPath, dirs=[]) {
let dir = fs.readdirSync(dirPath, {
withFileTypes: true
});
let nestedDirs = dir.filter(curr => curr.isDirectory() &&
!(curr.name.indexOf('.') === 0));
let nestedFiles = dir.filter(curr => curr.isFile() &&
!(curr.name.indexOf('.') === 0));
for (let file of nestedFiles) {
let curr = path.resolve(dirPath, file.name);
let currStream = fs.createReadStream(curr);
currStream.setMaxListeners(100000);
currStream.pipe(textMatchContentTransformFactory(curr)).pipe(process.stdout);
}
for (let entrypoint of nestedDirs) {
let curr = path.resolve(dirPath, entrypoint.name);
traverse(curr);
}
}
process.stdin.setEncoding('utf-8');
process.stdout.setMaxListeners(100000);
if (!isTTY) {
process.stdin.pipe(textMatchContentTransformFactory()).pipe(process.stdout);
} else if (isTTY && !module.parent) {
// traverse directories
traverse(process.cwd());
}
Estamos avançando para uma versão bastante promissora que é a seguinte.
'use strict';
function thrownArgumentException(text, term) {
if (typeof term !== 'string' || typeof text !== 'string') {
throw new Error('Each argument, must be a string');
}
}
export function search(term, text): boolean {
thrownArgumentException(term, text);
return (new RegExp(term)).test(text);
}
export function times(term, text) {
thrownArgumentException(term, text);
return text.match((new RegExp(term, 'g'))).length;
}
export function match(term, text) {
thrownArgumentException(term, text);
return text.match(new RegExp(term)).input;
}
export class TextContent {
content: any;
constructor(content: any) {
this.content = content;
this.setContent = this.setContent.bind(this);
this.getContent = this.getContent.bind(this);
this.isBuffer = this.isBuffer.bind(this);
this.search = this.search.bind(this);
this.times = this.times.bind(this);
this.match = this.match.bind(this);
}
setContent(value) {
this.content = value;
return this;
}
getContent() {
return this.content;
}
isBuffer(value) {
return value.constructor.toString().indexOf('Buffer') > -1;
}
search(term) {
if (this.isBuffer(term) && this.isBuffer(this.getContent())) {
return this.getContent().indexOf(term) > -1;
}
return search(term, this.getContent());
}
times(term, t=-1, o=0) {
if (this.isBuffer(term) && this.isBuffer(this.getContent())) {
let out = o;
let curr = this.getContent().indexOf(term, t + 1);
if (curr > -1) {
out += 1;
return this.times(term, t=curr + 1, out);
}
return out;
}
return times(term, this.getContent());
}
match(term) {
if (this.isBuffer(term) && this.isBuffer(this.getContent())) {
let out = {};
let lines = [];
let line = [];
for (let i = 0; i < this.getContent().length; i += 1) {
let chr = this.getContent()[i];
if (chr === Buffer.from('\n')[0]) {
lines.push(line);
line = [];
} else if (i === this.getContent().length - 1) {
line.push(chr);
lines.push(line);
line = [];
} else {
line.push(chr);
}
}
for (let i in lines) {
let curr = Buffer.from(lines[i]);
if (curr.indexOf(term) > -1) {
out[parseInt(i, 10) + 1] = curr;
}
}
return out;
}
return match(term, this.getContent());
}
}
const isTTY = process.stdin.isTTY;
const { Transform } = require('stream');
const args = process.argv.slice(2);
function textMatchContentTransformFactory(filePath='') {
const opts = {
transform(raw, encoding, callback) {
let text = new TextContent(raw);
if (!!args.length && text.search(Buffer.from(args[0]))) {
if (!!filePath) console.log(filePath);
let matches = text.match(Buffer.from(args[0]));
let lines = [];
Object.keys(matches).forEach(k => {
lines.push(`${k}:${matches[k].toString().replace((new RegExp(args[0], 'g')), '\x1b[100m' + args[0] + '\x1b[49m')}`);
});
this.push(Buffer.from(`${lines.join('\n')}\n`));
}
callback();
}
};
return new Transform(opts);
}
const fs = require('fs');
const path = require('path');
function traverse(dirPath, dirs=[]) {
let dir = fs.readdirSync(dirPath, {
withFileTypes: true
});
let nestedDirs = dir.filter(curr => curr.isDirectory() &&
!(curr.name.indexOf('.') === 0));
let nestedFiles = dir.filter(curr => curr.isFile() &&
!(curr.name.indexOf('.') === 0));
for (let file of nestedFiles) {
let curr = path.resolve(dirPath, file.name);
let currStream = fs.createReadStream(curr);
currStream.setMaxListeners(100000);
currStream.pipe(textMatchContentTransformFactory(curr)).pipe(process.stdout);
}
for (let entrypoint of nestedDirs) {
let curr = path.resolve(dirPath, entrypoint.name);
traverse(curr);
}
}
process.stdin.setEncoding('utf-8');
process.stdout.setMaxListeners(100000);
if (!isTTY) {
process.stdin.pipe(textMatchContentTransformFactory()).pipe(process.stdout);
} else if (isTTY && !module.parent) {
// traverse directories
traverse(process.cwd());
}
Com isto estabilizamos a nossa versão e podemos fazer o que bem entender com ela desta forma, inclusives melhorias de como o programa funciona. Fica o exercicio que pode ser executado por você, esta livre para vasculhar e implementar melhorias no funcionamento do programa.
Este programa pode ser extendido de várias formas e alcançar um nível de conhecimento mais polido e útil, para o seu dia a dia. Estamos abertos para novas implementações até que finalizemos a obra por completo, de forma coletiva e inteligênte.
Obrigado a você que leu e opinou sobre o livro.
Após utilizar a ferramenta logo vemos que existem varios modos de se fazer a mesma coisa, mas como podemos realizar esta tarefa enquanto estamos projetando a ferramenta? É simples, aplicaremos tecnicas que serve com o propósito de manter uma analise constante.
'use strict';
const assert = require('assert');
const { exec } = require('child_process');
exec('echo "foobar" | node app.js foo', (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
assert.equal(stdout, '1:\x1b[100m' + 'foo' + '\x1b[49mbar\n');
});
Anter de portarmos a nossa solução para o Deno cobriremos vários casos de uso que a ferramenta pode ser utilizada. Com este código nos testamos uma das formas que ele pode ser utilizada. Mas em outras baterias de testes descobrimos que o processo aglutina quando executa o modo varredura, por isto devemos investigar nossa implementação e decidirmos se é interessante manter este tipo de cobertura.
Neste caso utilizei algumas linhas de python que é ótimo para casos como estes em sistemas linux e unix por ter um suporte grande a funções que não precisam de tanto desprendimento de tempo e esforço.
import os
node = 'node app.js biz'
node_output = os.popen(node).read()
assert (len(node_output) > 0)
Depois de testarmos o minimo do uso da linha de comando, nós vamos dar atenção a implementação util em outros motores de execução, neste caso nós devemos seguir o que esta sendo levantado como exceção pelo motor, neste caso o Deno.
Para esta implementação nós vamos ter que lidar com um método chamado "traverse" que varre os diretorios e quando encontra arquivos procura pela ocorrencia de texto e outro comportamento util é a utilização de um código que faz a busca na entrada de texto padrão.
'use strict';
import { existsSync } from 'https://deno.land/std/fs/mod.ts';
function thrownArgumentException(text: string, term: string): void {
if (typeof term !== 'string' || typeof text !== 'string') {
throw new Error('Each argument, must be a string');
}
}
export function search(term: string, text: string): boolean {
thrownArgumentException(term, text);
return (new RegExp(term)).test(text);
}
export function times(term: string, text: string): number {
thrownArgumentException(term, text);
let out = 0;
let matched = text.match((new RegExp(term, 'g')));
if (matched !== null) {
out = matched.length;
}
return out;
}
export function match(term: string, text: string): string {
thrownArgumentException(term, text);
let out = '';
let matched = text.match(new RegExp(term));
if (matched !== null && matched.input !== undefined) {
out = matched.input;
}
return out;
}
export class TextContent {
content: any;
constructor(content: any) {
this.content = content;
this.setContent = this.setContent.bind(this);
this.getContent = this.getContent.bind(this);
this.isBuffer = this.isBuffer.bind(this);
this.search = this.search.bind(this);
this.times = this.times.bind(this);
this.match = this.match.bind(this);
}
setContent(value: any) {
this.content = value;
return this;
}
getContent() {
return this.content;
}
isBuffer(value: Deno.Buffer) {
return value.constructor.toString().indexOf('Buffer') > -1;
}
search(term: any) {
if (this.isBuffer(term) && this.isBuffer(this.getContent())) {
return this.getContent().indexOf(term) > -1;
}
return search(term, this.getContent());
}
times(term: any, t: number =-1, o: number = 0): any {
if (this.isBuffer(term) && this.isBuffer(this.getContent())) {
let out = o;
let curr = this.getContent().indexOf(term, t + 1);
if (curr > -1) {
out += 1;
return this.times(term, t=curr + 1, out);
}
return out;
}
return times(term, this.getContent());
}
match(term: any): any {
if (this.isBuffer(term) && this.isBuffer(this.getContent())) {
let out: { [k: string]: any } = {};
let lines = [];
let line = [];
for (let i = 0; i < this.getContent().length; i += 1) {
let chr = this.getContent()[i];
if (chr === '\n'.charCodeAt(0)) {
lines.push(line);
line = [];
} else if (i === this.getContent().length - 1) {
line.push(chr);
lines.push(line);
line = [];
} else {
line.push(chr);
}
}
for (let i in lines) {
let curr = lines[i];
if (curr.indexOf(term) > -1) {
out[(parseInt(i, 10) + 1)] = curr;
}
}
return out;
}
return match(term, this.getContent());
}
}
export class Lines {}
async function traverse(dir=Deno.cwd()) {
if (Deno.cwd() !== dir) {
dir = Deno.cwd() + '/' + dir;
} if (existsSync(dir)) {
for await (const dirEntry of Deno.readDir(dir)) {
if (dirEntry.isFile) {
let decoder = new TextDecoder('utf-8');
let rawPath = Deno.cwd() + '/' + dirEntry.name;
if (existsSync(rawPath)) {
let raw = await Deno.readFile(rawPath);
let data = decoder.decode(raw);
let text = new TextContent(data.toString());
if (text.search(Deno.args[0])) {
let matches = text.match(Deno.args[0]);
let output = matches.replace((new RegExp(Deno.args[0], 'g')), '\x1b[100m' + Deno.args[0] + '\x1b[49m');
console.log(`${output}`);
}
}
} else if (dirEntry.isDirectory) {
traverse(dirEntry.name);
}
}
}
}
if (!!Deno.args.length && Deno.isatty(Deno.stdin.rid)) {
traverse();
} else if (!!Deno.args.length && !Deno.isatty(Deno.stdin.rid)) {
(async () => {
let decoder = new TextDecoder('utf-8');
let raw = await Deno.readAll(Deno.stdin);
let data = decoder.decode(raw);
let text = new TextContent(data.toString());
if (text.search(Deno.args[0])) {
let matches = text.match(Deno.args[0]);
let output = matches.replace((new RegExp(Deno.args[0], 'g')), '\x1b[100m' + Deno.args[0] + '\x1b[49m');
console.log(`${output}`);
}
})();
}
Com isto nós podemos verificar que a implementação contou também com uma verificação se a entrada era do tipo "tty" para se comportar da maneira adequada. Ao executar este código você vai encontrar o necessário para saber o que fazer após. E esta é a nossa finalização do programa, e podemos melhorar muito mais este programa.
Neste momento podemos realizar que nossas duas versões estão maduras o suficiente para receber modificações e também para vermos que elas tem algo em comum que tem uma importancia muito grande. A forma com que lidamos com o texto fazendo buscas e combinações de igualdade para obter um resultado tem similaridades e é onde podemos visualizar que é onde o Javascript teve importancia na construção.
Creio que com isto podemos considerar que o conhecimento aqui apresentado pode ter um reflexo positivo se incorporado da forma adequada em futuros projetos, onde outras implementações surgiram e um novo se fara materializado. Aproveite para práticar e construir coisas novas. Um bom exercicio que fica para o futuro é construir uma aplicação que tira proveito de conexões em rede, e o utiliza diversos tipos de protocolos, isto pode entregar uma visão mais ampla e minuciosa.
Agradeço a leitura e fique antenado para mais conteúdo.