Compare commits

...

57 commits

Author SHA1 Message Date
Steven Loria
6eb0d3ba1c Remove sudo: from .travis.yml (#571)
TravisCI has migrated their infrastructure: https://blog.travis-ci.com/2018-11-19-required-linux-infrastructure-migration
2019-02-23 13:05:33 -08:00
Austin Scott
50da2c8d0e Added @hollystyles' fix for Issue #415 (#564)
Fixes the problem with permissions when trying to use an external editor to edit old entries on Windows.
2018-11-14 10:20:12 -08:00
cclauss
f04dd2bb0b Travis CI: Add Python 3.7 to testing (#561) 2018-11-08 10:14:18 -08:00
Austin Scott
c0a76b2929 Fixed typo (#563) 2018-11-08 10:13:51 -08:00
Steven Loria
6eb9e44bf5 Fix downloads badge (#553) 2018-07-31 09:41:50 -07:00
semi
cd9cf8d66a Avoid data loss by postponing open() (#502) (#545)
Postpone the reading of existing entries until a new entry
has been obtained and is ready to be appended to the journal.

This patch reduces the likelihood of losing entries when the
user has launched concurrent jrnl sessions to add multiple
entries. A proper solution would require locking the journal
file for the time between reading and writing, but this is
already good enough for typical interactive use cases.
2018-06-26 11:27:52 -07:00
Craig MacEachern
037784f783 Update advanced.rst (#527)
Update to clarify that jrnl creates .jrnl_config file under $XDG_CONFIG/jrnl/ folder (I mistakenly interpreted the original to mean the config file should be called $XDG_CONFIG/jrnl, and was super confused that jrnl did not read it).
2018-02-19 14:49:01 -08:00
Manuel Ebert
c0cbbf06e5 Copy update 2017-12-11 11:34:29 -08:00
Sylvain
588d0126f0 Add most recent Python versions in Travis CI (#474)
Add more recent Python versions including development branches and nightly build.
2017-03-09 12:47:52 -08:00
Manuel Ebert
58b473236d Docs CSS update 2017-03-09 12:42:08 -08:00
Manuel Ebert
7f5564c63c Explicit code blocks in docs 2017-03-09 12:40:38 -08:00
Manuel Ebert
7003b3d13a Updated docs from master 2017-03-09 12:09:05 -08:00
Paul Liu
ba34ba2e58 Update iA Writer bundle id (#464) 2017-02-12 21:03:42 -08:00
Manuel Ebert
a7b98f28e1 Update CONTRIBUTING.md 2017-01-04 12:02:53 -08:00
Radomír Bosák
4ba577db33 Fix failing behave tests (#447)
The keyring package broke backward compatibility in version 8.0 by
moving some keyring backends to another package, keyrings.alt, as
documented in the changelog [1].

This change broke behave tests which were trying to use
keyring.backends.file.PlaintextKeyring - no longer existing in keyring
package.

This commit adds the keyrings.alt package as dependency so that the
PlaintextKeyring class can be used.

[1] https://pythonhosted.org/keyring/history.html#id22
2017-01-04 10:24:03 -08:00
Andrew Sauber
b2ddd22e50 add fully functional decryption script to docs (#431) 2016-08-20 17:55:09 +00:00
doozr
cf6bc9c051 Do not crash if subdirs found inside Day One dir (#430) 2016-07-14 14:24:48 -07:00
Andrew Sauber
976a6faaa8 Minor Documentation Fixes (#425)
* Update installation.rst

* minor fixes to encryption.rst

instruct user to set `HISTIGNORE` while maintaining their existing `HISTIGNORE`
minor whitespace change
2016-05-17 10:35:56 -07:00
David Silva
54d36297a8 Change README url to point to actual documentation (#423) 2016-05-06 11:02:53 -07:00
Manuel Ebert
5dccceff78 Don't build 2.6 2015-12-28 21:58:08 -08:00
Manuel Ebert
7f8ebcbca6 Merge pull request #370 from vitorgalvao/patch-1
readme.md: homebrew instructions
2015-12-28 21:54:54 -08:00
Vítor Galvão
4a89f92f97 readme.md: homebrew instructions 2015-07-08 20:40:08 +01:00
Manuel Ebert
7c1d552a35 Merge pull request #325 from nikvdp/master
Correct issue #259, also fix issue with specifying DayOne journals in install
2015-01-11 08:50:24 -08:00
Manuel Ebert
f5f301c519 Merge pull request #310 from dedan/patch-2
fix small typo
2014-12-18 10:37:07 +07:00
Nik V
b99c82f9be Don't die if you specify a directory (DayOne journal) to install 2014-12-11 21:28:04 +08:00
Nik V
ef0227cf89 Fix bug with dayone journals on linux 2014-12-11 21:21:18 +08:00
Manuel Ebert
d0b476187f Merge pull request #321 from seinfield/master
corrects issue #316
2014-12-08 13:39:36 +07:00
seinfield
10235b77ce corrected typo 2014-12-07 03:00:41 -05:00
seinfield
3323db3e2a corrects issue #316 2014-12-07 02:55:49 -05:00
Manuel Ebert
270157010c Icon update! 2014-11-29 18:36:42 +07:00
Stephan Gabler
12d34667fd fix small typo 2014-11-19 14:26:13 +01:00
Manuel Ebert
79eb1bb757 Merge pull request #309 from maebert/tmpfile-fix
Fixes #308
2014-11-17 15:46:45 +01:00
Manuel Ebert
44c74d509c Fixes #308 2014-11-17 15:36:43 +01:00
Manuel Ebert
d216dcdbd2 Merge pull request #305 from pcarranza/debug-flag
Added basic logging feature to understand how is configuration loaded
2014-11-09 10:51:03 +01:00
Pablo Carranza
75eaf275b4 Added exception info to the output when it cannot be loaded 2014-11-06 19:15:11 -05:00
Pablo Carranza
a43cf16395 Added basic logging feature to understand how is configuration loaded 2014-11-06 15:36:03 -05:00
Manuel Ebert
73d9882b64 Travis update 2014-11-06 12:38:57 +01:00
Manuel Ebert
e77037b0f1 github_release plug on setup.py 2014-11-06 12:35:31 +01:00
Manuel Ebert
b45e52f743 Merge pull request #304 from maebert/unicode-fixes
Unicode fixes
2014-11-06 11:33:36 +01:00
Manuel Ebert
474bf0a71a version bump 2014-11-06 11:30:15 +01:00
Manuel Ebert
799ff762b2 Fix for writing non-unicode entries on prompt 2014-11-06 11:30:11 +01:00
Manuel Ebert
43f1166ecf Tests for writing non-unicode entries on prompt 2014-11-06 11:30:06 +01:00
Manuel Ebert
acf41a153a Timezone parsing fix 2014-10-21 18:28:03 +02:00
Manuel Ebert
19c57ecf70 Open journal before writing 2014-10-21 18:28:03 +02:00
Manuel Ebert
94b53b9247 Safer temp file creation 2014-10-21 18:28:03 +02:00
Manuel Ebert
9603f7d3ad Merge pull request #289 from willbarrett/bug/journal-not-found
Handle situation where a specified jounal is not found.
2014-10-20 11:47:55 -07:00
Manuel Ebert
cde55ad540 Merge pull request #294 from jfunction/master
Fixed a bug. --edit now shows [1 entry modified] not [1 entries modified]
2014-10-16 15:22:54 -07:00
jfunction
1013d77173 Fixed a bug whereby editing a single entry resulted in the plural form being used. Now editing one entry results in "[1 entry modified]" 2014-10-15 14:23:03 +02:00
Will Barrett
76881f66e9 Handle situation where a specified jounal is not found. 2014-10-06 16:45:25 -04:00
Manuel Ebert
e6cfeabc17 Installation instructions for homebrew 2014-10-02 10:28:39 -07:00
Manuel Ebert
17b439eba4 version bump 2014-09-30 10:17:07 -07:00
Manuel Ebert
c8e5d1ff34 util fixes 2014-09-30 10:16:50 -07:00
Manuel Ebert
bd6edffc81 Merge pull request #283 from pcarranza/ontoday
Fixed "-on today" option inclusive date parsing
2014-09-25 12:35:17 -07:00
Pablo Carranza
e9f691e399 Fixed -on today option parsing 2014-09-25 10:04:17 -04:00
Manuel Ebert
05e63dc76a Merge pull request #278 from pcarranza/master
Added how to ignore history appending for zsh
2014-09-22 12:16:16 -07:00
Pablo Carranza
c81f0e0c1d Added how to ignore history appending for zsh 2014-09-22 07:42:00 -04:00
Manuel Ebert
afcccb78c1 Fix docs typo 2014-09-17 08:35:39 -07:00
29 changed files with 360 additions and 116 deletions

View file

@ -1,12 +1,19 @@
language: python
python:
- "2.6"
- "2.7"
- "3.3"
- "3.4"
- "3.5"
- "3.6"
matrix:
include:
- python: "3.7"
dist: xenial # required for Python >= 3.7 (travis-ci/travis-ci#9069)
- python: "nightly"
dist: xenial # required for Python >= 3.7 (travis-ci/travis-ci#9069)
install:
- "pip install -e . --use-mirrors"
- "pip install pycrypto>=2.6 --use-mirrors"
- "pip install -e ."
- "pip install pycrypto>=2.6"
- "pip install -q behave"
# command to run tests
script:

View file

@ -4,6 +4,9 @@ Changelog
### 1.9 (July 21, 2014)
* __1.9.8__ Fixes a problem with temporary files on windows
* __1.9.7__ Fixes writing non-ascii entries on the prompt
* __1.9.6__ Fuzzy time parsing improvements (thanks to @pcarranza)
* __1.9.5__ Multi-word tags for DayOne Journals
* __1.9.4__ Fixed: Order of journal entries in file correct after --edit'ing
* __1.9.3__ Fixed: Tags at the beginning of lines

View file

@ -3,6 +3,9 @@ Contributing
If you use jrnl, you can totally make my day by just saying "thanks for the code" or by [tweeting about jrnl](https://twitter.com/intent/tweet?text=Write+your+memoirs+on+the+command+line.+Like+a+boss.+%23jrnl&url=http%3A%2F%2Fmaebert.github.io%2Fjrnl&via=maebert). It's your chance to make a programmer happy today! If you have a minute or two, let me know what you use jrnl for and how, it'll help me to make it even better. If you blog about jrnl, I'll send you a post card!
> # Important:
> ### Please develop new features against the `2.0-rc1` branch. PRs to the `master` branch will not get merged until version 2.0 is released.
Docs & Typos
------------
@ -12,7 +15,7 @@ If you find a typo or a mistake in the docs, just fix it right away and send a p
Bugs
----
They unfortunately happen. Specifically, I don't have a Windows machine to test on, so expect a few rough spots. If you found a bug, please [open a new issue](https://www.github.com/maebert/jrnl/issues/new) and describe it as well as possible. If you're a programmer and have a little time time spare, go ahead, fork the code and fix bugs you spot, it'll be much appreciated!
They unfortunately happen. Specifically, I don't have a Windows machine to test on, so expect a few rough spots. If you found a bug, please [open a new issue](https://www.github.com/maebert/jrnl/issues/new) and describe it as well as possible. If you're a programmer and have a little time to spare, go ahead, fork the code and fix bugs you spot, it'll be much appreciated!
Feature requests and ideas

View file

@ -1,3 +1,5 @@
.PHONY: clean docs
# A Makefile for commands I run frequently:
clean:

View file

@ -1,7 +1,7 @@
jrnl [![Build Status](http://img.shields.io/travis/maebert/jrnl.svg?style=flat)](https://travis-ci.org/maebert/jrnl) [![Downloads](http://img.shields.io/pypi/dm/jrnl.svg?style=flat)](https://pypi.python.org/pypi/jrnl/) [![Version](http://img.shields.io/pypi/v/jrnl.svg?style=flat)](https://pypi.python.org/pypi/jrnl/)
jrnl [![Build Status](http://img.shields.io/travis/maebert/jrnl.svg?style=flat)](https://travis-ci.org/maebert/jrnl) [![Downloads](https://pepy.tech/badge/jrnl)](https://pepy.tech/project/jrnl) [![Version](http://img.shields.io/pypi/v/jrnl.svg?style=flat)](https://pypi.python.org/pypi/jrnl/)
====
_For news on updates or to get help, [read the docs](http://maebert.github.io/jrnl), follow [@maebert](https://twitter.com/maebert) or [submit an issue](https://github.com/maebert/jrnl/issues/new) on Github._
_For news on updates or to get help, [read the docs](http://maebert.github.io/jrnl/overview.html), follow [@maebert](https://twitter.com/maebert) or [submit an issue](https://github.com/maebert/jrnl/issues/new) on Github._
*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.
@ -11,7 +11,7 @@ Optionally, your journal can be encrypted using the [256-bit AES](http://en.wiki
### Why keep a journal?
Journals aren't only for 13-year old girls and people who have too much time on their summer vacation. A journal helps you to keep track of the things you get done and how you did them. Your imagination may be limitless, but your memory isn't. For personal use, make it a good habit to write at least 20 words a day. Just to reflect what made this day special, why you haven't wasted it. For professional use, consider a text-based journal to be the perfect complement to your GTD todo list - a documentation of what and how you've done it.
Journals aren't just for angsty teenagers and people who have too much time on their summer vacation. A journal helps you to keep track of the things you get done and how you did them. Your imagination may be limitless, but your memory isn't. For personal use, make it a good habit to write at least 20 words a day. Just to reflect what made this day special, why you haven't wasted it. For professional use, consider a text-based journal to be the perfect complement to your GTD todo list - a documentation of what and how you've done it.
In a Nutshell
-------------
@ -39,3 +39,6 @@ Or, if you want the option to encrypt your journal,
pip install jrnl[encrypted]
Alternatively, on OS X with [Homebrew](http://brew.sh/) installed:
brew install jrnl

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -114,7 +114,7 @@ a:hover, a:active
background: desaturate(lighten(@terminal,10), 10);
pre {color: white;}
}
.highlight-python
.highlight-python, .highlight-sh
{
.terminal;
pre
@ -223,12 +223,12 @@ div.footer
{
body:not(.landing){
padding-top: 130px;
.highlight-output,.highlight-python, .highlight-javascript
.highlight-output, .highlight-python, .highlight-javascript, .highlight-sh
{
width: auto;
max-width: 500px;
}
.highlight-python
.highlight-python, .highlight-sh
{
pre { margin: -10px 0 10px 0;}
&:before

View file

@ -6,7 +6,7 @@ Advanced Usage
Configuration File
-------------------
You can configure the way jrnl behaves in a configuration file. By default, this is ``~/.jrnl_config``. If you have the ``XDG_CONFIG_HOME`` variable set, the configuration file will be saved under ``$XDG_CONFIG_HOME/jrnl``.
You can configure the way jrnl behaves in a configuration file. By default, this is ``~/.jrnl_config``. If you have the ``XDG_CONFIG_HOME`` variable set, the configuration file will be saved as ``$XDG_CONFIG_HOME/jrnl/.jrnl_config``.
.. note::
@ -84,12 +84,11 @@ You can configure _jrnl_ to use with multiple journals (eg. ``private`` and ``wo
}
}
The ``default`` journal gets created the first time you start _jrnl_. Now you can access the ``work`` journal by using ``jrnl work`` instead of ``jrnl``, eg. ::
The ``default`` journal gets created the first time you start _jrnl_. Now you can access the ``work`` journal by using ``jrnl work`` instead of ``jrnl``, eg.
.. code-block:: sh
jrnl work at 10am: Meeting with @Steve
::
jrnl work -n 3
will both use ``~/work.txt``, while ``jrnl -n 3`` will display the last three entries from ``~/journal.txt`` (and so does ``jrnl default -n 3``).

View file

@ -7,11 +7,15 @@ Encrypting and decrypting
-------------------------
If you don't choose to encrypt your file when you run `jrnl` for the first time, you can encrypt your existing journal file or change its password using ::
If you don't choose to encrypt your file when you run `jrnl` for the first time, you can encrypt your existing journal file or change its password using
.. code-block:: sh
jrnl --encrypt
If it is already encrypted, you will first be asked for the current password. You can then enter a new password and your plain journal will replaced by the encrypted file. Conversely, ::
If it is already encrypted, you will first be asked for the current password. You can then enter a new password and your plain journal will replaced by the encrypted file. Conversely,
.. code-block:: sh
jrnl --decrypt
@ -28,20 +32,47 @@ If you don't initially store the password in the keychain but decide to do so at
A note on security
------------------
While jrnl follows best practises, true security is an illusion. Specifically, jrnl will leave traces in your memory and your shell history -- it's meant to keep journals secure in transit, for example when storing it on an `untrusted <http://techcrunch.com/2014/04/09/condoleezza-rice-joins-dropboxs-board/>`_ services such as Dropbox. If you're concerned about security, disable history logging for journal in your ``.bashrc`` ::
While jrnl follows best practises, true security is an illusion. Specifically, jrnl will leave traces in your memory and your shell history -- it's meant to keep journals secure in transit, for example when storing it on an `untrusted <http://techcrunch.com/2014/04/09/condoleezza-rice-joins-dropboxs-board/>`_ services such as Dropbox. If you're concerned about security, disable history logging for journal in your ``.bashrc``
HISTINGNORE="jrnl *"
.. code-block:: sh
HISTIGNORE="$HISTIGNORE:jrnl *"
If you are using zsh instead of bash, you can get the same behaviour adding this to your ``zshrc``
.. code-block:: sh
setopt HIST_IGNORE_SPACE
alias jrnl=" jrnl"
Manual decryption
-----------------
Should you ever want to decrypt your journal manually, you can do so with any program that supports the AES algorithm in CBC. The key used for encryption is the SHA-256-hash of your password, the IV (initialisation vector) is stored in the first 16 bytes of the encrypted file. The plain text is encoded in UTF-8 and padded according to PKCS#7 before being encrypted. So, to decrypt a journal file in python, run::
Should you ever want to decrypt your journal manually, you can do so with any program that supports the AES algorithm in CBC. The key used for encryption is the SHA-256-hash of your password, the IV (initialisation vector) is stored in the first 16 bytes of the encrypted file. The plain text is encoded in UTF-8 and padded according to PKCS#7 before being encrypted. Here's a Python script that you can use to decrypt your journal
.. code-block:: python
#!/usr/bin/env python3
import argparse
from Crypto.Cipher import AES
import getpass
import hashlib
import sys
parser = argparse.ArgumentParser()
parser.add_argument("filepath", help="journal file to decrypt")
args = parser.parse_args()
pwd = getpass.getpass()
key = hashlib.sha256(pwd.encode('utf-8')).digest()
with open(args.filepath, 'rb') as f:
ciphertext = f.read()
crypto = AES.new(key, AES.MODE_CBC, ciphertext[:16])
plain = crypto.decrypt(ciphertext[16:])
plain = plain.strip(plain[-1:])
plain = plain.decode("utf-8")
print(plain)
import hashlib, Crypto.Cipher
key = hashlib.sha256(my_password).digest()
with open("my_journal.txt") as f:
cipher = f.read()
crypto = AES.new(key, AES.MODE_CBC, iv = cipher[:16])
plain = crypto.decrypt(cipher[16:])
plain = plain.strip(plain[-1])
plain = plain.decode("utf-8")

View file

@ -6,7 +6,9 @@ Import and Export
Tag export
----------
With::
With
.. code-block:: sh
jrnl --tags
@ -15,7 +17,7 @@ you'll get a list of all tags you used in your journal, sorted by most frequent.
List of all entries
-------------------
::
.. code-block:: sh
jrnl --short
@ -24,7 +26,9 @@ Will only display the date and title of each entry.
JSON export
-----------
Can do::
Can do
.. code-block:: sh
jrnl --export json
@ -33,7 +37,9 @@ Why not create a `beautiful timeline <http://timeline.verite.co/>`_ of your jour
Markdown export
---------------
Use::
Use
.. code-block:: sh
jrnl --export markdown
@ -42,7 +48,7 @@ Markdown is a simple markup language that is human readable and can be used to b
Text export
-----------
::
.. code-block:: sh
jrnl --export text
@ -51,11 +57,15 @@ Pretty-prints your entire journal.
Export to files
---------------
You can specify the output file of your exported journal using the `-o` argument::
You can specify the output file of your exported journal using the `-o` argument
.. code-block:: sh
jrnl --export md -o journal.md
The above command will generate a file named `journal.md`. If the `-o` argument is a directory, jrnl will export each entry into an individual file::
The above command will generate a file named `journal.md`. If the `-o` argument is a directory, jrnl will export each entry into an individual file
.. code-block:: sh
jrnl --export json -o my_entries/

View file

@ -6,11 +6,21 @@ Getting started
Installation
------------
Install *jrnl* using pip ::
On OS X, the easiest way to install *jrnl* is using `Homebrew <http://brew.sh/>`_
.. code-block:: sh
brew install jrnl
On other platforms, install *jrnl* using pip
.. code-block:: sh
pip install jrnl
Or, if you want the option to encrypt your journal, ::
Or, if you want the option to encrypt your journal,
.. code-block:: sh
pip install jrnl[encrypted]
@ -20,7 +30,7 @@ to install the dependencies for encrypting journals as well.
Installing the encryption library, `pycrypto`, requires a `gcc` compiler. For this reason, jrnl will not install `pycrypto` unless explicitly told so like this. You can `install PyCrypto manually <https://www.dlitz.net/software/pycrypto/>`_ first or install it with ``pip install pycrypto`` if you have a `gcc` compiler.
Also note that when using zsh, you the correct syntax is ``pip install "jrnl[encrypted]"`` (note the quotes).
Also note that when using zsh, the correct syntax is ``pip install "jrnl[encrypted]"`` (note the quotes).
The first time you run ``jrnl`` you will be asked where your journal file should be created and whether you wish to encrypt it.
@ -28,7 +38,9 @@ The first time you run ``jrnl`` you will be asked where your journal file should
Quickstart
----------
to make a new entry, just type::
to make a new entry, just type
.. code-block:: sh
jrnl yesterday: Called in sick. Used the time to clean the house and spent 4h on writing my book.

View file

@ -15,7 +15,7 @@ Optionally, your journal can be encrypted using the `256-bit AES <http://en.wiki
Why keep a journal?
-------------------
Journals aren't only for 13-year old girls and people who have too much time on their summer vacation. A journal helps you to keep track of the things you get done and how you did them. Your imagination may be limitless, but your memory isn't.
Journals aren't just for angsty teenagers and people who have too much time on their summer vacation. A journal helps you to keep track of the things you get done and how you did them. Your imagination may be limitless, but your memory isn't.
For personal use, make it a good habit to write at least 20 words a day. Just to reflect what made this day special, why you haven't wasted it.

View file

@ -9,7 +9,9 @@ Recipes
Co-occurrence of tags
~~~~~~~~~~~~~~~~~~~~~
If I want to find out how often I mentioned my flatmates Alberto and Melo in the same entry, I run ::
If I want to find out how often I mentioned my flatmates Alberto and Melo in the same entry, I run
.. code-block:: sh
jrnl @alberto --tags | grep @melo
@ -18,7 +20,9 @@ And will get something like ``@melo: 9``, meaning there are 9 entries where both
Combining filters
~~~~~~~~~~~~~~~~~
You can do things like ::
You can do things like
.. code-block:: sh
jrnl @fixed -starred -n 10 -until "jan 2013" --short
@ -27,11 +31,15 @@ To get a short summary of the 10 most recent, favourited entries before January
Statistics
~~~~~~~~~~
How much did I write last year? ::
How much did I write last year?
.. code-block:: sh
jrnl -from "jan 1 2013" -until "dec 31 2013" | wc -w
Will give you the number of words you wrote in 2013. How long is my average entry? ::
Will give you the number of words you wrote in 2013. How long is my average entry?
.. code-block:: sh
expr $(jrnl --export text | wc -w) / $(jrnl --short | wc -l)
@ -40,7 +48,9 @@ This will first get the total number of words in the journal and divide it by th
Importing older files
~~~~~~~~~~~~~~~~~~~~~
If you want to import a file as an entry to jrnl, you can just do ``jrnl < entry.ext``. But what if you want the modification date of the file to be the date of the entry in jrnl? Try this ::
If you want to import a file as an entry to jrnl, you can just do ``jrnl < entry.ext``. But what if you want the modification date of the file to be the date of the entry in jrnl? Try this
.. code-block:: sh
echo `stat -f %Sm -t '%d %b %Y at %H:%M: ' entry.txt` `cat entry.txt` | jrnl
@ -108,10 +118,17 @@ On OS X, you can use the fabulous `iA Writer <http://www.iawriter.com/mac>`_ to
.. code-block:: javascript
"editor": "open -b jp.informationarchitects.WriterForMacOSX -Wn"
"editor": "open -b pro.writer.mac -Wn"
What does this do? ``open -b ...`` opens a file using the application identified by the bundle identifier (a unique string for every app out there). ``-Wn`` tells the application to wait until it's closed before passing back control, and to use a new instance of the application.
If the ``pro.writer.mac`` bundle identifier is not found on your system, you can find the right string to use by inspecting iA Writer's ``Info.plist`` file in your shell:
.. code-block:: sh
$ grep -A 1 CFBundleIdentifier /Applications/iA\ Writer.app/Contents/Info.plist
<key>CFBundleIdentifier</key>
<string>pro.writer.mac</string>
Notepad++ on Windows
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -10,7 +10,9 @@ We intentionally break a convention on command line arguments: all arguments sta
Listing Journals
----------------
You can list the journals accessible by jrnl::
You can list the journals accessible by jrnl
.. code-block:: sh
jrnl -ls
@ -19,7 +21,9 @@ The journals displayed correspond to those specified in the jrnl configuration f
Composing Entries
-----------------
Composing mode is entered by either starting ``jrnl`` without any arguments -- which will prompt you to write an entry or launch your editor -- or by just writing an entry on the prompt, such as::
Composing mode is entered by either starting ``jrnl`` without any arguments -- which will prompt you to write an entry or launch your editor -- or by just writing an entry on the prompt, such as
.. code-block:: sh
jrnl today at 3am: I just met Steve Buscemi in a bar! He looked funny.
@ -28,7 +32,9 @@ Composing mode is entered by either starting ``jrnl`` without any arguments -- w
Most shell contains a certain number of reserved characters, such as ``#`` and ``*``. Unbalanced quotes, parenthesis, and so on will also get into the way of your editing. For writing longer entries, just enter ``jrnl`` and hit ``return``. Only then enter the text of your journal entry. Alternatively, :doc:`use an external editor <advanced>`).
You can also import an entry directly from a file::
You can also import an entry directly from a file
.. code-block:: sh
jrnl < my_entry.txt
@ -48,7 +54,9 @@ Timestamps that work:
Starring entries
~~~~~~~~~~~~~~~~
To mark an entry as a favourite, simply "star" it::
To mark an entry as a favourite, simply "star" it
.. code-block:: sh
jrnl last sunday *: Best day of my life.
@ -65,30 +73,42 @@ If you don't want to add a date (ie. your entry will be dated as now), The follo
Viewing
-------
::
.. code-block:: sh
jrnl -n 10
will list you the ten latest entries (if you're lazy, ``jrnl -10`` will do the same), ::
will list you the ten latest entries (if you're lazy, ``jrnl -10`` will do the same),
.. code-block:: sh
jrnl -from "last year" -until march
everything that happened from the start of last year to the start of last march. To only see your favourite entries, use ::
everything that happened from the start of last year to the start of last march. To only see your favourite entries, use
.. code-block:: sh
jrnl -starred
Using Tags
----------
Keep track of people, projects or locations, by tagging them with an ``@`` in your entries ::
Keep track of people, projects or locations, by tagging them with an ``@`` in your entries
.. code-block:: sh
jrnl Had a wonderful day on the @beach with @Tom and @Anna.
You can filter your journal entries just like this: ::
You can filter your journal entries just like this:
.. code-block:: sh
jrnl @pinkie @WorldDomination
Will print all entries in which either ``@pinkie`` or ``@WorldDomination`` occurred. ::
Will print all entries in which either ``@pinkie`` or ``@WorldDomination`` occurred.
.. code-block:: sh
jrnl -n 5 -and @pineapple @lubricant
@ -101,7 +121,9 @@ the last five entries containing both ``@pineapple`` **and** ``@lubricant``. You
Editing older entries
---------------------
You can edit selected entries after you wrote them. This is particularly useful when your journal file is encrypted or if you're using a DayOne journal. To use this feature, you need to have an editor configured in your journal configuration file (see :doc:`advanced usage <advanced>`)::
You can edit selected entries after you wrote them. This is particularly useful when your journal file is encrypted or if you're using a DayOne journal. To use this feature, you need to have an editor configured in your journal configuration file (see :doc:`advanced usage <advanced>`)
.. code-block:: sh
jrnl -until 1950 @texas -and @history --edit
@ -109,7 +131,9 @@ Will open your editor with all entries tagged with ``@texas`` and ``@history`` b
Of course, if you are using multiple journals, you can also edit e.g. the latest entry of your work journal with ``jrnl work -n 1 --edit``. In any case, this will bring up your editor and save (and, if applicable, encrypt) your edited journal after you save and exit the editor.
You can also use this feature for deleting entries from your journal::
You can also use this feature for deleting entries from your journal
.. code-block:: sh
jrnl @girlfriend -until 'june 2012' --edit

View file

@ -0,0 +1,16 @@
{
"default_hour": 9,
"timeformat": "%Y-%m-%d %H:%M",
"linewrap": 80,
"encrypt": false,
"editor": "",
"default_minute": 0,
"highlight": true,
"password": "",
"journals": {
"simple": "features/journals/simple.journal",
"work": "features/journals/work.journal",
"ideas": "features/journals/nothing.journal"
},
"tagsymbols": "@"
}

View file

@ -34,3 +34,8 @@ Feature: Multiple journals
Then journal "ideas" should not exist
When we run "jrnl ideas 23 july 2012: sell my junk on ebay and make lots of money"
Then journal "ideas" should have 1 entry
Scenario: Gracefully handle a config without a default journal
Given we use the config "multiple_without_default.json"
When we run "jrnl fork this repo and fix something"
Then we should see the message "You have not specified a journal. Either provide a default journal in your config file, or specify one of your journals on the command line."

View file

@ -59,3 +59,10 @@ Feature: Zapped bugs should stay dead.
2014-04-24 09:00 Ran 6.2 miles today in 1:02:03.
| I'm feeling sore because I forgot to stretch.
"""
Scenario: Writing an entry at the prompt with non-ascii characters
# https://github.com/maebert/jrnl/issues/295
Given we use the config "basic.json"
When we run "jrnl" and enter "Crème brûlée & Mötorhead"
Then we should get no error
and the journal should contain "Crème brûlée & Mötorhead"

View file

@ -2,11 +2,11 @@ from behave import *
from jrnl import cli, Journal, util
from dateutil import parser as date_parser
import os
import sys
import codecs
import json
import pytz
import keyring
keyring.set_keyring(keyring.backends.file.PlaintextKeyring())
import keyrings
keyring.set_keyring(keyrings.alt.file.PlaintextKeyring())
try:
from io import StringIO
except ImportError:
@ -30,7 +30,7 @@ def _parse_args(command):
def read_journal(journal_name="default"):
with open(cli.CONFIG_PATH) as config_file:
config = json.load(config_file)
with open(config['journals'][journal_name]) as journal_file:
with codecs.open(config['journals'][journal_name], 'r', 'utf-8') as journal_file:
journal = journal_file.read()
return journal
@ -57,7 +57,7 @@ def run_with_input(context, command, inputs=None):
buffer = StringIO(text.strip())
util.STDIN = buffer
try:
cli.run(args or None)
cli.run(args)
context.exit_status = 0
except SystemExit as e:
context.exit_status = e.code
@ -66,7 +66,7 @@ def run_with_input(context, command, inputs=None):
def run(context, command):
args = _parse_args(command)
try:
cli.run(args or None)
cli.run(args)
context.exit_status = 0
except SystemExit as e:
context.exit_status = e.code
@ -124,10 +124,8 @@ def check_output(context, text=None):
def check_output_time_inline(context, text):
out = context.stdout_capture.getvalue()
local_tz = tzlocal.get_localzone()
utc_time = date_parser.parse(text)
date = utc_time + local_tz._utcoffset
local_date = date.strftime("%Y-%m-%d %H:%M")
assert local_date in out, local_date
local_time = date_parser.parse(text).astimezone(local_tz).strftime("%Y-%m-%d %H:%M")
assert local_time in out, local_time
@then('the output should contain "{text}"')
def check_output_inline(context, text):
@ -186,7 +184,7 @@ def config_var(context, key, value, journal=None):
@then('the journal should have {number:d} entry')
@then('journal "{journal_name}" should have {number:d} entries')
@then('journal "{journal_name}" should have {number:d} entry')
def check_journal_content(context, number, journal_name="default"):
def check_num_entries(context, number, journal_name="default"):
journal = open_journal(journal_name)
assert len(journal.entries) == number

View file

@ -30,6 +30,8 @@ class DayOne(Journal.Journal):
filenames = [os.path.join(self.config['journal'], "entries", f) for f in os.listdir(os.path.join(self.config['journal'], "entries"))]
self.entries = []
for filename in filenames:
if os.path.isdir(filename):
continue
with open(filename, 'rb') as plist_entry:
try:
dict_entry = plistlib.readPlist(plist_entry)
@ -58,7 +60,10 @@ class DayOne(Journal.Journal):
if not hasattr(entry, "uuid"):
entry.uuid = uuid.uuid1().hex
utc_time = datetime.utcfromtimestamp(time.mktime(entry.date.timetuple()))
filename = os.path.join(self.config['journal'], "entries", entry.uuid + ".doentry")
# make sure to upper() the uuid since uuid.uuid1 returns a lowercase string by default
# while dayone uses uppercase by default. On fully case preserving filesystems (e.g.
# linux) this results in duplicated entries when we save the file
filename = os.path.join(self.config['journal'], "entries", entry.uuid.upper() + ".doentry")
entry_plist = {
'Creation Date': utc_time,
'Starred': entry.starred if hasattr(entry, 'starred') else False,

View file

@ -36,8 +36,6 @@ class Journal(object):
self.search_tags = None # Store tags we're highlighting
self.name = name
self.open()
def __len__(self):
"""Returns the number of entries"""
return len(self.entries)

View file

@ -8,7 +8,7 @@ jrnl is a simple journal application for your command line.
from __future__ import absolute_import
__title__ = 'jrnl'
__version__ = '1.9.5'
__version__ = '1.9.8'
__author__ = 'Manuel Ebert'
__license__ = 'MIT License'
__copyright__ = 'Copyright 2013 - 2014 Manuel Ebert'

View file

@ -17,16 +17,19 @@ import jrnl
import os
import argparse
import sys
import logging
xdg_config = os.environ.get('XDG_CONFIG_HOME')
CONFIG_PATH = os.path.join(xdg_config, "jrnl") if xdg_config else os.path.expanduser('~/.jrnl_config')
PYCRYPTO = install.module_exists("Crypto")
log = logging.getLogger(__name__)
def parse_args(args=None):
parser = argparse.ArgumentParser()
parser.add_argument('-v', '--version', dest='version', action="store_true", help="prints version information and exits")
parser.add_argument('-ls', dest='ls', action="store_true", help="displays accessible journals")
parser.add_argument('-d', '--debug', dest='debug', action='store_true', help='execute in debug mode')
composing = parser.add_argument_group('Composing', 'To write an entry simply write it on the command line, e.g. "jrnl yesterday at 1pm: Went to the gym."')
composing.add_argument('text', metavar='', nargs="*")
@ -80,7 +83,7 @@ def encrypt(journal, filename=None):
def decrypt(journal, filename=None):
""" Decrypts into new file. If filename is not set, we encrypt the journal file itself. """
""" Decrypts into new file. If filename is not set, we decrypt the journal file itself. """
journal.config['encrypt'] = False
journal.config['password'] = ""
journal.write(filename)
@ -90,6 +93,7 @@ def decrypt(journal, filename=None):
def touch_journal(filename):
"""If filename does not exist, touch the file"""
if not os.path.exists(filename):
log.debug('Creating journal file %s', filename)
util.prompt("[Journal created at {0}]".format(filename))
open(filename, 'a').close()
@ -114,8 +118,15 @@ def update_config(config, new_config, scope, force_local=False):
config.update(new_config)
def configure_logger(debug=False):
logging.basicConfig(level=logging.DEBUG if debug else logging.INFO,
format='%(levelname)-8s %(name)-12s %(message)s')
logging.getLogger('parsedatetime').setLevel(logging.INFO) # disable parsedatetime debug logging
def run(manual_args=None):
args = parse_args(manual_args)
configure_logger(args.debug)
args.text = [p.decode('utf-8') if util.PY2 and not isinstance(p, unicode) else p for p in args.text]
if args.version:
version_str = "{0} version {1}".format(jrnl.__title__, jrnl.__version__)
@ -123,8 +134,10 @@ def run(manual_args=None):
sys.exit(0)
if not os.path.exists(CONFIG_PATH):
log.debug('Configuration file not found, installing jrnl...')
config = install.install_jrnl(CONFIG_PATH)
else:
log.debug('Reading configuration from file %s', CONFIG_PATH)
config = util.load_and_fix_json(CONFIG_PATH)
install.upgrade_config(config, config_path=CONFIG_PATH)
@ -132,6 +145,7 @@ def run(manual_args=None):
print(util.py2encode(list_journals(config)))
sys.exit(0)
log.debug('Using configuration "%s"', config)
original_config = config.copy()
# check if the configuration is supported by available modules
if config['encrypt'] and not PYCRYPTO:
@ -151,15 +165,34 @@ def run(manual_args=None):
except:
pass
log.debug('Using journal "%s"', journal_name)
journal_conf = config['journals'].get(journal_name)
if type(journal_conf) is dict: # We can override the default config on a by-journal basis
log.debug('Updating configuration with specific jourlnal overrides %s', journal_conf)
config.update(journal_conf)
else: # But also just give them a string to point to the journal file
config['journal'] = journal_conf
if config['journal'] is None:
util.prompt("You have not specified a journal. Either provide a default journal in your config file, or specify one of your journals on the command line.")
sys.exit(1)
config['journal'] = os.path.expanduser(os.path.expandvars(config['journal']))
touch_journal(config['journal'])
log.debug('Using journal path %(journal)s', config)
mode_compose, mode_export = guess_mode(args, config)
# open journal file or folder
if os.path.isdir(config['journal']):
if config['journal'].strip("/").endswith(".dayone") or \
"entries" in os.listdir(config['journal']):
journal = DayOneJournal.DayOne(**config)
else:
util.prompt("[Error: {0} is a directory, but doesn't seem to be a DayOne journal either.".format(config['journal']))
sys.exit(1)
else:
journal = Journal.Journal(journal_name, **config)
# How to quit writing?
if "win32" in sys.platform:
_exit_multiline_code = "on a blank line, press Ctrl+Z and then Enter"
@ -183,22 +216,14 @@ def run(manual_args=None):
else:
mode_compose = False
# open journal file or folder
if os.path.isdir(config['journal']):
if config['journal'].strip("/").endswith(".dayone") or \
"entries" in os.listdir(config['journal']):
journal = DayOneJournal.DayOne(**config)
else:
util.prompt("[Error: {0} is a directory, but doesn't seem to be a DayOne journal either.".format(config['journal']))
sys.exit(1)
else:
journal = Journal.Journal(journal_name, **config)
journal.open()
# Writing mode
if mode_compose:
raw = " ".join(args.text).strip()
if util.PY2 and type(raw) is not unicode:
raw = raw.decode(sys.getfilesystemencoding())
log.debug('Appending raw line "%s" to journal "%s"', raw, journal_name)
journal.new_entry(raw)
util.prompt("[Entry added to {0} journal]".format(journal_name))
journal.write()
@ -233,14 +258,14 @@ def run(manual_args=None):
elif args.encrypt is not False:
encrypt(journal, filename=args.encrypt)
# Not encrypting to a separate file: update config!
if not args.encrypt:
if not args.encrypt or args.encrypt == config['journal']:
update_config(original_config, {"encrypt": True}, journal_name, force_local=True)
install.save_config(original_config, config_path=CONFIG_PATH)
elif args.decrypt is not False:
decrypt(journal, filename=args.decrypt)
# Not decrypting to a separate file: update config!
if not args.decrypt:
if not args.decrypt or args.decrypt == config['journal']:
update_config(original_config, {"encrypt": False}, journal_name, force_local=True)
install.save_config(original_config, config_path=CONFIG_PATH)
@ -259,7 +284,7 @@ def run(manual_args=None):
if num_deleted:
prompts.append("{0} {1} deleted".format(num_deleted, "entry" if num_deleted == 1 else "entries"))
if num_edited:
prompts.append("{0} {1} modified".format(num_edited, "entry" if num_deleted == 1 else "entries"))
prompts.append("{0} {1} modified".format(num_edited, "entry" if num_edited == 1 else "entries"))
if prompts:
util.prompt("[{0}]".format(", ".join(prompts).capitalize()))
journal.entries += other_entries

View file

@ -87,7 +87,8 @@ def install_jrnl(config_path='~/.jrnl_config'):
except OSError:
pass
open(default_config['journals']['default'], 'a').close() # Touch to make sure it's there
if not os.path.isdir(path): # if it's a directory and exists (e.g. a DayOne journal, let it be)
open(default_config['journals']['default'], 'a').close() # Touch to make sure it's there
# Write config to ~/.jrnl_conf
with open(config_path, 'w') as f:

View file

@ -48,7 +48,10 @@ def parse(date_str, inclusive=False, default_hour=None, default_minute=None):
return None
if flag is 1: # Date found, but no time. Use the default time.
date = datetime(*date[:3], hour=default_hour or 0, minute=default_minute or 0)
date = datetime(*date[:3],
hour=23 if inclusive else default_hour or 0,
minute=59 if inclusive else default_minute or 0,
second=59 if inclusive else 0)
else:
date = datetime(*date[:6])

View file

@ -13,6 +13,7 @@ import tempfile
import subprocess
import codecs
import unicodedata
import logging
PY3 = sys.version_info[0] == 3
PY2 = sys.version_info[0] == 2
@ -22,6 +23,8 @@ STDOUT = sys.stdout
TEST = False
__cached_tz = None
log = logging.getLogger(__name__)
def getpass(prompt="Password: "):
if not TEST:
@ -71,16 +74,19 @@ def py2encode(s):
def prompt(msg):
"""Prints a message to the std err stream defined in util."""
if not msg:
return
if not msg.endswith("\n"):
msg += "\n"
STDERR.write(u(msg))
def py23_input(msg=""):
STDERR.write(u(msg))
return STDIN.readline().strip()
prompt(msg)
return u(STDIN.readline()).strip()
def py23_read(msg=""):
return STDIN.read()
prompt(msg)
return u(STDIN.read())
def yesno(prompt, default=True):
prompt = prompt.strip() + (" [Y/n]" if default else " [y/N]")
@ -93,27 +99,35 @@ def load_and_fix_json(json_path):
"""
with open(json_path) as f:
json_str = f.read()
config = fixed = None
log.debug('Configuration file %s read correctly', json_path)
config = None
try:
return json.loads(json_str)
except ValueError as e:
log.debug('Could not parse configuration %s: %s', json_str, e,
exc_info=True)
# Attempt to fix extra ,
json_str = re.sub(r",[ \n]*}", "}", json_str)
# Attempt to fix missing ,
json_str = re.sub(r"([^{,]) *\n *(\")", r"\1,\n \2", json_str)
try:
log.debug('Attempting to reload automatically fixed configuration file %s',
json_str)
config = json.loads(json_str)
with open(json_path, 'w') as f:
json.dump(config, f, indent=2)
log.debug('Fixed configuration saved in file %s', json_path)
prompt("[Some errors in your jrnl config have been fixed for you.]")
return config
except ValueError as e:
log.debug('Could not load fixed configuration: %s', e, exc_info=True)
prompt("[There seems to be something wrong with your jrnl config at {0}: {1}]".format(json_path, e.message))
prompt("[Entry was NOT added to your journal]")
sys.exit(1)
def get_text_from_editor(config, template=""):
tmpfile = os.path.join(tempfile.mktemp(prefix="jrnl"))
_, tmpfile = tempfile.mkstemp(prefix="jrnl", text=True, suffix=".txt")
os.close(_)
with codecs.open(tmpfile, 'w', "utf-8") as f:
if template:
f.write(template)

105
setup.py
View file

@ -9,7 +9,7 @@ Optionally, your journal can be encrypted using 256-bit AES.
Why keep a journal?
```````````````````
Journals aren't only for 13-year old girls and people who have too much time on their summer vacation. A journal helps you to keep track of the things you get done and how you did them. Your imagination may be limitless, but your memory isn't. For personal use, make it a good habit to write at least 20 words a day. Just to reflect what made this day special, why you haven't wasted it. For professional use, consider a text-based journal to be the perfect complement to your GTD todo list - a documentation of what and how you've done it.
Journals aren't just for angsty teenagers and people who have too much time on their summer vacation. A journal helps you to keep track of the things you get done and how you did them. Your imagination may be limitless, but your memory isn't. For personal use, make it a good habit to write at least 20 words a day. Just to reflect what made this day special, why you haven't wasted it. For professional use, consider a text-based journal to be the perfect complement to your GTD todo list - a documentation of what and how you've done it.
In a Nutshell
`````````````
@ -51,13 +51,8 @@ except ImportError:
readline_available = False
if sys.argv[-1] == 'publish':
os.system("python setup.py sdist upload")
sys.exit()
base_dir = os.path.dirname(os.path.abspath(__file__))
def get_version(filename="jrnl/__init__.py"):
with open(os.path.join(base_dir, filename)) as initfile:
for line in initfile.readlines():
@ -65,6 +60,71 @@ def get_version(filename="jrnl/__init__.py"):
if m:
return m.group(1)
def get_changelog(filename="CHANGELOG.md"):
changelog = {}
current_version = None
with open(os.path.join(base_dir, filename)) as changelog_file:
for line in changelog_file.readlines():
if line.startswith("* __"):
parts = line.strip("* ").split(" ", 1)
if len(parts) == 2:
current_version, changes = parts[0].strip("_\n"), parts[1]
changelog[current_version] = [changes.strip()]
else:
current_version = parts[0].strip("_\n")
changelog[current_version] = []
elif line.strip() and current_version and not line.startswith("#"):
changelog[current_version].append(line.strip(" *\n"))
return changelog
def dist_pypi():
os.system("python setup.py sdist upload")
sys.exit()
def dist_github():
"""Creates a release on the maebert/jrnl repository on github"""
import requests
import keyring
import getpass
version = get_version()
version_tuple = version.split(".")
changes_since_last_version = ["* __{}__: {}".format(key, "\n".join(changes)) for key, changes in get_changelog().items() if key.startswith("{}.{}".format(*version_tuple))]
changes_since_last_version = "\n".join(sorted(changes_since_last_version, reverse=True))
payload = {
"tag_name": version,
"target_commitish": "master",
"name": version,
"body": "Changes in Version {}.{}: \n\n{}".format(version_tuple[0], version_tuple[1], changes_since_last_version)
}
print("Preparing release {}...".format(version))
username = keyring.get_password("github", "__default_user") or raw_input("Github username: ")
password = keyring.get_password("github", username) or getpass.getpass()
otp = raw_input("One Time Token: ")
response = requests.post("https://api.github.com/repos/maebert/jrnl/releases", headers={"X-GitHub-OTP": otp}, json=payload, auth=(username, password))
if response.status_code in (403, 404):
print("Authentication error.")
else:
keyring.set_password("github", "__default_user", username)
keyring.set_password("github", username, password)
if response.status_code > 299:
if "message" in response.json():
print("Error: {}".format(response.json()['message']))
for error_dict in response.json().get('errors', []):
print("*", error_dict)
else:
print("Unkown error")
print(response.text)
else:
print("Release created.")
sys.exit()
if sys.argv[-1] == 'publish':
dist_pypi()
if sys.argv[-1] == 'github_release':
dist_github()
conditional_dependencies = {
"pyreadline>=2.0": not readline_available and "win32" in sys.platform,
"readline>=6.2": not readline_available and "win32" not in sys.platform,
@ -86,30 +146,31 @@ setup(
"six>=1.6.1",
"tzlocal>=1.1",
"keyring>=3.3",
"keyrings.alt>=1.3",
] + [p for p, cond in conditional_dependencies.items() if cond],
extras_require = {
"encrypted": "pycrypto>=2.6"
},
long_description=__doc__,
entry_points={
'console_scripts': [
'jrnl = jrnl:run',
],
"console_scripts": [
"jrnl = jrnl:run",
]
},
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: MIT License',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Topic :: Office/Business :: News/Diary',
'Topic :: Text Processing'
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Topic :: Office/Business :: News/Diary",
"Topic :: Text Processing"
],
# metadata for upload to PyPI
author = "Manuel Ebert",