Last active
November 10, 2019 08:29
-
-
Save noklam/08d7afd25dd6e15b0208c3b782ebebfc to your computer and use it in GitHub Desktop.
A script to batch export all Boostnote file to markdown file without broken linked images
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# coding: utf-8 | |
""" | |
Author : noklam | |
Created Time : 2019-11-09 12:30:04 | |
Base on: https://gist.github.com/weaming/32b7b62956b304ff66fbae396ca9c86b | |
Prerequisite: | |
python3 -m pip install cson arrow | |
""" | |
import json | |
import os | |
import sys | |
import datetime | |
import cson | |
import re | |
import shutil | |
try: | |
import arrow | |
time_aware = True | |
except ImportError: | |
print( | |
'warning: datetime information will be discarded unless you install arrow' | |
) | |
time_aware = False | |
def read_file(fp): | |
with open(fp) as f: | |
return f.read() | |
def text_to_dict(text): | |
"""convert json or cson to dict""" | |
if not text: | |
return '' | |
try: | |
return cson.loads(text) | |
except: | |
pass | |
try: | |
return json.loads(text) | |
except: | |
pass | |
raise Exception("text should be in cson or json format") | |
def read_folder_names(fp): | |
data = text_to_dict(read_file(fp)) | |
return {x['key']: x['name'] for x in data['folders']} | |
def write_boostnote_markdown(data, output, folder_map): | |
"""write boostnote dict to markdown""" | |
if not data: | |
print('skipped, empty notes') | |
return | |
target_dir = os.path.join(output, folder_map[data['folder']]) | |
if not os.path.exists(target_dir): | |
os.makedirs(target_dir) | |
target_file = os.path.join(target_dir, '{}.md'.format(data['title'].replace('/', '-'))) | |
with open(target_file, 'w') as f: | |
# print(data) | |
content = data.get('content','') | |
content = content.replace(':storage', '../attachments') # Quick Hack | |
content = content.replace('\\', '/') # convert to backslash | |
f.write(content) | |
print(target_file) | |
if time_aware: | |
update_at = arrow.get(data['updatedAt']) | |
update_at_epoch = int(update_at.strftime('%s')) | |
os.utime(target_file, (update_at_epoch, update_at_epoch)) | |
stat = os.stat(target_file) | |
def process_file(source, output, folder_map): | |
print(f'Processing: {source}') | |
data = text_to_dict(read_file(source)) | |
write_boostnote_markdown(data, output, folder_map) | |
def main(boostnote_dir, output): | |
""" | |
:input: input folder path | |
:output: output folder path | |
""" | |
folder_map = read_folder_names(os.path.join(boostnote_dir, 'boostnote.json')) | |
notes_dir = os.path.join(boostnote_dir, 'notes') | |
for name in os.listdir(notes_dir): | |
if not name.endswith('.cson'): | |
continue | |
source = os.path.join(notes_dir, name) | |
process_file(source, output, folder_map) | |
# Copy Attachments | |
attachments_folder = os.path.join(boostnote_dir,'attachments') | |
output_attachments_folder = os.path.join(output, 'attachments') | |
notes_folders = os.listdir(attachments_folder) # Attachments folder | |
for folder in notes_folders: | |
sub_folder_name = os.path.join(attachments_folder, folder) | |
print('sub_folder', sub_folder_name) | |
files = os.listdir(sub_folder_name) | |
for file in files: | |
file_name = os.path.join(folder, file) | |
full_file_name = os.path.join(attachments_folder, file_name) | |
if os.path.isfile(full_file_name): | |
dest = os.path.join(output_attachments_folder, file_name) | |
os.makedirs(os.path.dirname(dest), exist_ok=True) # Created nested folder structure | |
shutil.copy(full_file_name, dest) | |
print('Copy files from {}, to {}'.format(full_file_name, dest)) | |
print('Output folder in {}'.format(output)) | |
print('Attachments are copied to the same folder') | |
if __name__ == '__main__': | |
import argparse | |
parser = argparse.ArgumentParser( | |
description="convert boostnote cson format data to markdown") | |
parser.add_argument( | |
'-s', | |
'--source', | |
type=str, | |
help="directory store the cson files", | |
default=".") | |
parser.add_argument( | |
'-o', '--output', type=str, help="output directory", default="output") | |
args = parser.parse_args() | |
main(args.source, args.output) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment