Just-what-you-need blogging with bari

Created: by Pradeep Gowda Updated: Sep 07, 2022

Update 1: attention conservation notice: This site is NOT using bari anymore.

Update 2: I’ve added the code below for reference.

I write this blog using a combination of pandoc, make and a small-ish python script called bari.

It is easy for me to design, write and maintain my own code than inherit someone else’s assumptions and “wants” for their software.

I saw another blog https://blog.notryan.com/ that is also written in a similar spirit – uses only C, and a bash script to generate a “text only” blog and RSS feed.

So, go on, write your own little program(s) to do what you need to get writing. Some of it is yak-shaving; but some yaks are small, some of you are expert shavers. You will figure it out.


Putting the code below for posteirity:

new - a new draft

#!/usr/bin/env python3
"""new draft"""

import time
import subprocess
from pathlib import Path
import sys
import glob

suf = None
if len(sys.argv) > 1:
    suf = sys.argv[1]
today = time.strftime('%Y-%m-%d')
mdl = len(glob.glob('%s*.md' % (today, )))
txtl = len(glob.glob('%s*.txt' % (today, )))
# print(f'{mdl},  {txtl}')
cnt = len(glob.glob('%s*.md' % (today, ))) + len(glob.glob('%s*.txt' % (today, ))) + 1
if suf:
    fname = '%s-%s-%s.txt' % (today, cnt, suf)
else:
    fname = '%s-%s.txt' % (today, cnt)
print(f'New draft file: {fname}')
p = Path(fname)
p.touch()
subprocess.call(['bbedit', fname])

bari - publish the whole site

#!/usr/bin/env python3
from pathlib import Path
import glob
import os
import subprocess
import sys
import time
import uuid


