Skip to content

Instantly share code, notes, and snippets.

@jangroth
Created March 25, 2023 04:53
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jangroth/6b2d333d8e5f437627fab5c33a61cf1a to your computer and use it in GitHub Desktop.
Save jangroth/6b2d333d8e5f437627fab5c33a61cf1a to your computer and use it in GitHub Desktop.
Converts dendron note structure to obsidian note structure
# converts dendron note structure to obsidian note structure
# - removes frontmatter
# - adds tags
# - converts internal links
# - ignores empty (frontmatter-only) notes
#
# use at your own risk
import glob
import re
import os
PATH_TO_DENDRON_VAULT = "TODO"
PATH_OBSIDIAN_VAULT = "TODO"
TAGS = {
"study": "#study",
"study.k8s": "#k8s",
"study.k8s.ckad": "#ckad",
# ...
"tools": "#tools",
"tools.vscode": "#vscode",
}
def to_wikilink(dendron_link):
segments = dendron_link.split("#")
link_components = segments[0].split(".")
if len(segments) == 1:
link = dendron_link
appendix = ""
note_name = link_components[-1]
else:
link = segments[0]
appendix = f"#{'#'.join(segments[1:])}"
note_name = segments[-1]
obsidian_link = link.replace(".", "/")
return f"{obsidian_link}.md{appendix}|{note_name}"
def _cleanse_markdown(dendron_path):
with open(dendron_path, "r") as f:
raw_content = f.read()
# remove frontmatter
blocks = raw_content.split("---\n")
md_content = "---\n".join(blocks[2:])
# add tags
file_name = os.path.basename(dendron_path)
if not any([tag in file_name for tag in ["daily", "monthly"]]) and not _is_empty(
md_content
):
tags = [v for k, v in TAGS.items() if file_name.startswith(k)]
md_content = f"{' '.join(tags)}\n{md_content}"
# convert internal links
for match in re.findall(r"\[\[(.*?)\]\]", md_content):
md_content = md_content.replace(f"[[{match}]]", f"[[{to_wikilink(match)}]]")
return md_content
def _is_empty(md_content):
return len(md_content.strip()) == 0
def _convert_dendron_note(dendron_path):
dendron_filename = os.path.basename(
dendron_path
) # tech.powershell.commands.md | cmd.journal.daily.2023.03.09.md | cmd.journal.monthly.2022.10.md
if "daily" in dendron_filename:
obs_dirs = "journal/daily"
date_part = dendron_filename.split(".")[-4:-1]
obs_name = "-".join(date_part) + ".md"
elif "monthly" in dendron_filename:
year = dendron_filename.split(".")[-3]
obs_dirs = f"journal/monthly/{year}"
obs_name = dendron_filename.split(".")[-2] + ".md"
print(obs_dirs, obs_name)
else:
obs_dirs = "/".join(dendron_filename.split(".")[:-2])
obs_name = f"{dendron_filename.split('.')[-2]}.md"
return obs_dirs, obs_name
def _create_note(obs_dirs, obs_note, md_content):
if not os.path.exists(f"{PATH_OBSIDIAN_VAULT}/{obs_dirs}"):
os.makedirs(f"{PATH_OBSIDIAN_VAULT}/{obs_dirs}")
with open(f"{PATH_OBSIDIAN_VAULT}/{obs_dirs}/{obs_note}", "w") as f:
f.write(md_content)
if __name__ == "__main__":
for filepath in glob.iglob(f"{PATH_TO_DENDRON_VAULT}/*.md"):
md_content = _cleanse_markdown(filepath)
if not _is_empty(md_content):
obs_dirs, obs_name = _convert_dendron_note(filepath)
_create_note(obs_dirs, obs_name, md_content)
print(filepath, "converted to", f"{obs_dirs}/{obs_name}")
else:
print("Skipping empty file", filepath)
@VFansss
Copy link

VFansss commented Apr 30, 2023

Hi there! I've improved the script, adding the following things:

  • Script now open file with UTF-8 Encoding
  • Script leaved ugly "\n" at the top of the file. Now it doesn't (is also compatible with tags)
  • PATH_TO_DENDRON_VAULT and PATH_OBSIDIAN_VAULT doesn't need sanitized path anymore
  • Add a warning for manually copying media from Dendron Vault to new one (I had to manually copy my "asset" folder, otherwise no referenced .png, .jpg and other files are available in Obsidian)

