mirror of
https://github.com/marty-oehme/scripts.git
synced 2024-12-22 07:58:08 +00:00
Merge pull request #5 from fmakowski/master
Headers parsing and project nesting
This commit is contained in:
commit
60ac1835b4
2 changed files with 133 additions and 27 deletions
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"name": "Taskwarrior import/export",
|
"name": "Taskwarrior",
|
||||||
"identifier": "taskwarrior",
|
"identifier": "taskwarrior",
|
||||||
"script": "taskwarrior.qml",
|
"script": "taskwarrior.qml",
|
||||||
"version": "0.0.2",
|
"version": "0.0.3",
|
||||||
"minAppVersion": "17.06.1",
|
"minAppVersion": "17.06.4",
|
||||||
"authors": ["@fmakowski"],
|
"authors": ["@fmakowski"],
|
||||||
"platforms": ["linux", "macos"],
|
"platforms": ["linux", "macos"],
|
||||||
"description" : "This script creates menu items and buttons to import and export Taskwarrior tasks.\n\n<b>Dependencies</b>\n\nUnix-like OSes only!\n<a href=\"http://taskwarrior.org\">Taskwarrior</a>\n\n<b>Usage</b>\n\n<i>Export:</i>\nThe script takes selected text from your note and parse it to create task entries based on it. The following rules currently apply for the test to be parsed correctly:\n * the project is defined by writing \"project:\" (case-insensitive) immediately followed by the project name; the rest of the line content is skipped\n * the task is defined by making a list item, using either - (minus) or * (asterisk) at the beginning; the task description taken will be used with the most recently detected project name to create a new task\n\n<i>Import:</i>\nThe script takes selected text from your note, parsing it into the project names you want to fetch from Taskwarrior into the note. The tasks will be written as a list right below the selection. The project names will be appended before the lists As \"Project: [projectName]\" to separate lists.<b>To Do</b>\n\n * foolproof used regexps\n * make project nesting easier\n * use headers to determine project name and nesting\n * Windows support\n * Taskwarrior parameter parsing (like tags, dates, priority, etc.)"
|
"description" : "This script creates menu items and buttons to import and export Taskwarrior tasks.\n\n<b>Dependencies</b>\n\nUnix-like OSes only!\n<a href=\"http://taskwarrior.org\">Taskwarrior</a>\n\n<b>Usage</b>\n\n<i>Export:</i>\nThe script takes selected text from your note and parse it to create task entries based on it. The following rules currently apply for the test to be parsed correctly:\n * the project is defined as the header. Subheaders are appended to hierarchically upper header name (giving the possibility of nesting) * the task is defined by making a list item, using either - (minus) or * (asterisk) at the beginning; the task description taken will be used with the most recently detected project name to create a new task\n\n<i>Import:</i>\nThe script takes selected text from your note, parsing it into the project names you want to fetch from Taskwarrior into the note. The tasks will be written as a list right below the selection. The project names will be created as headers to separate tasks, with appropriate nesting.<b>To Do</b>\n\n * foolproof regexps\n * Windows support\n * Taskwarrior parameter parsing (like tags, dates, priority, etc.)"
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,34 @@ import QOwnNotesTypes 1.0
|
||||||
* importing tasks from a certain project, or exporting them from a note.
|
* importing tasks from a certain project, or exporting them from a note.
|
||||||
*/
|
*/
|
||||||
QtObject {
|
QtObject {
|
||||||
|
property string taskPath;
|
||||||
|
property bool verbose;
|
||||||
|
property bool deleteOnImport;
|
||||||
|
|
||||||
|
property variant settingsVariables: [
|
||||||
|
{
|
||||||
|
"identifier": "taskPath",
|
||||||
|
"name": "Taskwarrior path",
|
||||||
|
"description": "A path to your Taskwarrior instance",
|
||||||
|
"type": "string",
|
||||||
|
"default": "/usr/bin/task",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "verbose",
|
||||||
|
"name": "Verbose logging",
|
||||||
|
"description": "Should the script log every action?",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "deleteOnImport",
|
||||||
|
"name": "Delete on import",
|
||||||
|
"description": "Delete tasks on import?",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the custom actions
|
* Initializes the custom actions
|
||||||
*/
|
*/
|
||||||
|
@ -45,14 +73,22 @@ QtObject {
|
||||||
function getProjectNameAndRun(str, func) {
|
function getProjectNameAndRun(str, func) {
|
||||||
// We are trying to get the name of the project.
|
// We are trying to get the name of the project.
|
||||||
// To do so, we are getting the substring of a line by using regexp group.
|
// To do so, we are getting the substring of a line by using regexp group.
|
||||||
var projectRegExp = /project:[\s*]?(.+)?[\s*]?/i;
|
var projectRegExp = /(#+)[\s*]?(.+)?[\s*]?/i;
|
||||||
var isProjectName = projectRegExp.exec(str);
|
var isProjectName = projectRegExp.exec(str);
|
||||||
if (isProjectName) {
|
if (isProjectName) {
|
||||||
func(isProjectName[1]);
|
var projectName = isProjectName[2];
|
||||||
|
var headerLevel = isProjectName[1].length;
|
||||||
|
func(projectName, headerLevel);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function logIfVerbose(str) {
|
||||||
|
if (verbose) {
|
||||||
|
script.log(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is invoked when a custom action is triggered
|
* This function is invoked when a custom action is triggered
|
||||||
* in the menu or via button
|
* in the menu or via button
|
||||||
|
@ -60,21 +96,41 @@ QtObject {
|
||||||
* @param identifier string the identifier defined in registerCustomAction
|
* @param identifier string the identifier defined in registerCustomAction
|
||||||
*/
|
*/
|
||||||
function customActionInvoked(identifier) {
|
function customActionInvoked(identifier) {
|
||||||
|
|
||||||
var pathToTaskwarrior = "/usr/bin/task";
|
|
||||||
|
|
||||||
switch (identifier) {
|
switch (identifier) {
|
||||||
// export selected lines to Taskwarriors as tasks.
|
// export selected lines to Taskwarriors as tasks.
|
||||||
// The project name will be taken from "Project:" keyword detected in first lines.
|
// The project name will be taken from "Project:" keyword detected in first lines.
|
||||||
case "exportToTaskwarrior":
|
case "exportToTaskwarrior":
|
||||||
|
|
||||||
|
logIfVerbose("Exporting tasks from a note.");
|
||||||
|
|
||||||
// Starting with an empty default project name.
|
// Starting with an empty default project name.
|
||||||
var projectName = "";
|
// We are keeping the project name as a array of strings. We will concatenate them to
|
||||||
|
// get the final projectName with nesting.
|
||||||
|
var projectName = [];
|
||||||
|
var referenceHeaderLevel = 0;
|
||||||
|
|
||||||
// For each line, we are gathering data to properly create tasks.
|
// For each line, we are gathering data to properly create tasks.
|
||||||
getSelectedTextAndSeparateByNewline().forEach(function (line){
|
getSelectedTextAndSeparateByNewline().forEach(function (line){
|
||||||
if (getProjectNameAndRun(line, function (proName) {
|
if (getProjectNameAndRun(line, function (proName, headerLevel) {
|
||||||
projectName = proName;
|
logIfVerbose("Detected project name: " + proName);
|
||||||
|
logIfVerbose("Detected header level: " + headerLevel);
|
||||||
|
|
||||||
|
if (projectName.length === 0) {
|
||||||
|
referenceHeaderLevel = headerLevel - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectName.length + referenceHeaderLevel >= headerLevel) {
|
||||||
|
var i;
|
||||||
|
for (i = projectName.length + referenceHeaderLevel - headerLevel + 1; i > 0; i--) {
|
||||||
|
projectName.pop();
|
||||||
|
if (projectName.length === 0) {
|
||||||
|
referenceHeaderLevel = headerLevel - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
projectName.push(proName);
|
||||||
|
|
||||||
// We expect, that the project name would be the only thing in line, hence `return`.
|
// We expect, that the project name would be the only thing in line, hence `return`.
|
||||||
return;
|
return;
|
||||||
})) return;
|
})) return;
|
||||||
|
@ -86,11 +142,15 @@ QtObject {
|
||||||
|
|
||||||
var isTask = taskRegExp.exec(line);
|
var isTask = taskRegExp.exec(line);
|
||||||
if (isTask) {
|
if (isTask) {
|
||||||
|
|
||||||
taskDescription = isTask[1];
|
taskDescription = isTask[1];
|
||||||
script.startDetachedProcess(pathToTaskwarrior,
|
logIfVerbose("Detected task: " + taskDescription);
|
||||||
|
var concatenatedProjectName = projectName.join('.');
|
||||||
|
logIfVerbose("Executing \"" + taskPath + " add pro:" + concatenatedProjectName + " " + taskDescription + "\"");
|
||||||
|
script.startDetachedProcess(taskPath,
|
||||||
[
|
[
|
||||||
"add",
|
"add",
|
||||||
"pro:" + projectName,
|
"pro:" + concatenatedProjectName,
|
||||||
taskDescription
|
taskDescription
|
||||||
]);
|
]);
|
||||||
// We expect, that the task description would be the only thing in the line, hence `return`.
|
// We expect, that the task description would be the only thing in the line, hence `return`.
|
||||||
|
@ -103,10 +163,35 @@ QtObject {
|
||||||
// Get selected text to determine the project we want to import from.
|
// Get selected text to determine the project we want to import from.
|
||||||
|
|
||||||
var projectNames = [];
|
var projectNames = [];
|
||||||
|
var referenceHeaderLevel = 0;
|
||||||
|
|
||||||
getSelectedTextAndSeparateByNewline().forEach(function (line){
|
getSelectedTextAndSeparateByNewline().forEach(function (line){
|
||||||
if (getProjectNameAndRun(line, function (proName) {
|
if (getProjectNameAndRun(line, function (proName, headerLevel) {
|
||||||
projectNames.push(proName);
|
if (projectNames.length === 0) {
|
||||||
|
logIfVerbose("No project detected yet. Inserting " + proName)
|
||||||
|
projectNames.push([proName]);
|
||||||
|
logIfVerbose("Reference header level set to " + headerLevel)
|
||||||
|
referenceHeaderLevel = headerLevel - 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newProjectName = projectNames[projectNames.length - 1].slice();
|
||||||
|
logIfVerbose("Last project name inserted was " + newProjectName.join('.'));
|
||||||
|
if (newProjectName.length + referenceHeaderLevel >= headerLevel) {
|
||||||
|
logIfVerbose("Same header level detected.");
|
||||||
|
var i;
|
||||||
|
for (i = newProjectName.length + referenceHeaderLevel - headerLevel + 1; i > 0; i--) {
|
||||||
|
newProjectName.pop();
|
||||||
|
if (newProjectName.length === 0) {
|
||||||
|
referenceHeaderLevel = headerLevel - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newProjectName.push(proName);
|
||||||
|
projectNames.push(newProjectName);
|
||||||
|
logIfVerbose("Project name detected. Inserted value is " + newProjectName.join('.'))
|
||||||
|
|
||||||
})) return;
|
})) return;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -114,21 +199,32 @@ QtObject {
|
||||||
script.noteTextEditWrite(script.noteTextEditSelectedText());
|
script.noteTextEditWrite(script.noteTextEditSelectedText());
|
||||||
|
|
||||||
projectNames.forEach( function(projectName) {
|
projectNames.forEach( function(projectName) {
|
||||||
var result = script.startSynchronousProcess(pathToTaskwarrior,
|
var concatenatedProjectName = projectName.join('.');
|
||||||
|
var result = script.startSynchronousProcess(taskPath,
|
||||||
[
|
[
|
||||||
"pro:" + projectName,
|
"pro.is:" + concatenatedProjectName,
|
||||||
"rc.report.next.columns=description",
|
"rc.report.next.columns=id,description.desc",
|
||||||
"rc.report.next.labels=Desc"
|
"rc.report.next.labels=ID,Desc"
|
||||||
],
|
],
|
||||||
"");
|
"");
|
||||||
if (result) {
|
if (result) {
|
||||||
|
// via https://stackoverflow.com/a/35635260
|
||||||
|
var repeat = function(str, count) {
|
||||||
|
var array = [];
|
||||||
|
for(var i = 0; i < count;)
|
||||||
|
array[i++] = str;
|
||||||
|
return array.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
script.noteTextEditWrite("\n");
|
||||||
|
script.noteTextEditWrite(repeat('#', projectName.length) + ' ' + projectName[projectName.length - 1] + "\n\n");
|
||||||
var tasksSeparated;
|
var tasksSeparated;
|
||||||
// The result does not contain any \n, so we are splitting by whitespace.
|
// The result does not contain any \n, so we are splitting by whitespace.
|
||||||
tasksSeparated = result.toString().split('\n');
|
tasksSeparated = result.toString().split('\n');
|
||||||
|
|
||||||
tasksSeparated.splice(0, 1); // removing ""
|
tasksSeparated.splice(0, 1); // removing ""
|
||||||
if (tasksSeparated.length === 0) {
|
if (tasksSeparated.length === 0) {
|
||||||
script.log("No entries");
|
logIfVerbose("No entries");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tasksSeparated.splice(0, 1); // removing "Desc"
|
tasksSeparated.splice(0, 1); // removing "Desc"
|
||||||
|
@ -138,12 +234,22 @@ QtObject {
|
||||||
tasksSeparated.splice(tasksSeparated.length - 1, 1); // removing "X tasks"
|
tasksSeparated.splice(tasksSeparated.length - 1, 1); // removing "X tasks"
|
||||||
tasksSeparated.splice(tasksSeparated.length - 1, 1); // removing ""
|
tasksSeparated.splice(tasksSeparated.length - 1, 1); // removing ""
|
||||||
|
|
||||||
script.noteTextEditWrite("\n");
|
var taskIds = [];
|
||||||
|
tasksSeparated.forEach( function(task){
|
||||||
script.noteTextEditWrite("Project: " + projectName + "\n\n");
|
var taskParamsRegexp = /(\d+)[\s*]?(.+)?[\s*]?/i;
|
||||||
tasksSeparated.forEach( function(taskDesc){
|
var fetchTaskParams = taskParamsRegexp.exec(task);
|
||||||
script.noteTextEditWrite("* " + taskDesc + "\n");
|
logIfVerbose("Extracted data from task: ID " + fetchTaskParams[1] + " Desc: " + fetchTaskParams[2]);
|
||||||
|
script.noteTextEditWrite("* " + fetchTaskParams[2] + "\n");
|
||||||
|
taskIds.push(fetchTaskParams[1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (deleteOnImport) {
|
||||||
|
script.startDetachedProcess(taskPath,
|
||||||
|
[
|
||||||
|
taskIds.join(' '),
|
||||||
|
"delete"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue