我的笔记工作流

发布于 2020-02-10 11:25:01

步骤

  1. boostnote 创建、编辑、更新、整理、搜索笔记
  2. 用 Dropbox 在多台机器同步笔记
  3. 用脚本将 boostnote 的 cson 格式笔记内容转换成 markdown 格式
  4. 用脚本生成 markdown 文件的目录文件 README.md
  5. docsify 展示 markdown 格式的笔记
  6. 用 nginx 提供静态文件内容服务,可以通过 https://note.drink.cafe 访问我的笔记

scripts (updated at 2020-02-10)

file: generate-readme-for-docsify

#!/usr/bin/env python3
# coding: utf-8
"""
Author       : weaming
Created Time : 2018-05-04 09:53:22
Requirement  :
    python2.x
    pip install filetree
Description  :
    generate README.md files to prepare for https://github.com/QingWei-Li/docsify
"""

import os, sys
from filetree import File

target = sys.argv[1] if len(sys.argv) > 1 else '.'

def is_markdown_article(x):
    return x.ext == 'md' and x.name.lower() != 'readme' and x.basename[0] != '.'


def conditional_title(text):
    if text[0] in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
        return text
    return text.title()


def gen(d, h2_prefix=''):
    fp = os.path.join(os.path.abspath(d.path), 'README.md')
    # print(dir(d))

    # gen recursively
    for dd in sorted(d.dirs, key=lambda x: x.mtime, reverse=True):
        if dd.basename[0] == '.':
            continue
        gen(dd, h2_prefix=os.path.join(h2_prefix, dd.basename))

    if os.path.isfile(fp):
        print('existed {}'.format(fp))
        return

    print(fp)
    with open(fp, 'w') as me:
        def genDir(d, prefix="", pre_hook=None):
            """
            scan markdown files,
            and write entries in README.md
            """
            mds = [x for x in d.files if is_markdown_article(x)]
            if pre_hook and mds:
                pre_hook()

            for f in sorted(mds, key=lambda x: x.mtime, reverse=True):
                relpath = os.path.relpath(d.path, target)
                me.write('{}- [{}]({})\n'.format(
                    prefix,
                    conditional_title(f.name.replace('-', ' ').replace('_', ' ')),
                    os.path.join('' if relpath == '.' else relpath, f.relpath),
                ))


        # use direcotry name as h1 title
        me.write('# {}\n'.format(conditional_title(d.basename)))

        # generate for next level sub directories
        for dd in sorted(d.dirs, key=lambda x: x.mtime, reverse=True):
            if dd.basename[0] == '.':
                continue

            me.write('\n## [{}]({}{}/)\n\n'.format(
                conditional_title(dd.basename),
                (h2_prefix + '/') if h2_prefix else '',
                dd.basename,
            ))
            genDir(dd, prefix="")

        genDir(d, pre_hook=lambda: me.write('\n## Articles\n'))


gen(File(target))

file: boostnote2md

#!/usr/bin/env python3
# coding: utf-8
"""
Author       : weaming
Created Time : 2018-05-26 21:32:59
Prerequisite:
    python3 -m pip install cson arrow
"""
import json
import os
import sys
import datetime
import cson

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"""
    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 get_reverse_in():
    exclude = os.getenv("BOOSTNOTE_EXCLUDE", "")
    excludes = exclude.split(",") if exclude else []

    def _in(title):
        for e in excludes:
            if e in title.lower():
                return True
        return False

    return _in


contains_keyswords = get_reverse_in()


def red(text):
    return "\033[{}m{}\033[0m".format(31, text)


def write_boostnote_markdown(data, output, folder_map):
    """write boostnote dict to markdown"""
    title = data["title"]
    folder = folder_map[data["folder"]]

    # ignore blank title
    if not title.strip():
        print(red("warning: ignore article with blank title"))
        return

    # ignore article whose title contains keyword
    if contains_keyswords(title):
        print(red("warning: ignore article with title: {}".format(title)))
        return

    # ignore article whose folder contains keyword
    if contains_keyswords(folder):
        print(red("warning: ignore article in folder: {}".format(folder)))
        return

    # ignore trashed article
    if data["isTrashed"]:
        print(red("warning: ignore trashed article: {}".format(title)))
        return

    target_dir = os.path.join(output, folder)
    if not os.path.exists(target_dir):
        os.makedirs(target_dir)

    target_file = os.path.join(
        target_dir, "{}.md".format(title.replace("/", "-").replace(" ", "-"))
    )
    with open(target_file, "w") as f:
        f.write(data["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):
    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)


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)