From 787a24c6dc052a07fad252e3782c32a29ddcad19 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Mon, 1 Nov 2021 10:28:55 +0100 Subject: [PATCH 1/7] Refactor subprocess stdlib import --- jrnlwarrior.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jrnlwarrior.py b/jrnlwarrior.py index 546a5bf..573188e 100755 --- a/jrnlwarrior.py +++ b/jrnlwarrior.py @@ -8,7 +8,7 @@ import re import os import sys -from subprocess import Popen, PIPE, check_output +import subprocess import options as opts import json from datetime import datetime @@ -91,7 +91,7 @@ def log_completed_to_taskwarrior(task_string, date, options): if options.dryrun: print(cmd) return - Popen(cmd) + subprocess.Popen(cmd) def handle_open_tasks(line, date, options): @@ -119,7 +119,7 @@ def add_incomplete_to_taskwarrior(task_string, date, options): if options.dryrun: print(cmd) return - Popen(cmd) + subprocess.Popen(cmd) def get_prio_string(task_string): @@ -162,7 +162,7 @@ def is_today(cur_date): def add_today(fname, options): cmd = ["task", "+TODAY or +OVERDUE", "export"] tasks = f"[{TODAY} 09:00] {options.todo_block_title}\n" - for task in json.loads(check_output(cmd)): + for task in json.loads(subprocess.check_output(cmd)): tasks += f"{task['description']}\n" if options.dryrun: From c6cb3dd6c37105c53d952c1b64a7f72a8aa7f965 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Mon, 1 Nov 2021 10:30:07 +0100 Subject: [PATCH 2/7] Add idea parsing for jrnl file --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++++----- jrnlwarrior.py | 30 ++++++++++++++++++++++++++++++ options.py | 10 ++++++++++ 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 162ab38..544da71 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,52 @@ More specifically, it: (marked with an empty `[ ] ` at the beginning of their line) * adds an entry for today's date if none exists yet and populates it with due/overdue tasks from `taskwarrior` +To accomplish this it borrows a little from the [todo.txt](http://todotxt.org/) syntax --- +namely the idea of (A) (B) (C) prioritization and `x task done syntax` +(i.e. starting a line with `x ` or `[x] ` means it represents an accomplished task). + All three of these operations *only* operate on entries which are marked with a special title (`todotxt` by default), though this can be changed through a regex option. That means, it is also entirely possible to have other entries (with the same date) which will simply be ignored by the script. -To accomplish this it borrows a little from the [todo.txt](http://todotxt.org/) syntax --- -namely the idea of (A) (B) (C) prioritization and `x task done syntax` -(i.e. starting a line with `x ` or `[x] ` means it represents an accomplished task). +Lastly, it transfers what I call 'ideas' to `taskwarrior`. +These are small tidbits and notions I jot down during the day (hence, 'ideas') +and marked as such by starting the line either with `idea: ` or the `taskwarrior` equivalent `+idea `. +I want them transferred to `taskwarrior` so they don't get lost overall, +but they should be +a) not bound to a specific date, even when written in a specific `jrnl` entry, and +b) specifically marked as vague ideas in `taskwarrior`. +For now, they will be applied the fixed tags `+idea +maybe` in `taskwarrior` and simply +transferred date-less. + +An example `jrnl` file: + +```jrnl +[2021-10-30 10:16] todotxt +This is just a test entry. It shows how tasks in todo blocks are handled. +[ ] This is an incomplete task - it will be transferred directly to tw. +[x] This is a completed task - it will be logged as completed in tw. +[ ] (A) This is an incomplete task which will get assigned high priority in taskwarrior. +x This is also a completed task, as long as the x is the first thing on the line with a space behind. +x All four of these tasks will be transferred to tw, and their lines deleted from this file. + +idea: This is an idea and it will be transferred to tw with corresponding tags, then deleted from this file. + +This is not a task but a normal line and will remain in the file. +xAs will this, +[ ]As will this. + +[ ] This will be transferred again, however. + +[2021-10-29 10:16] Another entry +This is a regular jrnl entry, since it is missing the correct title. +By default it looks for `todotxt` titles, +but the title can be freely set for the script (`-b` option). +jrnlwarrior will not look for incomplete or completed tasks within this block at all. +[x] This is a completed task, but it will not be transferred to taskwarrior. +[ ] Neither will this incomplete task. ++idea This idea however *will* be transferred, since ideas are looked for in *all* entries. +``` ## Usage @@ -52,7 +91,7 @@ If you want to switch off one of the three ways this script connects the two, you can use the `-L`, `-I`, `-T` options which turn off logging, adding, or creating entries respectively. ```bash -./jrnlwarrior.py -I -T +./jrnlwarrior.py -DIT ``` The above invocation will *only* log completed tasks to `taskwarrior`. @@ -63,7 +102,7 @@ Adding new to-dos and creating today entries is turned off. ``` This will create a one-way connection to `taskwarrior`, -by transferring both new and completed tasks to the program. +by transferring both new and completed tasks and ideas to the program. However, tasks to be done today will not be transferred back to this file. ## Scope diff --git a/jrnlwarrior.py b/jrnlwarrior.py index 573188e..59a7574 100755 --- a/jrnlwarrior.py +++ b/jrnlwarrior.py @@ -40,6 +40,9 @@ def process_file(options): for line in file: line_number += 1 + if handle_idea(line, options): + lines_to_delete.append(line_number) + todo_block = get_todo_block_date(line, options.todo_block_title) if todo_block == False: curdate = "" @@ -122,6 +125,33 @@ def add_incomplete_to_taskwarrior(task_string, date, options): subprocess.Popen(cmd) +def handle_idea(line, options): + completed_task = re.search( + rf"^{options.regex_task_idea} (.*)", + line, + ) + if completed_task and options.taskwarrior_add_ideas: + add_idea_to_taskwarrior(completed_task[1], options) + return True + return False + + +def add_idea_to_taskwarrior(idea_string, options): + overrides = options.taskwarrior_overrides + cmd = [ + "task", + *overrides, + "add", + "+idea", + "+maybe", + idea_string, + ] + if options.dryrun: + print(cmd) + return + subprocess.Popen(cmd) + + def get_prio_string(task_string): prio = re.search(r"^\(([ABC])\) (.*)$", task_string) prio_string = "" diff --git a/options.py b/options.py index 8f72fe2..8a12439 100644 --- a/options.py +++ b/options.py @@ -13,6 +13,7 @@ class Options: taskwarrior_log_completed: bool = True taskwarrior_add_incomplete: bool = True taskwarrior_fill_today: bool = True + taskwarrior_add_ideas: bool = True # can not yet be changed taskwarrior_overrides: List = field( @@ -24,6 +25,7 @@ class Options: ) regex_task_completed: str = r"(?:x|\[x\])" regex_task_incomplete: str = r"\[ \]" + regex_task_idea: str = r"(?:idea:|\+idea)" def init(): @@ -63,6 +65,12 @@ def parse_cmdline_args(options): help="don't add today's todo items as entry to file", action="store_true", ) + parser.add_argument( + "-D", + "--noidea", + help="don't add ideas as maybe items to taskwarrior", + action="store_true", + ) args = parser.parse_args() if args.dryrun: @@ -71,6 +79,8 @@ def parse_cmdline_args(options): options.taskwarrior_log_completed = False if args.noincomplete: options.taskwarrior_add_incomplete = False + if args.noidea: + options.taskwarrior_add_ideas = False if args.nofilltoday: options.taskwarrior_fill_today = False if args.file: From ec5ff4744925591e132147868472683a615a11b6 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Mon, 1 Nov 2021 12:00:48 +0100 Subject: [PATCH 3/7] Refactor options --- options.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/options.py b/options.py index 8a12439..faa29e1 100644 --- a/options.py +++ b/options.py @@ -30,7 +30,10 @@ class Options: def init(): opt = Options() - parse_cmdline_args(opt) + + parser = argparse.ArgumentParser() + create_cmdline_args(parser) + parse_cmdline_args(parser, opt) if opt.dryrun: dryrun_show_options(opt) return opt @@ -40,8 +43,7 @@ def dryrun_show_options(options): print(options) -def parse_cmdline_args(options): - parser = argparse.ArgumentParser() +def create_cmdline_args(parser): parser.add_argument( "-n", "--dryrun", help="only simulate and print changes", action="store_true" ) @@ -71,6 +73,9 @@ def parse_cmdline_args(options): help="don't add ideas as maybe items to taskwarrior", action="store_true", ) + + +def parse_cmdline_args(parser, options): args = parser.parse_args() if args.dryrun: From 978da38001024a7890d090651a5a788582127fff Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Mon, 1 Nov 2021 12:01:10 +0100 Subject: [PATCH 4/7] Fix file writing and replacement process --- jrnlwarrior.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/jrnlwarrior.py b/jrnlwarrior.py index 59a7574..b73d1a9 100755 --- a/jrnlwarrior.py +++ b/jrnlwarrior.py @@ -35,6 +35,7 @@ def process_file(options): fname = options.jrnl_fname lines_to_delete = [] today_exists = False + curdate = "" with open(fname) as file: line_number = 0 for line in file: @@ -62,11 +63,11 @@ def process_file(options): if is_today(curdate): today_exists = True - if lines_to_delete: - delete_lines_from_file(fname, lines_to_delete, options.dryrun) + if lines_to_delete: + delete_lines_from_file(fname, lines_to_delete, options.dryrun) - if not today_exists and options.taskwarrior_fill_today: - add_today(fname, options) + if not today_exists and options.taskwarrior_fill_today: + add_today(fname, options) def handle_completed_tasks(line, date, options): @@ -190,22 +191,23 @@ def is_today(cur_date): def add_today(fname, options): - cmd = ["task", "+TODAY or +OVERDUE", "export"] + cmd = ["task", *options.taskwarrior_overrides, "+TODAY or +OVERDUE", "export"] + due_json = json.loads(subprocess.run(cmd,capture_output=True).stdout) + tasks = f"[{TODAY} 09:00] {options.todo_block_title}\n" - for task in json.loads(subprocess.check_output(cmd)): + for task in due_json: tasks += f"{task['description']}\n" + tasks += "\n" if options.dryrun: print(f"\nWRITING TODAY:\n{tasks}") return - repl_file = fname + ".bak" - with open(fname) as read_file, open(repl_file, "w") as write_file: - write_file.write(tasks + "\n") - for line in read_file: - write_file.write(line) - os.rename(repl_file, fname) - + repl_fname = fname + ".bak" + with open(fname, "r") as read_file, open(repl_fname, "w") as write_file: + write_file.write(tasks) + write_file.write(read_file.read()) + os.rename(repl_fname, fname) if __name__ == "__main__": process_file(opts.init()) From 417536f1b587140cd8354a219c037890c752e2e6 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 14 Dec 2021 17:55:48 +0100 Subject: [PATCH 5/7] Switching subprocess commands to recommended ones Switched out all `subprocess.Popen` statements for `subprocess.run` since they are the more recommended and less low-level versions. --- jrnlwarrior.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/jrnlwarrior.py b/jrnlwarrior.py index b73d1a9..7f206fe 100755 --- a/jrnlwarrior.py +++ b/jrnlwarrior.py @@ -95,7 +95,7 @@ def log_completed_to_taskwarrior(task_string, date, options): if options.dryrun: print(cmd) return - subprocess.Popen(cmd) + subprocess.run(cmd) def handle_open_tasks(line, date, options): @@ -123,7 +123,7 @@ def add_incomplete_to_taskwarrior(task_string, date, options): if options.dryrun: print(cmd) return - subprocess.Popen(cmd) + subprocess.run(cmd) def handle_idea(line, options): @@ -150,7 +150,7 @@ def add_idea_to_taskwarrior(idea_string, options): if options.dryrun: print(cmd) return - subprocess.Popen(cmd) + subprocess.run(cmd) def get_prio_string(task_string): @@ -192,7 +192,7 @@ def is_today(cur_date): def add_today(fname, options): cmd = ["task", *options.taskwarrior_overrides, "+TODAY or +OVERDUE", "export"] - due_json = json.loads(subprocess.run(cmd,capture_output=True).stdout) + due_json = json.loads(subprocess.run(cmd, capture_output=True).stdout) tasks = f"[{TODAY} 09:00] {options.todo_block_title}\n" for task in due_json: @@ -209,6 +209,7 @@ def add_today(fname, options): write_file.write(read_file.read()) os.rename(repl_fname, fname) + if __name__ == "__main__": process_file(opts.init()) sys.exit(0) From 253dc4ba37807d778e2874bf2f2c50db228f3d4b Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 14 Dec 2021 17:57:19 +0100 Subject: [PATCH 6/7] Fix default file path Fixed the location of the default file path to mirror the one mentioned in the README. It now roughly adheres to the XDG home data location as its default. Added simple path validation for passed in file paths - will not see if its readable/writable but simply look for the file's existence and error out otherwise. --- options.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/options.py b/options.py index faa29e1..111242e 100644 --- a/options.py +++ b/options.py @@ -1,4 +1,5 @@ import os +import sys import argparse from dataclasses import dataclass, field from typing import List @@ -6,8 +7,9 @@ from typing import List @dataclass class Options: - # can be changed - jrnl_fname: str = f"{os.path.expanduser('~')}/documents/records/todo.md" + jrnl_fname: str = field( + default_factory=lambda: f"{os.environ.get('XDG_DATA_HOME', os.environ.get('HOME'))}/jrnl/journal.txt" + ) todo_block_title: str = "todotxt" dryrun: bool = False taskwarrior_log_completed: bool = True @@ -36,6 +38,7 @@ def init(): parse_cmdline_args(parser, opt) if opt.dryrun: dryrun_show_options(opt) + validate_opts(opt) return opt @@ -94,3 +97,9 @@ def parse_cmdline_args(parser, options): options.todo_block_title = args.blocktitle return options + + +def validate_opts(options): + if not os.path.isfile(options.jrnl_fname): + print(f"{options.jrnl_fname} does not seem to be a file. Aborting.") + sys.exit(1) From 426925c6a775853a32c68952066f09749e7b0432 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 14 Dec 2021 18:00:48 +0100 Subject: [PATCH 7/7] Fix missing mention of idea toggle in README Added usage instructions for turning off idea syncing. --- .gitlab-ci.yml | 13 ------------- README.md | 4 ++-- 2 files changed, 2 insertions(+), 15 deletions(-) delete mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index bf177e0..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,13 +0,0 @@ -# You can override the included template(s) by including variable overrides -# SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings -# Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings -# Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings -# Note that environment variables can be set in several places -# See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence -stages: -- test -sast: - stage: test -include: -- template: Security/SAST.gitlab-ci.yml - diff --git a/README.md b/README.md index 544da71..acb0538 100644 --- a/README.md +++ b/README.md @@ -88,14 +88,14 @@ lines to be removed from the file and any to-do entries added to the file. It will not change your `jrnl` file in any way. If you want to switch off one of the three ways this script connects the two, -you can use the `-L`, `-I`, `-T` options which turn off logging, adding, or creating entries respectively. +you can use the `-L`, `-I`, `-T`, `-D` options which turn off [*L*]ogging completed to `taskwarrior`, [*I*]nserting incomplete to `taskwarrior`, creating new `jrnl` entries for [*T*]oday's tasks, or moving hatched i[*D*]eas to `taskwarrior` respectively. ```bash ./jrnlwarrior.py -DIT ``` The above invocation will *only* log completed tasks to `taskwarrior`. -Adding new to-dos and creating today entries is turned off. +Adding new to-dos, ideas and creating today entries is turned off. ```bash ./jrnlwarrior.py -T