144 lines
3.1 KiB
Plaintext
144 lines
3.1 KiB
Plaintext
|
{
|
||
|
function makeCommand(type, filters, parts) {
|
||
|
|
||
|
const validParts = parts || [];
|
||
|
// Extract pure description (without attributes)
|
||
|
const description = validParts
|
||
|
.filter(part => typeof part === 'string')
|
||
|
.join(' ')
|
||
|
.trim();
|
||
|
|
||
|
// Collect all attributes
|
||
|
const attributes = validParts
|
||
|
.filter(part => typeof part === 'object')
|
||
|
.filter(part => part !== null);
|
||
|
|
||
|
// Store original parts for reconstruction
|
||
|
const originalParts = validParts.filter(part => part !== null);
|
||
|
|
||
|
|
||
|
return {
|
||
|
type: type,
|
||
|
filters: filters.flat() || [],
|
||
|
description: description || [],
|
||
|
attributes: attributes || [],
|
||
|
parts: parts || [],
|
||
|
reconstruct: function() {
|
||
|
const filterStr = this.filters.map(f => f.reconstruct()).join(',');
|
||
|
const partsStr = this.parts.map(p => p.reconstruct()).join(' ');
|
||
|
return [filterStr, this.type, partsStr].filter(Boolean).join(' ');
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Start = AddCommand / DoneCommand / ExplicitFilterCommand / ImplicitFilterCommand
|
||
|
|
||
|
// ADD COMMAND
|
||
|
AddCommand = "add" _ parts:(Part / _)+ EOF {
|
||
|
return makeCommand('add', null, parts.filter(p => p !== null));
|
||
|
}
|
||
|
|
||
|
// DONE COMMAND
|
||
|
DoneCommand = filters:Filters _ "done" EOF {
|
||
|
return makeCommand('done', filters, null);
|
||
|
}
|
||
|
|
||
|
ExplicitFilterCommand = filters:Filters _ "filter" _* moreFilters:Filters* EOF {
|
||
|
return makeCommand('filter', [...filters, ...moreFilters], null);
|
||
|
}
|
||
|
|
||
|
ImplicitFilterCommand = filters:Filters+ EOF {
|
||
|
return makeCommand('filter', filters, null);
|
||
|
}
|
||
|
|
||
|
Filters = first:Filter rest:(_ Filter)* {
|
||
|
return [first, ...rest.map(r => r[1])];
|
||
|
}
|
||
|
|
||
|
|
||
|
IdRange = start:Integer "-" end:Integer {
|
||
|
const ids = [];
|
||
|
for (let i = start; i <= end; i++) {
|
||
|
ids.push(i);
|
||
|
}
|
||
|
return ids;
|
||
|
}
|
||
|
|
||
|
SingleId = id:Integer {
|
||
|
return [id];
|
||
|
}
|
||
|
|
||
|
Filter = IdFilter / Tag / Project / Priority / Due
|
||
|
|
||
|
IdFilter = first:(IdRange / SingleId) rest:("," (IdRange / SingleId))* trailing:"," ? {
|
||
|
const ids = [first, ...rest.map(r => r[1])].flat();
|
||
|
return {
|
||
|
type: 'id',
|
||
|
ids: ids,
|
||
|
reconstruct: function() { return this.ids.join(','); }
|
||
|
};
|
||
|
}
|
||
|
|
||
|
Part = Attribute / TextPart
|
||
|
|
||
|
TextPart = chars:Word {
|
||
|
return {
|
||
|
type: "text",
|
||
|
value: chars,
|
||
|
reconstruct: function() { return this.value; }
|
||
|
};
|
||
|
}
|
||
|
|
||
|
Attribute = Due / Tag / Project / Priority
|
||
|
|
||
|
Due = "@" value:TimeValue {
|
||
|
return {
|
||
|
type: "due",
|
||
|
value: value,
|
||
|
reconstruct: function() { return `@${this.value}`; }
|
||
|
};
|
||
|
}
|
||
|
|
||
|
Project = ("pro:" / "project:" / "+") value:Word {
|
||
|
return {
|
||
|
type: "project",
|
||
|
value: value,
|
||
|
reconstruct: function() { return `+${this.value}`; }
|
||
|
};
|
||
|
}
|
||
|
|
||
|
Priority = "priority:" value:[HML] {
|
||
|
return {
|
||
|
type: "priority",
|
||
|
value: value,
|
||
|
reconstruct: function() { return `priority:${this.value}`; }
|
||
|
};
|
||
|
}
|
||
|
|
||
|
Tag = "#" value:Word {
|
||
|
return {
|
||
|
type: "tag",
|
||
|
value: value,
|
||
|
reconstruct: function() { return `#${this.value}`; }
|
||
|
};
|
||
|
}
|
||
|
|
||
|
Integer = digits:[0-9]+ {
|
||
|
return parseInt(digits.join(''), 10);
|
||
|
}
|
||
|
|
||
|
TimeValue = chars:[0-9:]+ ("am" / "pm")? {
|
||
|
return chars.join('') + (text() || '');
|
||
|
}
|
||
|
|
||
|
Word = chars:[a-zA-Z0-9_-]+ {
|
||
|
return chars.join('');
|
||
|
}
|
||
|
|
||
|
_ = [ \t]+ {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
EOF = !.
|