class Bari(object):
    def __init__(self):
        self.SHOW_ON_HOME_PAGE = 5
        self.allmd = sorted(glob.glob('2*.md'))
        if len(self.allmd) > self.SHOW_ON_HOME_PAGE:
            self.top = self.SHOW_ON_HOME_PAGE
        else:
            self.top = len(self.allmd)

    def get_tags(self):
        tags = {}
        for md in self.allmd:
            with open(md, 'r') as f:
                lines = f.readlines()
                title = lines[0].strip()
                date = md[:10]
                for line in lines:
                    if line.startswith('◊'):
                        doctags = [
                            tag.lstrip('◊').strip() for tag in line.split(' ')
                        ]
                        for dt in doctags:
                            link = md.replace('.md', '.html')
                            meta = {'title': title, 'date': date, 'link': link}
                            if dt in tags.keys():
                                tags[dt].append(meta)
                            else:
                                tags[dt] = [meta]
        return tags

        with open('index.md', 'a') as f:
            f.write('\n## Tags\n')
            f.write('<dl>')
            for k in sorted(tags.keys()):
                f.write(
                    '<dt><span id="{k}" class="tagged">{k}</span></dt>'.format(
                        k=k))
                tsoup = ''.join([
                    '<dd><p><a href="{link}">{title}</a>\
                    <date style="float:right;">{date}</date></p></dd> '
                    .format(**meta) for meta in tags[k]
                ])
                f.write(tsoup)
            f.write('</dl>\n\n')

    def gen_home(self):
        noteno = len(self.allmd)
        selected = self.allmd[-self.top:]
        with open('index.md', 'w') as f:
            # write the top n posts
            for post in reversed(selected):
                with open(post, 'r') as fn:
                    date = post[:10]
                    link = post.replace('.md', '.html')
                    lines = fn.readlines()
                    title = lines[0]
                    f.write(f'<div class="post"><div class="post-title"><span id="{noteno}">°{noteno} // <a href="{link}">{title}</a></span> ')
                    f.write(f'<span class="post-date"><date>{date}</date></span></div>\n')
                    # f.write(f'')
                    for line in self.proc_file(post):
                        f.write(line)
                    f.write('</div>\n')
                    noteno -= 1
            # write the archive part
            years = list(
                reversed(sorted(list(set([md[:4] for md in self.allmd])))))
            if len(self.allmd) <= self.SHOW_ON_HOME_PAGE:
                return
            f.write('\n## Archive\n(Reverse chronologic)')
            f.write('<dl id="archive-links">')
            for post in reversed(self.allmd[:-self.SHOW_ON_HOME_PAGE]):
                date = post[:10]
                with open(post, 'r') as fn:
                    lines = fn.readlines()
                    title = lines[0]
                    link = post.replace('.md', '.html')
                    year = post[:4]
                    if year in years:
                        f.write(f'<dt class="archive-year" id="y{year}">{year}</dt>\n')
                        years.remove(year)
                    f.write(f'<dd class="archive-item"><p><span id="{noteno}">°{noteno}. <a href="{link}">{title}</a></span>\
                        <date>{date}</date></p></dd>')
                    noteno -= 1
            f.write('</dl>')
            # write the tags
            tags = self.get_tags()
            f.write('\n## Tags\n(Chronologic)\n')
            f.write('<dl>')
            for k in sorted(tags.keys()):
                f.write(
                    '<dt><span id="{k}" class="tagged">{k}</span></dt>'.format(
                        k=k))
                tsoup = ''.join([
                    '<dd><p><a href="{link}">{title}</a>\
                    <date style="float:right;">{date}</date></p></dd> '
                    .format(**meta) for meta in tags[k]
                ])
                f.write(tsoup)
            f.write('</dl>\n\n')

    def gen_atom_feed(self):
        feed_templ = '''<?xml version="1.0" encoding="utf-8"?>
            <feed xmlns="http://www.w3.org/2005/Atom">
            <title>btbytes.github.io feed</title>
            <link href="https://btbytes.github.io/"/>
            <updated>{feed_updated}</updated>
            <author>
            <name>Pradeep Gowda</name>
            </author>
            <id>https://btbytes.github.io/</id>
                {entries}
             </feed>'''
        entry_templ = '''<entry>
            <title>{title}</title>
            <link href="{url}"/>
            <id>{url}</id>
            <updated>{updated}</updated>
            <summary>{summary}</summary>
            </entry>
        '''
        if len(self.allmd) > self.SHOW_ON_HOME_PAGE:
            selected = self.allmd[-self.top:]
        with open('atom.xml', 'w') as of:
            entries = ''
            cnt = 0
            for post in reversed(selected):
                updated = post[:10] + 'T00:00:00.00Z'
                if cnt == 0:
                    feed_updated = updated
                cnt += 1
                with open(post, 'r') as fn:
                    lines = fn.readlines()
                    title = lines[0].strip()
                    link = post.replace('.md', '.html')
                    url = f'https://btbytes.github.io/{link}'
                    uid = str(uuid.uuid4())
                entry = entry_templ.format(
                    title=title,
                    url=url,
                    uid=uid,
                    updated=updated,
                    summary=title)
                entries += entry
            content = feed_templ.format(
                feed_updated=feed_updated, entries=entries)
            of.write(content)

    def process_file(self, args):
        print(''.join(self.proc_file(args.fpath)))

    def proc_file(self, fpath):
        compiled = []
        pcount = 0
        # 2020-05-01-01-interest-calculation.md -> 20050101
        docid = fpath.split('/')[-1][0:13].replace('-', '')[2:]
        with open(fpath) as f:
            lines = f.readlines()
            for line in lines[1:]:
                if line.startswith('◊'):  # tags
                    doctags = [
                        tag.lstrip('◊').strip() for tag in line.split(' ')
                    ]
                    compiled.append(' '.join([
                        '<a href="index.html#{tag}" class="tag {tag}">{tag}\
                        </a> '.format(tag=tag) for tag in doctags
                    ]))
                    compiled.append('\n')
                elif line.startswith('¶'):  # new part
                    line = line.replace('¶', '')
                    compiled.append(f'<a id="{docid}{pcount}"></a>{line} \
                        <a href="#{docid}{pcount}">¶</a>\
                        <div class="part"></div>')
                    pcount += 1
                else:
                    compiled.append(line)
        return compiled

    def newdraft(self, args):
        """XXX: Fix"""
        print('Create a draft')
        suf = None
        if len(sys.argv) > 1:
            suf = sys.argv[1]
        today = time.strftime('%Y-%m-%d')
        cnt = len(glob.glob('%s*.md' % (today, ))) + len(
            glob.glob('%s*.txt' % (today, ))) + 1
        if suf:
            fname = '%s-%s-%s.txt' % (today, cnt, suf)
        else:
            fname = '%s-%s.txt' % (today, cnt)
        print(f'New draft file: {fname}')
        p = Path(fname)
        p.touch()
        subprocess.call(['bbedit', fname])

    def edit(self, args):
        print('Edit a draft')

        def prompt(zd):
            daft = None
            while not daft:
                for k, v in zd.items():
                    title = v[13:-4].replace('-', ' ').title()
                    print(f'{k}. {title}')
                print()
                i = input('Which draft to edit? ')
                i = int(i)
                if i in zd.keys():
                    return zd[i]
                elif i == 0:
                    sys.exit(0)

        drafts = sorted(glob.glob('*.txt'))
        if len(drafts) == 0:
            print('No drafts to edit.')

        elif len(drafts) == 1:
            subprocess.call(['bbedit', drafts[0]])
        else:
            zd = zip(range(1, len(drafts) + 1), drafts)
            subprocess.call(['bbedit', prompt(dict(zd))])

    def publish(self, args):
        print('Publish a draft')

    def build(self, args):
        self.gen_home()
        self.gen_atom_feed()


