Skip to content

Instantly share code, notes, and snippets.

@noklam
Last active November 10, 2019 08:29
Show Gist options
  • Save noklam/08d7afd25dd6e15b0208c3b782ebebfc to your computer and use it in GitHub Desktop.
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
#!/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