liquido
Version:
Conjunto de ferramentas para desenvolvimento de aplicações para a internet 100% em português
398 lines • 20.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Liquido = void 0;
const sistemaDeArquivos = __importStar(require("fs"));
const caminho = __importStar(require("path"));
const avaliador_sintatico_com_importacao_1 = require("@designliquido/delegua-node/avaliador-sintatico/avaliador-sintatico-com-importacao");
const construtos_1 = require("@designliquido/delegua/construtos");
const declaracoes_1 = require("@designliquido/delegua/declaracoes");
const estruturas_1 = require("@designliquido/delegua/interpretador/estruturas");
const informacao_variavel_ou_constante_1 = require("@designliquido/delegua/informacao-variavel-ou-constante");
const lexador_1 = require("@designliquido/delegua/lexador");
const importador_1 = require("@designliquido/delegua-node/importador");
const foles_1 = require("@designliquido/foles");
const infraestrutura_1 = require("./infraestrutura");
const formatadores_1 = require("./infraestrutura/formatadores");
const provedores_1 = require("./infraestrutura/provedores");
const roteador_1 = require("./infraestrutura/roteador");
const centro_configuracoes_1 = require("./infraestrutura/centro-configuracoes");
const auto_documentador_1 = require("./infraestrutura/auto-documentacao/auto-documentador");
const requisicao_1 = require("./infraestrutura/requisicao");
const interpretador_liquido_1 = require("./infraestrutura/interpretador-liquido");
/**
* O núcleo do framework.
*/
class Liquido {
constructor(diretorioBase) {
this.arquivosAbertos = {};
this.conteudoArquivosAbertos = {};
this.arquivosDelegua = [];
this.rotasDelegua = [];
this.diretorioDescobertos = [];
this.diretorioBase = diretorioBase;
this.diretorioEstatico = 'publico';
this.importador = new importador_1.Importador(new lexador_1.Lexador(), this.arquivosAbertos, this.conteudoArquivosAbertos, false);
this.avaliadorSintatico = new avaliador_sintatico_com_importacao_1.AvaliadorSintaticoComImportacao(this.importador);
this.avaliadorSintatico.tiposDeFerramentasExternas = {
liquido: {
lincones: 'módulo',
liquido: 'módulo',
requisicao: 'módulo',
resposta: 'módulo'
}
};
this.formatadorLmht = new formatadores_1.FormatadorLmht(this.diretorioBase);
this.interpretador = new interpretador_liquido_1.InterpretadorLiquido(this.importador, process.cwd(), false, console.log);
this.autoDocumentador = new auto_documentador_1.AutoDocumentador();
this.roteador = new roteador_1.Roteador(this.autoDocumentador);
this.provedorLincones = new provedores_1.ProvedorLincones();
this.foles = new foles_1.FolEs(false);
}
async iniciar() {
this.importarArquivoConfiguracao();
this.roteador.configurarArquivosEstaticos(this.diretorioEstatico);
this.roteador.iniciarMiddlewares();
await this.importarArquivosRotas();
this.roteador.iniciar();
if (this.provedorLincones.configurado) {
this.interpretador.pilhaEscoposExecucao.definirVariavel('lincones', await this.provedorLincones.resolver());
}
this.escreverEstilos();
}
/**
* Método de importação do arquivo `configuracao.delegua`.
* @returns void.
*/
importarArquivoConfiguracao() {
const caminhoConfigArquivo = this.resolverArquivoConfiguracao();
if (caminhoConfigArquivo.valor === false) {
console.info("Arquivo 'configuracao.delegua' não encontrado.");
return;
}
try {
const retornoImportador = this.importador.importar(caminhoConfigArquivo.caminho, -1);
const retornoAvaliadorSintatico = this.avaliadorSintatico.analisar(retornoImportador.retornoLexador, retornoImportador.hashArquivo);
if (retornoAvaliadorSintatico.erros.length > 0) {
let mensagemCompleta = "";
for (const erro of retornoAvaliadorSintatico.erros) {
mensagemCompleta += `[Linha ${erro.linha}] Erro no arquivo de configuração: ${erro.message}\n`;
}
throw new Error(mensagemCompleta);
}
this.centroConfiguracoes = new centro_configuracoes_1.CentroConfiguracoes(retornoAvaliadorSintatico.declaracoes);
for (const [chave, configuracao] of Object.entries(this.centroConfiguracoes)) {
switch (chave) {
case 'liquido':
const configuracaoTipada = configuracao;
configuracaoTipada.configurar({
autoDocumentador: this.autoDocumentador,
roteador: this.roteador,
provedorLincones: this.provedorLincones
});
break;
default:
throw new Error(`Chave de configuração desconhecida: ${chave}`);
}
}
}
catch (error) {
console.error(error);
}
}
/**
* Retorna o caminho do arquivo de configuração se existir.
* @param {string} caminhoTotal O caminho para o diretório a ser pesquisado.
* @returns Um objeto com duas propriedades: `caminho` e `valor`. Se o caminho foi
* encontrado, `valor` será `true`, e `caminho` terá o caminho completo
* do arquivo de configuração. Caso contrário, `valor` será `false`, e
* `caminho` será nulo.
*/
resolverArquivoConfiguracao(caminhoTotal = '') {
const diretorioBase = caminhoTotal === '' ? this.diretorioBase : caminhoTotal;
const listaDeArquivos = sistemaDeArquivos.readdirSync(diretorioBase);
for (const arquivo of listaDeArquivos) {
if (arquivo === 'configuracao.delegua') {
return { caminho: caminho.join(diretorioBase, arquivo), valor: true };
}
}
return { caminho: null, valor: false };
}
/**
* Método de descoberta de rotas. Recursivo.
* @param diretorio O diretório a ser pesquisado.
*/
descobrirRotas(diretorio) {
const listaDeRotas = sistemaDeArquivos.readdirSync(diretorio);
const diretorioDescobertos = [];
listaDeRotas.forEach((diretorioOuArquivo) => {
const caminhoAbsoluto = caminho.join(diretorio, diretorioOuArquivo);
if (caminhoAbsoluto.endsWith('.delegua')) {
this.arquivosDelegua.push(caminhoAbsoluto);
return;
}
if (sistemaDeArquivos.lstatSync(caminhoAbsoluto).isDirectory()) {
diretorioDescobertos.push(caminhoAbsoluto);
}
});
diretorioDescobertos.forEach((diretorioDescoberto) => {
this.descobrirRotas(diretorioDescoberto);
});
}
descobrirEstilos() {
try {
const listaDeEstilos = sistemaDeArquivos.readdirSync('./estilos');
const arquivosDescobertos = [];
listaDeEstilos.forEach((diretorioOuArquivo) => {
const caminhoAbsoluto = caminho.join('./estilos', diretorioOuArquivo);
if (caminhoAbsoluto.endsWith('.foles')) {
arquivosDescobertos.push(caminhoAbsoluto);
}
});
return arquivosDescobertos;
}
catch (erro) {
console.error(`Pulando descoberta de estilos. Causa: ${erro}.`);
return [];
}
}
escreverEstilos() {
const arquivosEstilos = this.descobrirEstilos();
if (!sistemaDeArquivos.existsSync(`./${this.diretorioEstatico}/css`)) {
sistemaDeArquivos.mkdirSync(`./${this.diretorioEstatico}/css`, { recursive: true });
}
for (const arquivo of arquivosEstilos) {
const teste = this.foles.converterParaCss(arquivo);
const arquivoDestino = caminho.join(process.cwd(), `./${this.diretorioEstatico}/css`, arquivo.replace('estilos', '').replace('.foles', '.css'));
sistemaDeArquivos.writeFile(arquivoDestino, teste, (erro) => {
if (erro) {
return console.log(erro);
}
console.log(`Salvo: ${arquivoDestino}`);
});
}
}
/**
* Ele pega um caminho de arquivo e retorna uma rota
* @param {string} caminhoArquivo O caminho do arquivo que está sendo lido
* @returns A rota resolvida.
*/
resolverCaminhoRota(caminhoArquivo) {
const partesArquivo = caminhoArquivo.split('rotas');
const rotaResolvida = partesArquivo[1]
.replace('inicial.delegua', '')
.replace('.delegua', '')
.replace(new RegExp(`\\${caminho.sep}`, 'g'), '/')
.replace(new RegExp(`/$`, 'g'), '')
.replace(new RegExp(`\\[(.+)\\]`, 'g'), ':$1');
return rotaResolvida;
}
async importarArquivosRotas() {
this.descobrirRotas(caminho.join(this.diretorioBase, 'rotas'));
for (const arquivo of this.arquivosDelegua) {
const retornoImportador = this.importador.importar(arquivo, -1);
const retornoAvaliadorSintatico = this.avaliadorSintatico.analisar(retornoImportador.retornoLexador, retornoImportador.hashArquivo);
if (retornoAvaliadorSintatico.erros.length > 0) {
for (const erro of retornoAvaliadorSintatico.erros) {
console.error(`[Linha ${erro.linha}] Erro na rota ${arquivo}: ${erro.message}`);
}
continue;
}
// Liquido espera declarações do tipo `Expressao`, contendo dentro
// um construto do tipo `Chamada`.
for (const declaracao of retornoAvaliadorSintatico.declaracoes) {
const expressao = declaracao.expressao;
const entidadeChamada = expressao.entidadeChamada;
const objeto = entidadeChamada.objeto;
if (objeto.simbolo.lexema.toLowerCase() === 'liquido') {
switch (entidadeChamada.nomeMetodo) {
case 'rotaGet':
case 'rotaPost':
case 'rotaPut':
case 'rotaDelete':
case 'rotaPatch':
case 'rotaOptions':
case 'rotaCopy':
case 'rotaHead':
case 'rotaLock':
case 'rotaUnlock':
case 'rotaPurge':
case 'rotaPropfind':
await this.adicionarRota(entidadeChamada.nomeMetodo, this.resolverCaminhoRota(arquivo), expressao.argumentos);
break;
default:
console.error(`Método ${entidadeChamada.nomeMetodo} não reconhecido.`);
break;
}
}
}
}
}
/**
* O Interpretador Delégua exige alguns parâmetros definidos antes de executar.
* Esse método define esses parâmetros na posição inicial da pilha de execução
* do Interpretador.
* @param requisicao O objeto de requisição do Express.
* @param nomeFuncao O nome da função a ser chamada pelo Interpretador.
* @param funcaoConstruto O conteúdo da função, declarada no arquivo `.delegua` correspondente.
*/
async prepararRequisicao(requisicao, nomeFuncao, funcaoConstruto) {
this.avaliadorSintatico.pilhaEscopos.definirInformacoesVariavel('liquido', new informacao_variavel_ou_constante_1.InformacaoVariavelOuConstante('liquido', 'módulo'));
this.avaliadorSintatico.pilhaEscopos.definirInformacoesVariavel('requisicao', new informacao_variavel_ou_constante_1.InformacaoVariavelOuConstante('requisicao', 'módulo'));
this.avaliadorSintatico.pilhaEscopos.definirInformacoesVariavel('resposta', new informacao_variavel_ou_constante_1.InformacaoVariavelOuConstante('resposta', 'módulo'));
const descritorClasseRequisicao = new requisicao_1.Requisicao(requisicao);
await descritorClasseRequisicao.chamar(this.interpretador, []);
const instanciaRequisicao = new estruturas_1.ObjetoDeleguaClasse(descritorClasseRequisicao);
instanciaRequisicao.definir({ lexema: 'corpo' }, requisicao.body);
instanciaRequisicao.definir({ lexema: 'parametros' }, requisicao.params);
instanciaRequisicao.definir({ lexema: 'parametrosPesquisa' }, requisicao.query);
instanciaRequisicao.definir({ lexema: 'parametrosCaminho' }, requisicao.path);
this.interpretador.pilhaEscoposExecucao.definirVariavel('requisicao', instanciaRequisicao);
const descritorClasseResposta = new infraestrutura_1.Resposta();
await descritorClasseResposta.chamar(this.interpretador, []);
this.interpretador.pilhaEscoposExecucao.definirVariavel('resposta', new estruturas_1.ObjetoDeleguaClasse(descritorClasseResposta));
const funcaoRetorno = new estruturas_1.DeleguaFuncao(nomeFuncao, funcaoConstruto);
this.interpretador.pilhaEscoposExecucao.definirVariavel(nomeFuncao, funcaoRetorno);
}
/**
* Chamada ao Interpretador Delégua com a estrutura declarativa para a
* execução da função nomeada na rota.
* @param nomeFuncao O nome da função da rota.
* @returns O resultado da interpretação.
*/
async chamarInterpretador(nomeFuncao) {
try {
return await this.interpretador.interpretar([
new declaracoes_1.Expressao(new construtos_1.Chamada(-1, new construtos_1.Variavel(-1, new lexador_1.Simbolo('IDENTIFICADOR', nomeFuncao, null, -1, -1)), [
new construtos_1.Variavel(-1, new lexador_1.Simbolo('IDENTIFICADOR', 'requisicao', null, -1, -1)),
new construtos_1.Variavel(-1, new lexador_1.Simbolo('IDENTIFICADOR', 'resposta', null, -1, -1))
]))
], true);
}
catch (erro) {
console.error(erro);
}
}
logicaComumErrosInterpretacao(retornoInterpretador) {
let corpoRetorno = '';
for (const erro of retornoInterpretador.erros) {
if (erro.erroInterno) {
const erroInternoTipado = erro.erroInterno;
corpoRetorno += erroInternoTipado.message;
corpoRetorno += erroInternoTipado.stack;
}
else {
corpoRetorno += `[Linha ${erro.linha}]: ${erro.mensagem}`;
}
}
return { corpoRetorno: corpoRetorno, statusHttp: 500 };
}
async logicaComumResultadoInterpretador(caminhoRota, retornoInterpretador) {
if (retornoInterpretador.erros.length > 0) {
return this.logicaComumErrosInterpretacao(retornoInterpretador);
}
// O resultado que interessa é sempre o último.
// Ele antigamente vinha como string, e precisava ser desserializado para ser usado.
// Em versões mais recentes do interpretador, o retorno tem sido objetos inteiros.
// TODO: Mudar o tipo em Delégua para `resultado` trabalhar com qualquer tipo de objeto.
const representacaoObjeto = retornoInterpretador.resultado.pop();
const informacoesObjeto = JSON.parse(representacaoObjeto);
const objetoResposta = informacoesObjeto.hasOwnProperty('valor') ?
informacoesObjeto.valor :
informacoesObjeto;
let statusHttp = 200;
if (objetoResposta.propriedades.statusHttp) {
statusHttp = objetoResposta.propriedades.statusHttp;
}
if (objetoResposta.propriedades.destino) {
// Redirecionamento
return { redirecionamento: objetoResposta.propriedades.destino };
}
if (objetoResposta.propriedades.lmht) {
try {
let visao = caminhoRota;
// Verifica se foi definida uma preferência de visão.
// Se não foi, usa o sufixo da rota como visão correspondente.
// Por exemplo, `/rotas/inicial.delegua` tem como visão correspondente `/visoes/inicial.lmht`.
if (objetoResposta.propriedades.visao) {
const partesRota = caminhoRota.split('/');
partesRota.pop();
visao = partesRota.join('/') + '/' + objetoResposta.propriedades.visao;
}
const resultadoFormatacaoLmht = await this.formatadorLmht.formatar(visao, objetoResposta.propriedades.valores);
return { corpoRetorno: resultadoFormatacaoLmht, statusHttp: statusHttp };
}
catch (erro) {
console.error(`Erro ao processar LMHT: ${erro}.`);
}
}
if (objetoResposta.propriedades.respostaJson) {
// TODO: Por que valor é sempre um array aqui?
const valor = objetoResposta.propriedades.respostaJson.hasOwnProperty('valor')
? objetoResposta.propriedades.respostaJson.valor[0]
: objetoResposta.propriedades.respostaJson;
return { corpoRetorno: valor, statusHttp: statusHttp };
}
if (objetoResposta.propriedades.mensagem) {
return { corpoRetorno: objetoResposta.propriedades.mensagem, statusHttp: statusHttp };
}
}
/**
* Configuração de uma rota no roteador Express.
* @param metodoRoteador O método da rota.
* @param caminhoRota O caminho completo do arquivo que define a rota.
* @param argumentos Todas as funções em Delégua que devem ser executadas
* para a resolução da rota. Por enquanto apenas a primeira
* função é executada.
*/
async adicionarRota(metodoRoteador, caminhoRota, argumentos) {
// TODO: Melhorar isso para permitir N intermediários na execução da rota.
const funcao = argumentos[0];
const metodoResolvido = roteador_1.MetodoRoteador[metodoRoteador.replace('rota', '')];
this.roteador.mapaRotas[metodoResolvido](caminhoRota, async (req, res) => {
await this.prepararRequisicao(req, `funcaoRota${metodoRoteador}`, funcao);
const retornoInterpretador = await this.chamarInterpretador(`funcaoRota${metodoRoteador}`);
const corpoEStatus = await this.logicaComumResultadoInterpretador(caminhoRota, retornoInterpretador);
if (corpoEStatus.redirecionamento) {
res.redirect(corpoEStatus.redirecionamento);
}
else {
res.send(corpoEStatus.corpoRetorno).status(corpoEStatus.statusHttp);
}
});
}
}
exports.Liquido = Liquido;
//# sourceMappingURL=liquido.js.map