From d3a3aa173f677df26f0ac9dcebfe94d514018a76 Mon Sep 17 00:00:00 2001 From: Wendy Hemlock <102826495+PvtWendy@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:09:48 -0400 Subject: [PATCH] =?UTF-8?q?Cria=C3=A7=C3=A3o=20da=20biblioteca=20Arquivos?= =?UTF-8?q?=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fontes/bibliotecas/arquivos.ts | 197 +++++++++++++++++++++++++++ testes/bibliotecas/arquivos.test.ts | 200 ++++++++++++++++++++++++++++ 2 files changed, 397 insertions(+) create mode 100644 fontes/bibliotecas/arquivos.ts create mode 100644 testes/bibliotecas/arquivos.test.ts diff --git a/fontes/bibliotecas/arquivos.ts b/fontes/bibliotecas/arquivos.ts new file mode 100644 index 0000000..deb9e55 --- /dev/null +++ b/fontes/bibliotecas/arquivos.ts @@ -0,0 +1,197 @@ +import { InterpretadorInterface } from '@designliquido/delegua/interfaces'; +import fs from 'fs'; +import path from 'path'; + +const NUMERO_MAXIMO_ARQUIVOS = 10; + +const ModoAcesso = { + LEITURA: 0, + ESCRITA: 1, + ACRESCENTAR: 2, +}; + +const arquivos = new Array(NUMERO_MAXIMO_ARQUIVOS).fill(null); + +export async function abrir_arquivo(interpretador: InterpretadorInterface, caminhoArquivo, modoAcesso) { + if (modoAcesso < 0 || modoAcesso > 2) { + throw new Error(`Modo de acesso inválido: ${modoAcesso}`); + } + + if (!arquivoAberto(caminhoArquivo)) { + const indice = obterProximoIndiceLivre(); + arquivos[indice] = { + caminho: caminhoArquivo, + modoAcesso, + stream: + modoAcesso === ModoAcesso.LEITURA + ? fs.createReadStream(caminhoArquivo, 'utf-8') + : fs.createWriteStream(caminhoArquivo, { + flags: modoAcesso === ModoAcesso.ACRESCENTAR ? 'a' : 'w', + encoding: 'utf-8', + }), + leitor: modoAcesso === ModoAcesso.LEITURA ? fs.promises.readFile(caminhoArquivo, 'utf-8') : null, + escritor: + modoAcesso !== ModoAcesso.LEITURA + ? fs.promises.writeFile(caminhoArquivo, '', { + flag: modoAcesso === ModoAcesso.ACRESCENTAR ? 'a' : 'w', + }) + : null, + fim: false, + }; + return indice; + } else { + throw new Error(`O arquivo '${caminhoArquivo}' já está aberto`); + } +} + +export async function fechar_arquivo(interpretador: InterpretadorInterface, endereco) { + const arquivo = obterArquivo(endereco); + if (arquivo) { + arquivo.stream.close(); + arquivos[endereco] = null; + } else { + throw new Error(`O endereço de memória especificado não aponta para um arquivo`); + } +} + +export function fim_arquivo(interpretador: InterpretadorInterface, endereco) { + const arquivo = obterArquivo(endereco); + return arquivo.fim; +} + +export async function ler_linha(interpretador: InterpretadorInterface, endereco) { + const arquivo = obterArquivo(endereco); + if (arquivo.modoAcesso !== ModoAcesso.LEITURA) { + throw new Error(`O arquivo '${arquivo.caminho}' está aberto em modo de escrita`); + } + const data = await arquivo.leitor; + return data.split('\n')[0]; +} + +export async function escrever_linha(interpretador: InterpretadorInterface, linha, endereco) { + const arquivo = obterArquivo(endereco); + if (arquivo.modoAcesso === ModoAcesso.LEITURA) { + throw new Error(`O arquivo '${arquivo.caminho}' está aberto em modo de leitura`); + } + await fs.promises.appendFile(arquivo.caminho, linha + '\n', 'utf-8'); +} + +export async function substituir_texto( + interpretador: InterpretadorInterface, + endereco, + textoPesquisa, + textoSubstituto, + onlyFirst +) { + const filePath = path.resolve(endereco); + const data = await fs.promises.readFile(filePath, 'utf-8'); + const newText = onlyFirst + ? data.replace(textoPesquisa, textoSubstituto) + : data.replace(new RegExp(textoPesquisa, 'g'), textoSubstituto); + await fs.promises.writeFile(filePath, newText, 'utf-8'); +} + +export async function arquivo_existe(interpretador: InterpretadorInterface, caminhoArquivo) { + const filePath = path.resolve(caminhoArquivo); + return fs.promises + .access(filePath, fs.constants.F_OK) + .then(() => true) + .catch(() => false); +} + +export async function apagar_arquivo(interpretador: InterpretadorInterface, caminhoArquivo) { + const filePath = path.resolve(caminhoArquivo); + await fs.promises.unlink(filePath); +} + +export async function criar_pasta(interpretador: InterpretadorInterface, caminho) { + const dirPath = path.resolve(caminho); + await fs.promises.mkdir(dirPath, { recursive: true }); +} + +export async function listar_pastas(interpretador: InterpretadorInterface, caminhoPai, vetorPastas) { + const dirPath = path.resolve(caminhoPai); + const items = await fs.promises.readdir(dirPath, { withFileTypes: true }); + const pastas = items.filter((item) => item.isDirectory()).map((item) => item.name); + + if (pastas.length > vetorPastas.length) { + throw new Error( + `Não foi possível listar as pastas pois o vetor passado é muito pequeno. O diretório escolhido possui ${pastas.length} pastas, mas o vetor passado comporta apenas ${vetorPastas.length} elementos.` + ); + } + + pastas.forEach((pasta, i) => { + vetorPastas[i] = pasta; + }); + + for (let i = pastas.length; i < vetorPastas.length; i++) { + vetorPastas[i] = ''; + } +} + +export async function listar_arquivos(interpretador: InterpretadorInterface, caminhoPai, vetorArquivos) { + const dirPath = path.resolve(caminhoPai); + const items = await fs.promises.readdir(dirPath, { withFileTypes: true }); + const arquivos = items.filter((item) => item.isFile()).map((item) => item.name); + + if (arquivos.length > vetorArquivos.length) { + throw new Error( + `Não foi possível listar os arquivos pois o vetor passado é muito pequeno. O diretório escolhido possui ${arquivos.length} arquivos, mas o vetor passado comporta apenas ${vetorArquivos.length} elementos.` + ); + } + + arquivos.forEach((arquivo, i) => { + vetorArquivos[i] = arquivo; + }); + + for (let i = arquivos.length; i < vetorArquivos.length; i++) { + vetorArquivos[i] = ''; + } +} + +export async function listar_arquivos_por_tipo( + interpretador: InterpretadorInterface, + caminhoPai, + vetorArquivos, + vetorTipos +) { + const dirPath = path.resolve(caminhoPai); + const items = await fs.promises.readdir(dirPath, { withFileTypes: true }); + const arquivos = items + .filter((item) => item.isFile() && vetorTipos.some((tipo) => item.name.endsWith(tipo))) + .map((item) => item.name); + + if (arquivos.length > vetorArquivos.length) { + throw new Error( + `Não foi possível listar os arquivos pois o vetor passado é muito pequeno. O diretório escolhido possui ${arquivos.length} arquivos, mas o vetor passado comporta apenas ${vetorArquivos.length} elementos.` + ); + } + + arquivos.forEach((arquivo, i) => { + vetorArquivos[i] = arquivo; + }); + + for (let i = arquivos.length; i < vetorArquivos.length; i++) { + vetorArquivos[i] = ''; + } +} + +function obterProximoIndiceLivre() { + const indice = arquivos.findIndex((arquivo) => arquivo === null); + if (indice === -1) { + throw new Error('O número máximo de arquivos que podem ser abertos ao mesmo tempo foi atingido'); + } + return indice; +} + +function obterArquivo(endereco) { + const arquivo = arquivos[endereco]; + if (!arquivo) { + throw new Error('O endereço de memória especificado não aponta para um arquivo'); + } + return arquivo; +} + +function arquivoAberto(caminho) { + return arquivos.some((arquivo) => arquivo && arquivo.caminho === caminho); +} diff --git a/testes/bibliotecas/arquivos.test.ts b/testes/bibliotecas/arquivos.test.ts new file mode 100644 index 0000000..b37207d --- /dev/null +++ b/testes/bibliotecas/arquivos.test.ts @@ -0,0 +1,200 @@ +import { InterpretadorInterface } from '@designliquido/delegua/interfaces'; +import fs from 'fs'; +import { + abrir_arquivo, + fechar_arquivo, + fim_arquivo, + ler_linha, + escrever_linha, + substituir_texto, + arquivo_existe, + apagar_arquivo, + criar_pasta, + listar_pastas, + listar_arquivos, + listar_arquivos_por_tipo, +} from '../../fontes/bibliotecas/arquivos'; + +describe('Biblioteca Arquivos', () => { + beforeEach(async () => { + await fs.promises.mkdir('testDir', { recursive: true }); + await fs.promises.writeFile('testDir/test.txt', 'Linha 1\nLinha 2'); + }); + + afterEach(async () => { + try { + await fechar_arquivo({} as InterpretadorInterface, 0); + } catch (error) {} + try { + await fs.promises.rm('testDir'); + } catch (error) {} + }); + + afterAll(async () => { + try { + await fs.promises.unlink('testDir/test.txt'); + await fs.promises.rm('testDir', { recursive: true }); + } catch (error) {} + }); + + describe('Abrir Arquivo', () => { + it('Trivial', async () => { + const arquivoIndex = await abrir_arquivo({} as InterpretadorInterface, 'testDir/test.txt', 0); + expect(arquivoIndex).toBeGreaterThanOrEqual(0); + }); + + it('Modo de acesso invalido', async () => { + await expect(abrir_arquivo({} as InterpretadorInterface, 'testDir/test.txt', 3)).rejects.toThrow( + 'Modo de acesso inválido' + ); + }); + + it('Arquivo já está aberto', async () => { + await abrir_arquivo({} as InterpretadorInterface, 'testDir/test.txt', 0); + await expect(abrir_arquivo({} as InterpretadorInterface, 'testDir/test.txt', 0)).rejects.toThrow( + "O arquivo 'testDir/test.txt' já está aberto" + ); + }); + }); + + describe('Fechar Arquivo', () => { + it('Trivial', async () => { + const arquivoIndex = await abrir_arquivo({} as InterpretadorInterface, 'testDir/test.txt', 0); + await fechar_arquivo({} as InterpretadorInterface, arquivoIndex); + await expect(fs.promises.access('testDir/test.txt')).resolves.toBeUndefined(); + }); + + it('Endereço invalido', async () => { + await expect(fechar_arquivo({} as InterpretadorInterface, 99)).rejects.toThrow( + 'O endereço de memória especificado não aponta para um arquivo' + ); + }); + }); + + describe('Fim Arquivo', () => { + it('Trivial', async () => { + const arquivoIndex = await abrir_arquivo({} as InterpretadorInterface, 'testDir/test.txt', 2); + expect(fim_arquivo({} as InterpretadorInterface, arquivoIndex)).toBe(false); + }); + + it('Fim não alcançado', async () => { + const arquivoIndex = await abrir_arquivo({} as InterpretadorInterface, 'testDir/test.txt', 0); + await ler_linha({} as InterpretadorInterface, arquivoIndex); + expect(fim_arquivo({} as InterpretadorInterface, arquivoIndex)).toBe(false); + }); + }); + + describe('Ler Linha', () => { + it('Trivial', async () => { + const arquivoIndex = await abrir_arquivo({} as InterpretadorInterface, 'testDir/test.txt', 0); + const linha = await ler_linha({} as InterpretadorInterface, arquivoIndex); + expect(linha).toBe('Linha 1'); + }); + + it('Erro caso em modo de escrita', async () => { + const arquivoIndex = await abrir_arquivo({} as InterpretadorInterface, 'testDir/test.txt', 1); + await expect(ler_linha({} as InterpretadorInterface, arquivoIndex)).rejects.toThrow( + "O arquivo 'testDir/test.txt' está aberto em modo de escrita" + ); + }); + }); + + describe('Escrever Linha', () => { + it('Trivial', async () => { + const arquivoIndex = await abrir_arquivo({} as InterpretadorInterface, 'testDir/test.txt', 1); + await escrever_linha({} as InterpretadorInterface, 'Nova linha', arquivoIndex); + const data = await fs.promises.readFile('testDir/test.txt', 'utf-8'); + expect(data).toContain('Nova linha'); + }); + + it('Arquivo em modo leitura', async () => { + const arquivoIndex = await abrir_arquivo({} as InterpretadorInterface, 'testDir/test.txt', 0); + await expect(escrever_linha({} as InterpretadorInterface, 'Nova linha', arquivoIndex)).rejects.toThrow( + "O arquivo 'testDir/test.txt' está aberto em modo de leitura" + ); + }); + }); + describe('Substituir Texto', () => { + it('Trivial', async () => { + await substituir_texto({} as InterpretadorInterface, 'testDir/test.txt', 'Linha 1', 'Linha Alterada', true); + const data = await fs.promises.readFile('testDir/test.txt', 'utf-8'); + expect(data).toContain('Linha Alterada'); + expect(data).not.toContain('Linha 1'); + }); + + it('Varias instancias de um texto', async () => { + await substituir_texto({} as InterpretadorInterface, 'testDir/test.txt', 'Linha', 'Linha Alterada', false); + const data = await fs.promises.readFile('testDir/test.txt', 'utf-8'); + expect(data.match(/Linha Alterada/g)).toHaveLength(2); + }); + }); + + describe('Arquivo Existe', () => { + it('Verdadeiro caso exista', async () => { + expect(await arquivo_existe({} as InterpretadorInterface, 'testDir/test.txt')).toBe(true); + }); + + it('Falso caso não exista', async () => { + expect(await arquivo_existe({} as InterpretadorInterface, 'testDir/nonexistent.txt')).toBe(false); + }); + }); + + describe('Apagar Arquivo', () => { + it('Trivial', async () => { + await apagar_arquivo({} as InterpretadorInterface, 'testDir/test.txt'); + await expect(fs.promises.access('testDir/test.txt')).rejects.toThrow(); + }); + }); + + describe('Cria Pasta', () => { + it('Trivial', async () => { + await criar_pasta({} as InterpretadorInterface, 'testDir/subDir'); + const exists = await fs.promises + .access('testDir/subDir') + .then(() => true) + .catch(() => false); + expect(exists).toBe(true); + }); + }); + + describe('Listar Pastas', () => { + it('Trivial', async () => { + const vetorPastas = new Array(1); + await listar_pastas({} as InterpretadorInterface, 'testDir', vetorPastas); + expect(vetorPastas).toEqual(['subDir']); + }); + + it('Vetor muito pequeno', async () => { + const vetorPastas = new Array(0); + await expect(listar_pastas({} as InterpretadorInterface, 'testDir', vetorPastas)).rejects.toThrow(); + }); + }); + + describe('Listar Arquivos', () => { + it('Trivial', async () => { + const vetorArquivos = new Array(1); + await listar_arquivos({} as InterpretadorInterface, './testDir', vetorArquivos); + expect(vetorArquivos).toContain('test.txt'); + }); + + it('Vetor muito pequeno', async () => { + const vetorArquivos = new Array(0); + await expect(listar_arquivos({} as InterpretadorInterface, './testDir', vetorArquivos)).rejects.toThrow(); + }); + }); + + describe('Listar Arquivos Por Tipo', () => { + it('Trivial', async () => { + const vetorArquivos = new Array(1); + await listar_arquivos_por_tipo({} as InterpretadorInterface, './testDir', vetorArquivos, ['.txt']); + expect(vetorArquivos).toContain('test.txt'); + }); + + it('Vetor muito pequeno', async () => { + const vetorArquivos = new Array(0); + await expect( + listar_arquivos_por_tipo({} as InterpretadorInterface, './testDir', vetorArquivos, ['.txt']) + ).rejects.toThrow(); + }); + }); +});