I've also Forked the snippet, but I guess is less interesting!

# converts dendron note structure to obsidian note structure
# - removes frontmatter
# - adds tags
# - converts internal links
# - ignores empty (frontmatter-only) notes
#
# use at your own risk

# !!!!!
#
# Remember to manually copy used media from Dendron folder to Obsidian Vault!
#
# !!!!!

import glob
import re
import os

# Replace Vaults paths with your owns
# Note: leave r before path string to avoid sanitizing slash and backslash 

PATH_TO_DENDRON_VAULT = r"C:\Users\Alex\Desktop\dendron-vault"
PATH_OBSIDIAN_VAULT = r"C:\Users\Alex\Desktop\obsidian-vault"
TAGS = {
    "study": "#study",
    "study.k8s": "#k8s",
    "study.k8s.ckad": "#ckad",
    # ...
    "tools": "#tools",
    "tools.vscode": "#vscode",
}


def to_wikilink(dendron_link):
    segments = dendron_link.split("#")
    link_components = segments[0].split(".")
    if len(segments) == 1:
        link = dendron_link
        appendix = ""
        note_name = link_components[-1]
    else:
        link = segments[0]
        appendix = f"#{'#'.join(segments[1:])}"
        note_name = segments[-1]
    obsidian_link = link.replace(".", "/")
    return f"{obsidian_link}.md{appendix}|{note_name}"


def _cleanse_markdown(dendron_path):
    with open(dendron_path, "r", encoding="utf8") as f:
        raw_content = f.read()

    # remove frontmatter
    blocks = raw_content.split("---\n")
    md_content = "---\n".join(blocks[2:])

    # add tags
    file_name = os.path.basename(dendron_path)

    if not any([tag in file_name for tag in ["daily", "monthly"]]) and not _is_empty(
        md_content
    ):
        tags = [v for k, v in TAGS.items() if file_name.startswith(k)]
        md_content = f"{' '.join(tags)}\n{md_content}"

    # convert internal links
    for match in re.findall(r"\[\[(.*?)\]\]", md_content):
        md_content = md_content.replace(f"[[{match}]]", f"[[{to_wikilink(match)}]]")

    first_non_newline = None
    for i, char in enumerate(md_content):
        if char != '\n':
            first_non_newline = i
            break
    if first_non_newline is not None:
        md_content = md_content[first_non_newline:].lstrip('\n')
    
    return md_content


def _is_empty(md_content):
    return len(md_content.strip()) == 0


def _convert_dendron_note(dendron_path):
    dendron_filename = os.path.basename(
        dendron_path
    )  # tech.powershell.commands.md | cmd.journal.daily.2023.03.09.md | cmd.journal.monthly.2022.10.md
    if "daily" in dendron_filename:
        obs_dirs = "journal/daily"
        date_part = dendron_filename.split(".")[-4:-1]
        obs_name = "-".join(date_part) + ".md"
    elif "monthly" in dendron_filename:
        year = dendron_filename.split(".")[-3]
        obs_dirs = f"journal/monthly/{year}"
        obs_name = dendron_filename.split(".")[-2] + ".md"
        print(obs_dirs, obs_name)
    else:
        obs_dirs = "/".join(dendron_filename.split(".")[:-2])
        obs_name = f"{dendron_filename.split('.')[-2]}.md"
    return obs_dirs, obs_name


def _create_note(obs_dirs, obs_note, md_content):
    if not os.path.exists(f"{PATH_OBSIDIAN_VAULT}/{obs_dirs}"):
        os.makedirs(f"{PATH_OBSIDIAN_VAULT}/{obs_dirs}")
    with open(f"{PATH_OBSIDIAN_VAULT}/{obs_dirs}/{obs_note}", "w", encoding="utf8") as f:
        f.write(md_content)


if __name__ == "__main__":
    for filepath in glob.iglob(f"{PATH_TO_DENDRON_VAULT}/*.md"):
        md_content = _cleanse_markdown(filepath)
        if not _is_empty(md_content):
            obs_dirs, obs_name = _convert_dendron_note(filepath)
            _create_note(obs_dirs, obs_name, md_content)
            print(filepath, "converted to", f"{obs_dirs}/{obs_name}")
        else:
            print("Skipping empty file", filepath)

@jangroth
Copy link
Author

Thanks 🙏

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