From ed9fbae65d440ab2d9b42a37a5c527d3d2ee8d25 Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 4 Jun 2020 00:04:57 +0200 Subject: [PATCH] attribute parser and tests WIP --- package.json | 3 +- spec-es6/attribute_parser.spec.js | 54 ++++++++++++++++- spec-es6/mini_test.js | 30 ++++++++++ src/public/app/services/attribute_parser.js | 59 ++++++++++++++++++- src/services/note_cache/note_cache_service.js | 1 + 5 files changed, 142 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8ba5f3983..a955eebfe 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "build-frontend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/collapsible_widget.js", "build-docs": "npm run build-backend-docs && npm run build-frontend-docs", "webpack": "npx webpack -c webpack-desktop.config.js && npx webpack -c webpack-mobile.config.js && npx webpack -c webpack-setup.config.js", - "test": "jasmine" + "test": "jasmine", + "test-es6": "node -r esm spec-es6/attribute_parser.spec.js " }, "dependencies": { "async-mutex": "0.2.2", diff --git a/spec-es6/attribute_parser.spec.js b/spec-es6/attribute_parser.spec.js index a69118589..5c16d513a 100644 --- a/spec-es6/attribute_parser.spec.js +++ b/spec-es6/attribute_parser.spec.js @@ -1,10 +1,62 @@ import attributeParser from '../src/public/app/services/attribute_parser.js'; import {describe, it, expect, execute} from './mini_test.js'; -describe("Lexer fulltext", () => { +describe("Lexer", () => { it("simple label", () => { expect(attributeParser.lexer("#label")).toEqual(["#label"]); }); + + it("label with value", () => { + expect(attributeParser.lexer("#label=Hallo")).toEqual(["#label", "=", "Hallo"]); + }); + + it("relation with value", () => { + expect(attributeParser.lexer('~relation=note')).toEqual(["~relation", "=", "#root/RclIpMauTOKS/NFi2gL4xtPxM"]); + }); + + it("use quotes to define value", () => { + expect(attributeParser.lexer("#'label a'='hello\"` world'")) + .toEqual(["#label a", "=", 'hello"` world']); + + expect(attributeParser.lexer('#"label a" = "hello\'` world"')) + .toEqual(["#label a", "=", "hello'` world"]); + + expect(attributeParser.lexer('#`label a` = `hello\'" world`')) + .toEqual(["#label a", "=", "hello'\" world"]); + }); +}); + +describe("Parser", () => { + it("simple label", () => { + const attrs = attributeParser.parser(["#token"]); + + expect(attrs.length).toEqual(1); + expect(attrs[0].type).toEqual('label'); + expect(attrs[0].name).toEqual('token'); + expect(attrs[0].value).toBeFalsy(); + }); + + it("label with value", () => { + const attrs = attributeParser.parser(["#token", "=", "val"]); + + expect(attrs.length).toEqual(1); + expect(attrs[0].type).toEqual('label'); + expect(attrs[0].name).toEqual('token'); + expect(attrs[0].value).toEqual("val"); + }); + + it("relation", () => { + const attrs = attributeParser.parser(["~token", "=", "#root/RclIpMauTOKS/NFi2gL4xtPxM"]); + + expect(attrs.length).toEqual(1); + expect(attrs[0].type).toEqual('relation'); + expect(attrs[0].name).toEqual("token"); + expect(attrs[0].value).toEqual('#root/RclIpMauTOKS/NFi2gL4xtPxM'); + }); + + it("error cases", () => { + expect(() => attributeParser.parser(["~token"])).toThrow('Relation "~token" should point to a note.'); + }); }); execute(); diff --git a/spec-es6/mini_test.js b/spec-es6/mini_test.js index 58b743115..525cdc50b 100644 --- a/spec-es6/mini_test.js +++ b/spec-es6/mini_test.js @@ -25,6 +25,36 @@ export function expect(val) { errorCount++; } + }, + toBeFalsy: () => { + if (!!val) { + console.trace("toBeFalsy failed."); + console.error(`expected: null, false, undefined, 0 or empty string`); + console.error(`got: ${val}`); + + errorCount++; + } + }, + toThrow: errorMessage => { + try { + val(); + } + catch (e) { + if (e.message !== errorMessage) { + console.trace("toThrow caught exception, but messages differ"); + console.error(`expected: ${errorMessage}`); + console.error(`got: ${e.message}`); + + errorCount++; + } + + return; + } + + console.trace("toThrow did not catch any exception."); + console.error(`expected: ${errorMessage}`); + console.error(`got: [none]`); + errorCount++; } } } diff --git a/src/public/app/services/attribute_parser.js b/src/public/app/services/attribute_parser.js index dd99b0eb0..3b5aa4c29 100644 --- a/src/public/app/services/attribute_parser.js +++ b/src/public/app/services/attribute_parser.js @@ -1,5 +1,9 @@ +function preprocessRelations(str) { + return str.replace(/]+href="(#root[A-Za-z0-9/]*)"[^>]*>[^<]*<\/a>/g, "$1"); +} + function lexer(str) { - str = str.toLowerCase(); + str = preprocessRelations(str); const expressionTokens = []; @@ -95,6 +99,55 @@ function lexer(str) { return expressionTokens; } -export default { - lexer +function parser(tokens) { + const attrs = []; + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + + if (token.startsWith('#')) { + const attr = { + type: 'label', + name: token.substr(1) + }; + + if (tokens[i + 1] === "=") { + if (i + 2 >= tokens.length) { + throw new Error(`Missing value for label "${token}"`); + } + + i += 2; + + attr.value = tokens[i]; + } + + attrs.push(attr); + } + else if (token.startsWith('~')) { + const attr = { + type: 'relation', + name: token.substr(1) + }; + + if (i + 2 >= tokens.length || tokens[i + 1] !== '=') { + throw new Error(`Relation "${token}" should point to a note.`); + } + + i += 2; + + attr.value = tokens[i]; + + attrs.push(attr); + } + else { + throw new Error(`Unrecognized attribute "${token}"`); + } + } + + return attrs; +} + +export default { + lexer, + parser } diff --git a/src/services/note_cache/note_cache_service.js b/src/services/note_cache/note_cache_service.js index 8007c9cb3..972683a40 100644 --- a/src/services/note_cache/note_cache_service.js +++ b/src/services/note_cache/note_cache_service.js @@ -2,6 +2,7 @@ const noteCache = require('./note_cache'); const hoistedNoteService = require('../hoisted_note'); +const protectedSessionService = require('../protected_session'); const stringSimilarity = require('string-similarity'); function isNotePathArchived(notePath) {