if __name__ == '__main__':
    import argparse
    bari = Bari()
    parser = argparse.ArgumentParser(prog='bari')
    subparsers = parser.add_subparsers(help='sub-command help')
    parser_build = subparsers.add_parser('new', help='new draft')
    parser_build.set_defaults(func=bari.newdraft)
    parser_build = subparsers.add_parser('edit', help='edit a draft')
    parser_build.set_defaults(func=bari.edit)
    parser_build = subparsers.add_parser('build', help='build the site')
    parser_build.set_defaults(func=bari.build)
    parser_process = subparsers.add_parser('process', help='process a file')
    parser_process.add_argument('fpath')
    parser_process.set_defaults(func=bari.process_file)
    args = parser.parse_args()
    args.func(args)

edit

#!/usr/bin/env python
"""edit a draft"""

import glob
import os
import subprocess
import sys


def prompt(zd):
    daft = None
    while not daft:
        for k, v in zd.items():
            title = v[13:-4].replace('-', ' ').title()
            print(f'{k}. {title}')
        print()
        i = input('Which draft to edit? ')
        i = int(i)
        if i in zd.keys():
            return zd[i]
        elif i == 0:
            sys.exit(0)


def main():
    drafts = sorted(glob.glob('*.txt'))
    if len(drafts) == 0:
        print('No drafts to edit.')

    elif len(drafts) == 1:
        subprocess.call(['bbedit', drafts[0]])
    else:
        zd = zip(range(1, len(drafts) + 1), drafts)
        subprocess.call(['bbedit', prompt(dict(zd))])


if __name__ == '__main__':
    main()

lish - publish a draft

#!/usr/bin/env python
"""publish a draft"""

import os
import sys
import glob
import time


def dst_name(src, dt=time.strftime('%Y-%m-%d')):
    """return the new filename with today's date"""
    today = time.strftime('%Y-%m-%d')
    dst = src.replace('.txt', '.md')
    dst = today + dst[10:]
    return dst


def pubfile(src):
    dst = dst_name(src)
    if os.path.exists(dst):
        print(f'{dst} already exists.')
        sys.exist(0)
    print(f'publishing {src} -> {dst}')
    os.rename(src, dst)


def prompt(zd):
    daft = None
    while not daft:
        for k, v in zd.items():
            print(f'{k}. {v}')
        i = input('Which draft to publish? ')
        i = int(i)
        if i in zd.keys():
            return zd[i]


def main():
    drafts = glob.glob('*.txt')
    if len(drafts) == 0:
        print('No drafts to publish.')

    elif len(drafts) == 1:
        pubfile(drafts[0])
    else:
        zd = zip(range(1, len(drafts) + 1), drafts)
        pubfile(prompt(dict(zd)))


if __name__ == '__main__':
    # print(dst_name(src='2019-01-01-01-hello.txt', dt='2020-04-01'))
    main()

push - push to github

#!/usr/bin/env bash

make -j4
git add *.md
git add *.html
git commit -am "A new build"
git push origin