diff --git a/spec-es6/attribute_parser.spec.js b/spec-es6/attribute_parser.spec.js index 62c95c3b0..d1ef19096 100644 --- a/spec-es6/attribute_parser.spec.js +++ b/spec-es6/attribute_parser.spec.js @@ -1,51 +1,56 @@ import attributeParser from '../src/public/app/services/attribute_parser.js'; import {describe, it, expect, execute} from './mini_test.js'; -describe("Lexer", () => { +describe("Lexing", () => { it("simple label", () => { - expect(attributeParser.lexer("#label").map(t => t.text)) + expect(attributeParser.lex("#label").map(t => t.text)) + .toEqual(["#label"]); + }); + + it("simple label with trailing spaces", () => { + expect(attributeParser.lex(" #label ").map(t => t.text)) .toEqual(["#label"]); }); it("inherited label", () => { - expect(attributeParser.lexer("#label(inheritable)").map(t => t.text)) + expect(attributeParser.lex("#label(inheritable)").map(t => t.text)) .toEqual(["#label", "(", "inheritable", ")"]); - expect(attributeParser.lexer("#label ( inheritable ) ").map(t => t.text)) + expect(attributeParser.lex("#label ( inheritable ) ").map(t => t.text)) .toEqual(["#label", "(", "inheritable", ")"]); }); it("label with value", () => { - expect(attributeParser.lexer("#label=Hallo").map(t => t.text)) + expect(attributeParser.lex("#label=Hallo").map(t => t.text)) .toEqual(["#label", "=", "Hallo"]); }); it("label with value", () => { - const tokens = attributeParser.lexer("#label=Hallo"); + const tokens = attributeParser.lex("#label=Hallo"); expect(tokens[0].startIndex).toEqual(0); expect(tokens[0].endIndex).toEqual(5); }); it("relation with value", () => { - expect(attributeParser.lexer('~relation=#root/RclIpMauTOKS/NFi2gL4xtPxM').map(t => t.text)) + expect(attributeParser.lex('~relation=#root/RclIpMauTOKS/NFi2gL4xtPxM').map(t => t.text)) .toEqual(["~relation", "=", "#root/RclIpMauTOKS/NFi2gL4xtPxM"]); }); it("use quotes to define value", () => { - expect(attributeParser.lexer("#'label a'='hello\"` world'").map(t => t.text)) + expect(attributeParser.lex("#'label a'='hello\"` world'").map(t => t.text)) .toEqual(["#label a", "=", 'hello"` world']); - expect(attributeParser.lexer('#"label a" = "hello\'` world"').map(t => t.text)) + expect(attributeParser.lex('#"label a" = "hello\'` world"').map(t => t.text)) .toEqual(["#label a", "=", "hello'` world"]); - expect(attributeParser.lexer('#`label a` = `hello\'" world`').map(t => t.text)) + expect(attributeParser.lex('#`label a` = `hello\'" world`').map(t => t.text)) .toEqual(["#label a", "=", "hello'\" world"]); }); }); describe("Parser", () => { it("simple label", () => { - const attrs = attributeParser.parser(["#token"].map(t => ({text: t}))); + const attrs = attributeParser.parse(["#token"].map(t => ({text: t}))); expect(attrs.length).toEqual(1); expect(attrs[0].type).toEqual('label'); @@ -55,7 +60,7 @@ describe("Parser", () => { }); it("inherited label", () => { - const attrs = attributeParser.parser(["#token", "(", "inheritable", ")"].map(t => ({text: t}))); + const attrs = attributeParser.parse(["#token", "(", "inheritable", ")"].map(t => ({text: t}))); expect(attrs.length).toEqual(1); expect(attrs[0].type).toEqual('label'); @@ -65,7 +70,7 @@ describe("Parser", () => { }); it("label with value", () => { - const attrs = attributeParser.parser(["#token", "=", "val"].map(t => ({text: t}))); + const attrs = attributeParser.parse(["#token", "=", "val"].map(t => ({text: t}))); expect(attrs.length).toEqual(1); expect(attrs[0].type).toEqual('label'); @@ -74,14 +79,14 @@ describe("Parser", () => { }); it("relation", () => { - let attrs = attributeParser.parser(["~token", "=", "#root/RclIpMauTOKS/NFi2gL4xtPxM"].map(t => ({text: t}))); + let attrs = attributeParser.parse(["~token", "=", "#root/RclIpMauTOKS/NFi2gL4xtPxM"].map(t => ({text: t}))); expect(attrs.length).toEqual(1); expect(attrs[0].type).toEqual('relation'); expect(attrs[0].name).toEqual("token"); expect(attrs[0].value).toEqual('NFi2gL4xtPxM'); - attrs = attributeParser.parser(["~token", "=", "#NFi2gL4xtPxM"].map(t => ({text: t}))); + attrs = attributeParser.parse(["~token", "=", "#NFi2gL4xtPxM"].map(t => ({text: t}))); expect(attrs.length).toEqual(1); expect(attrs[0].type).toEqual('relation'); @@ -97,6 +102,9 @@ describe("error cases", () => { expect(() => attributeParser.lexAndParse("#a&b/s")) .toThrow(`Attribute name "a&b/s" contains disallowed characters, only alphanumeric characters, colon and underscore are allowed.`); + + expect(() => attributeParser.lexAndParse("#")) + .toThrow(`Attribute name is empty, please fill the name.`); }); }); diff --git a/src/public/app/services/attribute_parser.js b/src/public/app/services/attribute_parser.js index 02eb8a63b..f214fcfc8 100644 --- a/src/public/app/services/attribute_parser.js +++ b/src/public/app/services/attribute_parser.js @@ -1,4 +1,6 @@ -function lexer(str) { +function lex(str) { + str = str.trim(); + const tokens = []; let quotes = false; @@ -106,12 +108,16 @@ function lexer(str) { const attrNameMatcher = new RegExp("^[\\p{L}\\p{N}_:]+$", "u"); function checkAttributeName(attrName) { + if (attrName.length === 0) { + throw new Error("Attribute name is empty, please fill the name."); + } + if (!attrNameMatcher.test(attrName)) { throw new Error(`Attribute name "${attrName}" contains disallowed characters, only alphanumeric characters, colon and underscore are allowed.`); } } -function parser(tokens, str, allowEmptyRelations = false) { +function parse(tokens, str, allowEmptyRelations = false) { const attrs = []; function context(i) { @@ -213,13 +219,13 @@ function parser(tokens, str, allowEmptyRelations = false) { } function lexAndParse(str, allowEmptyRelations = false) { - const tokens = lexer(str); + const tokens = lex(str); - return parser(tokens, str, allowEmptyRelations); + return parse(tokens, str, allowEmptyRelations); } export default { - lexer, - parser, + lex, + parse, lexAndParse } diff --git a/src/public/app/services/attribute_renderer.js b/src/public/app/services/attribute_renderer.js index d52777d6d..462eaa07d 100644 --- a/src/public/app/services/attribute_renderer.js +++ b/src/public/app/services/attribute_renderer.js @@ -11,7 +11,7 @@ function renderAttribute(attribute, $container, renderIsInheritable) { $container.append(document.createTextNode(formatValue(attribute.value))); } - $container.append(' '); + $container.append(" "); } else if (attribute.type === 'relation') { if (attribute.isAutoLink) { return; @@ -20,7 +20,7 @@ function renderAttribute(attribute, $container, renderIsInheritable) { if (attribute.value) { $container.append(document.createTextNode('~' + attribute.name + isInheritable + "=")); $container.append(createNoteLink(attribute.value)); - $container.append(" "); + $container.append(" "); } else { ws.logError(`Relation ${attribute.attributeId} has empty target`); } diff --git a/src/public/app/services/glob.js b/src/public/app/services/glob.js index 9e4ee89a2..6692bf6ff 100644 --- a/src/public/app/services/glob.js +++ b/src/public/app/services/glob.js @@ -34,12 +34,12 @@ function setupGlobs() {
@abc
- returns notes with label abc@year=2019
- matches notes with label year
having value 2019
@rock @pop
- matches notes which have both rock
and pop
labels@rock or @pop
- only one of the labels must be present@year<=2000
- numerical comparison (also >, >=, <).@dateCreated>=MONTH-1
- notes created in the last month#abc
- returns notes with label abc#year = 2019
- matches notes with label year
having value 2019
#rock #pop
- matches notes which have both rock
and pop
labels#rock or #pop
- only one of the labels must be present#year <= 2000
- numerical comparison (also >, >=, <).note.dateCreated >= MONTH-1
- notes created in the last month=handler
- will execute script defined in handler
relation to get resultsTo add label, just type e.g. #rock
or if you want to add also value then e.g. #year = 2020
For relation, type ~author = @
which should bring up an autocomplete where you can look up the desired note.
Alternatively you can add label and relation using the +
button on the right side.