Skip to content

Instantly share code, notes, and snippets.

@DavideWiest
Created March 24, 2024 10:42
Show Gist options
  • Select an option

  • Save DavideWiest/96c39de773cdee369c2d42298963dc10 to your computer and use it in GitHub Desktop.

Select an option

Save DavideWiest/96c39de773cdee369c2d42298963dc10 to your computer and use it in GitHub Desktop.
Markdown Tasks to Taskpaper (Obsidian Tasks Syntax)
import glob
import os
import sys
import re
import urllib.parse
# sorted by priority
markdown_priorities = [
"highest", "high", "medium", "low", "lowest"
]
use_tabs_as_indetation_primarily = True
keep_indentation = True
replace_tag_slash_with_dash = True
date_as_due_else_date = True
remove_insertion_strings = True
replace_obsidian_links_with_url = True
obsidian_vault_name = "V-1.2"
obsidian_base_subdir = "K - Projektinhalte"
obsidian_url = "obsidian://vault/" + urllib.parse.quote(obsidian_vault_name) + "/" + urllib.parse.quote(obsidian_base_subdir)
remove_leftover_symbols = True
def convert_md_to_taskpaper(fileContents, file_path):
taskpaper = ""
lines = fileContents.split("\n")
for line in lines:
# count indentation level
if use_tabs_as_indetation_primarily:
line = line.replace("\t", " ")
indentation = line.count(" ")
line = line.strip()
print(indentation)
if line.startswith("- ["):
is_done = line[3] == "X"
task = line[5:]
task = task.strip()
task = " ".join(task.split())
task = task.replace(":: ", "::")
task = task.replace(" ::", "::")
# remove insertion strings
if remove_insertion_strings:
task = re.sub(r"\{\{.*?\}\}", "", task)
# replace obsidian links
if replace_obsidian_links_with_url:
task = task.replace("]]", f"]({obsidian_url})").replace("[[", "[")
# replace priority
for priority in markdown_priorities:
task = task.replace(f"[priority::{priority}]", f"@priorty({markdown_priorities.index(priority)+1})")
# replace date
date = [i.split("::")[1].strip() for i in task.split(" ") if i.startswith("[due::") or i.startswith("[start::") or i.startswith("[scheduled::")]
date = [i.replace("]", "") for i in date if i != ""]
task = " ".join([i for i in task.split(" ") if not (i.startswith("[due::") or i.startswith("[start::") or i.startswith("[scheduled::") or i.startswith("[completion::]"))])
if date != []:
if date_as_due_else_date:
task += f" @due({date[0]})"
else:
task += f" @date({date[0]})"
# replace tags
tags = [i[1:] for i in task.split(" ") if i.startswith("#")]
if replace_tag_slash_with_dash:
tags = [i.replace("/", "-") for i in tags]
task = " ".join([i for i in task.split(" ") if not i.startswith("#")])
for tag in tags:
task += f" @{tag}"
if is_done:
task += " @done"
# remove leftover symbols
if remove_leftover_symbols:
task = " " + task + " "
for symbol in [":", ".", ",", ";", "-"]:
task = task.replace(symbol, "")
task = task.strip()
if keep_indentation:
indent = ("\t" if use_tabs_as_indetation_primarily else " ") * indentation
else:
indent = ""
taskpaper += indent + "- " + task + "\n"
else:
taskpaper += line + "\n"
return taskpaper
def getFileTuples(fileOrFolder, outputLocation = None):
path = fileOrFolder
fileContents = []
if outputLocation is None:
outputLocation = fileOrFolder
if os.path.isfile(path):
fileContents.append((path, open(path, "r", encoding="utf-8").read(), outputLocation.replace(".md", ".taskpaper")))
elif os.path.isdir(path):
for filePath in glob.glob(os.path.join(path, "*.md")):
file = os.path.basename(filePath)
fileContents.append((filePath, open(filePath, "r", encoding="utf-8").read(), os.path.join(outputLocation, file.replace(".md", ".taskpaper"))))
else:
print("Please provide a valid file or folder as a command line argument.")
sys.exit(1)
return fileContents
if __name__ == "__main__":
# extract parameters file or folder
# Check if a file or folder is provided as a command line argument
if len(sys.argv) == 2:
fileContents = getFileTuples(sys.argv[1])
elif len(sys.argv) > 2:
fileContents = getFileTuples(sys.argv[1], sys.argv[2])
else:
print("Please provide a file or folder as a command line argument.")
sys.exit(1)
for (filePath, fileContents, outputLocation) in fileContents:
taskpaper = convert_md_to_taskpaper(fileContents, filePath)
# Create directory if it doesn't exist
if not os.path.exists(outputLocation.rsplit("/", 1)[0]):
os.makedirs(outputLocation.rsplit("/", 1)[0])
# Write taskpaper to file
with open(outputLocation, "w", encoding="utf-8") as f:
f.write(taskpaper)
@DavideWiest

DavideWiest commented Mar 24, 2024

Copy link
Copy Markdown
Author

Examples

  • python md_to_taskpaper.py myfile.md: Convert a single Markdown file (myfile.md) to TaskPaper (same directory)
  • python md_to_taskpaper.py myfile.md output_folder: Convert a single Markdown file (myfile.md) to TaskPaper (different output directory)
  • python md_to_taskpaper.py myfolder output_folder: Convert all Markdown files in a folder (myfolder) to TaskPaper (other directory)
  • python md_to_taskpaper.py myfolder: Convert all Markdown files in a folder (myfolder) to TaskPaper (same directory)
  • python md_to_taskpaper.py myfile.txt: Convert a non-standard Markdown file (myfile.txt) to TaskPaper (optionally add an output path)

@DavideWiest

Copy link
Copy Markdown
Author

Configuration

markdown_priorities = ["highest", "high", "medium", "low", "lowest"]
use_tabs_as_indetation_primarily = True
keep_indentation = True
replace_tag_slash_with_dash = True
date_as_due_else_date = True
remove_insertion_strings = True
replace_obsidian_links_with_url = True
obsidian_vault_name = "MyVault"
obsidian_base_subdir = "Templates"
obsidian_url = "obsidian://vault/" + urllib.parse.quote(obsidian_vault_name) + "/" + urllib.parse.quote(obsidian_base_subdir)
remove_leftover_symbols = True
  • if replace_obsidian_links_with_url´ is set to true, it's recommended to change obsidian_vault_nameandobsidian_base_subdir`

@DavideWiest

DavideWiest commented Mar 24, 2024

Copy link
Copy Markdown
Author

Use Case

  1. Migrate from markdown tasks to taskpaper
  2. Migrate from markdown tasks to Todoist (over the taskbone integration)
  3. Migrate from obsidian tasks to Todoist (see 2.)
    • Obsidian link conversion is supported
    • Obsidian Tasks syntax conversion is fully supported

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment