#!/usr/bin/python
# Copyright 2010-2013 Lars Wirzenius
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
import cliapp
import optparse
import os
import re
import shutil
import subprocess
import sys
import tempfile
import time
import traceback
__version__ = '0.0'
template = '''\
[[!meta title="%(title)s"]]
[[!tag ]]
[[!meta date="%(date)s"]]
'''
class JournalTool(cliapp.Application):
cmd_synopsis = {
'attach': 'DRAFT-ID [FILE]...',
'edit': '[DRAFT-ID]',
'finish': '[DRAFT-ID]',
'list': '',
'new': 'TITLE',
'remove': 'DRAFT-ID',
}
def add_settings(self):
self.settings.string(
['source'],
'use journal source tree in DIR',
metavar='DIR')
self.settings.choice(
['layout'],
['ct', 'liw'],
'use journal layout (one of liw, ct)',
metavar='LAYOUT')
self.settings.boolean(
['git'],
'add entries to git automatically',
default=True)
self.settings.string(
['editor'],
'editor to launch for journal entries. Must include %s to '
'indicate where the filename goes',
default='sensible-editor %s')
self.settings.boolean(
['push'],
'push finished articles with git?')
def cmd_new(self, args):
'''Create a new journal entry draft.'''
if not args:
raise cliapp.AppException('Usage: journal-note new TITLE')
self.settings.require('source')
self.settings.require('layout')
if not self.settings['source']:
raise cliapp.AppException(
'The --source setting is empty or missing.')
if not os.path.exists(self.drafts_dir()):
os.mkdir(self.drafts_dir())
for i in range(1000):
name = self.draft_name(i)
if not os.path.exists(name):
break
else:
raise cliapp.AppException('ERROR: too many existing drafts')
values = {
'title': args[0],
'date': time.strftime('%Y-%m-%d %H:%M')
}
f = open(name, 'w')
f.write(template % values)
f.close()
self.edit_file(name)
def drafts_dir(self):
return os.path.join(self.settings['source'], 'drafts')
def draft_name(self, draft_id):
return os.path.join(self.drafts_dir(), '%s.mdwn' % draft_id)
def published_dir(self):
subdirs = {
'liw': 'notes',
'ct': 'log/%d' % time.localtime().tm_year,
}
subdir = subdirs[self.settings['layout']]
return os.path.join(self.settings['source'], subdir)
def edit_file(self, pathname):
safe_pathname = cliapp.shell_quote(pathname)
cmdline = ['sh', '-c', self.settings['editor'] % pathname]
self.runcmd(cmdline, stdin=None, stdout=None, stderr=None)
def cmd_list(self, args):
'''List journal entry drafts.'''
for draft_id, filename in self.find_drafts():
with open(filename) as f:
for line in f:
m = re.match(
'\[\[!meta title="(?P
.*)("\]\])$',
line)
if m:
title = m.group('title')
break
else:
title = 'unknown title'
print draft_id, title
def find_drafts(self):
drafts_dir = self.drafts_dir()
for name in os.listdir(drafts_dir):
# .# is what Emacs autosave files start with.
if name.endswith('.mdwn') and not name.startswith('.#'):
yield name[:-len('.mdwn')], os.path.join(drafts_dir, name)
def cmd_edit(self, args):
'''Edit a draft journal entry.'''
filename = self.choose_draft(args)
self.edit_file(filename)
def choose_draft(self, args):
if len(args) == 0:
drafts = list(self.find_drafts())
if len(drafts) == 1:
draft_id, filename = drafts[0]
return filename
else:
raise cliapp.AppException('Cannot choose entry draft automatically')
elif len(args) == 1:
filename = self.draft_name(args[0])
if not os.path.exists(filename):
raise cliapp.AppException('draft %s does not exist' % args[0])
return filename
elif len(args) > 1:
raise cliapp.AppException('Must give only one draft number')
def cmd_attach(self, args):
'''Attach files to a journal entry draft.'''
if len(args) < 2:
raise cliapp.AppException('Usage: journal-note attach ID file...')
filename = self.draft_name(args[0])
dirname, ext = os.path.splitext(filename)
assert ext == '.mdwn'
if not os.path.exists(dirname):
os.mkdir(dirname)
for filename in args[1:]:
shutil.copy(filename, dirname)
def cmd_remove(self, args):
'''Remove a draft.'''
if not args:
raise cliapp.AppException('Usage: journal-note remove ID')
filename = self.draft_name(args[0])
os.remove(filename)
dirname, ext = os.path.splitext(filename)
assert ext == '.mdwn'
if os.path.exists(dirname):
shutil.rmtree(dirname)
def cmd_finish(self, args):
'''Publish a draft journal entry.'''
draft_mdwn = self.choose_draft(args)
draft_attch, ext = os.path.splitext(draft_mdwn)
assert ext == '.mdwn'
basename = time.strftime('%Y-%m-%d-%H:%M:%S')
pub_mdwn = os.path.join(self.published_dir(), '%s.mdwn' % basename)
pub_attch = os.path.join(self.published_dir(), basename)
if os.path.exists(pub_mdwn):
raise cliapp.AppException('%s already exists' % pub_mdwn)
if not os.path.exists(self.published_dir()):
os.makedirs(self.published_dir())
os.rename(draft_mdwn, pub_mdwn)
if os.path.exists(draft_attch):
os.rename(draft_attch, pub_attch)
if self.settings['git']:
argv = ['git', 'add', pub_mdwn]
if os.path.exists(pub_attch):
argv.append(pub_attch)
cliapp.runcmd(argv, cwd=self.settings['source'])
cliapp.runcmd(
['git', 'commit', '-m', 'Publish log entry'],
cwd=self.settings['source'])
if self.settings['push']:
cliapp.runcmd(
['git', 'push', 'origin', 'HEAD'],
cwd=self.settings['source'])
def cmd_new_person(self, args):
'''Create a page to list all notes referring to a person.
This is probably only useful to Lars's personal journal.
'''
if len(args) != 1:
raise cliapp.AppException(
'Need the name of a person (in Last, First form)')
def normalise(name):
s = name.lower()
s = ' '.join(s.split(','))
s = '.'.join(s.split())
return s
name = args[0]
basename = normalise(name)
pathname = os.path.join(
self.settings['source'], 'people', basename + '.mdwn')
if os.path.exists(pathname):
raise cliapp.AppException('File %s already exists' % pathname)
with open(pathname, 'w') as f:
f.write('''\
[[!meta title="%(name)s"]]
[[!inline archive=yes pages="link(people/%(basename)s)"]]
''' %
{
'name': name,
'basename': basename,
})
JournalTool(version=__version__).run()