UNPKG

liquido

Version:

Conjunto de ferramentas para desenvolvimento de aplicações para a internet 100% em português

398 lines 20.3 kB
"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