mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-10 16:48:31 +02:00
commit
61835d88ec
4 changed files with 80 additions and 8 deletions
|
@ -5,6 +5,7 @@ Changelog
|
||||||
|
|
||||||
* [Fixed] A bug where jrnl would not go into compose mode
|
* [Fixed] A bug where jrnl would not go into compose mode
|
||||||
* [Improved] Each journal can have individual settings
|
* [Improved] Each journal can have individual settings
|
||||||
|
* [New] Integrates seamlessly with DayOne
|
||||||
|
|
||||||
### 0.3.2 (July 5, 2012)
|
### 0.3.2 (July 5, 2012)
|
||||||
|
|
||||||
|
|
16
README.md
16
README.md
|
@ -1,7 +1,9 @@
|
||||||
jrnl
|
jrnl
|
||||||
====
|
====
|
||||||
|
|
||||||
*jrnl* is a simple journal application for your command line. Journals are stored as human readable plain text files - you can put them into a Dropbox folder for instant syncinc and you can be assured that your journal will still be readable in 2050, when all your fancy iPad journal applications will long be forgotten.
|
*jrnl* is a simple journal application for your command line. Journals are stored as human readable plain text files - you can put them into a Dropbox folder for instant syncing and you can be assured that your journal will still be readable in 2050, when all your fancy iPad journal applications will long be forgotten.
|
||||||
|
|
||||||
|
*jrnl* also plays nice with the fabulous [DayOne](http://dayoneapp.com/) and can read and write directly from and to DayOne Journals.
|
||||||
|
|
||||||
Optionally, your journal can be encrypted using the [256-bit AES](http://en.wikipedia.org/wiki/Advanced_Encryption_Standard).
|
Optionally, your journal can be encrypted using the [256-bit AES](http://en.wikipedia.org/wiki/Advanced_Encryption_Standard).
|
||||||
|
|
||||||
|
@ -154,6 +156,18 @@ The configuration file is a simple JSON file with the following options.
|
||||||
>
|
>
|
||||||
> Or use the built-in prompt or an external editor to compose your entries.
|
> Or use the built-in prompt or an external editor to compose your entries.
|
||||||
|
|
||||||
|
### DayOne Integration
|
||||||
|
|
||||||
|
Using your DayOne journal instead of a flat text file is dead simple - instead of pointing to a text file, set the `"journal"` key in your `.jrnl_conf` to point to your DayOne journal. This is a folder ending with `.dayone`, and it's located at
|
||||||
|
|
||||||
|
* `~/Library/Application Support/Day One/` by default
|
||||||
|
* `~/Dropbox/Apps/Day One/` if you're syncing with Dropbox and
|
||||||
|
* `~/Library/Mobile Documents/5U8NS4GX82~com~dayoneapp~dayone/Documents/` if you're syncing with iCloud.
|
||||||
|
|
||||||
|
Instead of all entries being in a single file, each entry will live in a separate `plist` file. You can also star entries when you write them:
|
||||||
|
|
||||||
|
jrnl -star yesterday: Lunch with @Arthur
|
||||||
|
|
||||||
### Multiple journal files
|
### Multiple journal files
|
||||||
|
|
||||||
You can configure _jrnl_ to use with multiple journals (eg. `private` and `work`) by defining more journals in your `.jrnl_config`, for example:
|
You can configure _jrnl_ to use with multiple journals (eg. `private` and `work`) by defining more journals in your `.jrnl_config`, for example:
|
||||||
|
|
|
@ -22,8 +22,10 @@ try:
|
||||||
import clint
|
import clint
|
||||||
except ImportError:
|
except ImportError:
|
||||||
clint = None
|
clint = None
|
||||||
|
import plistlib
|
||||||
|
import uuid
|
||||||
|
|
||||||
class Journal:
|
class Journal(object):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.config = {
|
self.config = {
|
||||||
'journal': "journal.txt",
|
'journal': "journal.txt",
|
||||||
|
@ -242,7 +244,7 @@ class Journal:
|
||||||
|
|
||||||
return date
|
return date
|
||||||
|
|
||||||
def new_entry(self, raw, date=None):
|
def new_entry(self, raw, date=None, sort=True):
|
||||||
"""Constructs a new entry from some raw text input.
|
"""Constructs a new entry from some raw text input.
|
||||||
If a date is given, it will parse and use this, otherwise scan for a date in the input first."""
|
If a date is given, it will parse and use this, otherwise scan for a date in the input first."""
|
||||||
|
|
||||||
|
@ -264,5 +266,53 @@ class Journal:
|
||||||
if not date: # Still nothing? Meh, just live in the moment.
|
if not date: # Still nothing? Meh, just live in the moment.
|
||||||
date = self.parse_date("now")
|
date = self.parse_date("now")
|
||||||
|
|
||||||
self.entries.append(Entry(self, date, title, body))
|
entry = Entry(self, date, title, body)
|
||||||
self.sort()
|
self.entries.append(entry)
|
||||||
|
if sort:
|
||||||
|
self.sort()
|
||||||
|
return entry
|
||||||
|
|
||||||
|
class DayOne(Journal):
|
||||||
|
"""A special Journal handling DayOne files"""
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.entries = []
|
||||||
|
super(DayOne, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
files = [os.path.join(self.config['journal'], "entries", f) for f in os.listdir(os.path.join(self.config['journal'], "entries"))]
|
||||||
|
return files
|
||||||
|
|
||||||
|
def parse(self, filenames):
|
||||||
|
"""Instead of parsing a string into an entry, this method will take a list
|
||||||
|
of filenames, interpret each as a plist file and create a new entry from that."""
|
||||||
|
self.entries = []
|
||||||
|
for filename in filenames:
|
||||||
|
with open(filename) as plist_entry:
|
||||||
|
dict_entry = plistlib.readPlist(plist_entry)
|
||||||
|
entry = self.new_entry(raw=dict_entry['Entry Text'], date=dict_entry['Creation Date'], sort=False)
|
||||||
|
entry.starred = dict_entry["Starred"]
|
||||||
|
entry.uuid = dict_entry["UUID"]
|
||||||
|
# We're using new_entry to create the Entry object, which adds the entry
|
||||||
|
# to self.entries already. However, in the original Journal.__init__, this
|
||||||
|
# method is expected to return a list of newly created entries, which is why
|
||||||
|
# we're returning the obvious.
|
||||||
|
return self.entries
|
||||||
|
|
||||||
|
|
||||||
|
def write(self):
|
||||||
|
"""Writes only the entries that have been modified into plist files."""
|
||||||
|
for entry in self.entries:
|
||||||
|
# Assumption: since jrnl can not manipulate existing entries, all entries
|
||||||
|
# that have a uuid will be old ones, and only the one that doesn't will
|
||||||
|
# have a new one!
|
||||||
|
if not hasattr(entry, "uuid"):
|
||||||
|
new_uuid = uuid.uuid1().hex
|
||||||
|
filename = os.path.join(self.config['journal'], "entries", new_uuid+".doentry")
|
||||||
|
entry_plist = {
|
||||||
|
'Creation Date': entry.date,
|
||||||
|
'Starred': entry.starred if hasattr(entry, 'starred') else False,
|
||||||
|
'Entry Text': entry.title+"\n"+entry.body,
|
||||||
|
'UUID': new_uuid
|
||||||
|
}
|
||||||
|
plistlib.writePlist(entry_plist, filename)
|
||||||
|
|
||||||
|
|
13
jrnl/jrnl.py
13
jrnl/jrnl.py
|
@ -31,6 +31,7 @@ def parse_args():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
composing = parser.add_argument_group('Composing', 'Will make an entry out of whatever follows as arguments')
|
composing = parser.add_argument_group('Composing', 'Will make an entry out of whatever follows as arguments')
|
||||||
composing.add_argument('-date', dest='date', help='Date, e.g. "yesterday at 5pm"')
|
composing.add_argument('-date', dest='date', help='Date, e.g. "yesterday at 5pm"')
|
||||||
|
composing.add_argument('-star', dest='star', help='Stars an entry (DayOne journals only)', action="store_true")
|
||||||
composing.add_argument('text', metavar='text', nargs="*", help='Log entry (or tags by which to filter in viewing mode)')
|
composing.add_argument('text', metavar='text', nargs="*", help='Log entry (or tags by which to filter in viewing mode)')
|
||||||
|
|
||||||
reading = parser.add_argument_group('Reading', 'Specifying either of these parameters will display posts of your journal')
|
reading = parser.add_argument_group('Reading', 'Specifying either of these parameters will display posts of your journal')
|
||||||
|
@ -157,8 +158,13 @@ def cli():
|
||||||
touch_journal(config['journal'])
|
touch_journal(config['journal'])
|
||||||
mode_compose, mode_export = guess_mode(args, config)
|
mode_compose, mode_export = guess_mode(args, config)
|
||||||
|
|
||||||
# open journal file
|
# open journal file or folder
|
||||||
journal = Journal.Journal(**config)
|
if os.path.isdir(config['journal']) and config['journal'].endswith(".dayone"):
|
||||||
|
journal = Journal.DayOne(**config)
|
||||||
|
else:
|
||||||
|
journal = Journal.Journal(**config)
|
||||||
|
|
||||||
|
|
||||||
if mode_compose and not args.text:
|
if mode_compose and not args.text:
|
||||||
if config['editor']:
|
if config['editor']:
|
||||||
raw = get_text_from_editor(config)
|
raw = get_text_from_editor(config)
|
||||||
|
@ -172,7 +178,8 @@ def cli():
|
||||||
# Writing mode
|
# Writing mode
|
||||||
if mode_compose:
|
if mode_compose:
|
||||||
raw = " ".join(args.text).strip()
|
raw = " ".join(args.text).strip()
|
||||||
journal.new_entry(raw, args.date)
|
entry = journal.new_entry(raw, args.date)
|
||||||
|
entry.starred = args.star
|
||||||
print("[Entry added to {} journal]").format(journal_name)
|
print("[Entry added to {} journal]").format(journal_name)
|
||||||
journal.write()
|
journal.write()
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue