From 080bfd513fec2f0226935fd42b144f796c011c3c Mon Sep 17 00:00:00 2001 From: No GUI Date: Sun, 14 Apr 2013 13:19:03 -0700 Subject: [PATCH 01/28] Support a custom location for the config Export $JRNL_CONFIG to override the location instead of using $HOME. Too many dotfiles are literring it, ya' know? Linux desktop users, you might like if you follow the XDG standards: export JRNL_CONFIG=$XDG_CONFIG_HOME/jrnl/config --- jrnl/jrnl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index 2de3fd16..e7bd2ef4 100755 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -24,7 +24,7 @@ __version__ = '1.0.0-rc1' __author__ = 'Manuel Ebert, Stephan Gabler' __license__ = 'MIT' -CONFIG_PATH = os.path.expanduser('~/.jrnl_config') +CONFIG_PATH = os.environ.get('JRNL_CONFIG', os.path.expanduser('~/.jrnl_config')) PYCRYPTO = install.module_exists("Crypto") def parse_args(): From fe521147c0bb41fce4436d9247e13bbcea17239c Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Wed, 17 Apr 2013 10:19:02 +0200 Subject: [PATCH 02/28] Uses colorama instead of clint --- CHANGELOG.md | 3 +++ README.md | 2 +- jrnl/Journal.py | 15 ++++++++------- jrnl/install.py | 6 +++--- setup.py | 6 ++++-- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b96c6de2..55ab9246 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ Changelog ========= +### 1.0.2 (April 17, 2013) + +* [Improved] Removed clint in favour of colorama ### 1.0.1 (March 12, 2013) * [Fixed] Requires parsedatetime 1.1.2 or newer diff --git a/README.md b/README.md index 3e581e08..55ef0d70 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ The configuration file is a simple JSON file with the following options. - `tagsymbols`: Symbols to be interpreted as tags. (__See note below__) - `default_hour` and `default_minute`: if you supply a date, such as `last thursday`, but no specific time, the entry will be created at this time - `timeformat`: how to format the timestamps in your journal, see the [python docs](http://docs.python.org/library/time.html#time.strftime) for reference -- `highlight`: if `true` and you have [clint](http://www.nicosphere.net/clint-command-line-library-for-python/) installed, tags will be highlighted in cyan. +- `highlight`: if `true`, tags will be highlighted in cyan. - `linewrap`: controls the width of the output. Set to `0` or `false` if you don't want to wrap long lines. > __Note on `tagsymbols`:__ Although it seems intuitive to use the `#` character for tags, there's a drawback: on most shells, this is interpreted as a meta-character starting a comment. This means that if you type diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 8147b536..29856975 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -19,9 +19,10 @@ except ImportError: import hashlib import getpass try: - import clint + import colorama + colorama.init() except ImportError: - clint = None + colorama = None import plistlib import uuid @@ -50,9 +51,9 @@ class Journal(object): self.entries = self.parse(journal_txt) self.sort() - def _colorize(self, string, color='red'): - if clint: - return str(clint.textui.colored.ColoredString(color.upper(), string)) + def _colorize(self, string): + if colorama: + return colorama.Fore.CYAN + string + colorama.Fore.RESET else: return string @@ -152,11 +153,11 @@ class Journal(object): for tag in self.search_tags: tagre = re.compile(re.escape(tag), re.IGNORECASE) pp = re.sub(tagre, - lambda match: self._colorize(match.group(0), 'cyan'), + lambda match: self._colorize(match.group(0)), pp) else: pp = re.sub(r"([%s]\w+)" % self.config['tagsymbols'], - lambda match: self._colorize(match.group(0), 'cyan'), + lambda match: self._colorize(match.group(0)), pp) return pp diff --git a/jrnl/install.py b/jrnl/install.py index 90b5939e..f0d7d48b 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -77,8 +77,8 @@ def install_jrnl(config_path='~/.jrnl_config'): print("PyCrypto not found. To encrypt your journal, install the PyCrypto package from http://www.pycrypto.org and run 'jrnl --encrypt'. For now, your journal will be stored in plain text.") # Use highlighting: - if module_exists("clint"): - print("clint not found. To turn on highlighting, install clint and set highlight to true in your .jrnl_conf.") + if module_exists("colorama"): + print("colorama not found. To turn on highlighting, install colorama and set highlight to true in your .jrnl_conf.") default_config['highlight'] = False open(default_config['journals']['default'], 'a').close() # Touch to make sure it's there @@ -91,4 +91,4 @@ def install_jrnl(config_path='~/.jrnl_config'): config['password'] = password return config - \ No newline at end of file + diff --git a/setup.py b/setup.py index ab80113b..eb1ac563 100644 --- a/setup.py +++ b/setup.py @@ -56,11 +56,13 @@ setup( version = "1.0.1", description = "A command line journal application that stores your journal in a plain text file", packages = ['jrnl'], - install_requires = ["parsedatetime >= 1.1.2"], extras_require = { 'encryption': ["pycrypto"], - 'highlight': ["clint"] }, + install_requires = [ + "parsedatetime >= 1.1.2", + "colorama >= 0.2.5", + ], long_description=__doc__, entry_points={ 'console_scripts': [ From 24f769cf662ec2e74c9ea463bb99991a1f4b0a42 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Wed, 17 Apr 2013 10:19:25 +0200 Subject: [PATCH 03/28] Makes PyCrypto an automatic install --- CHANGELOG.md | 1 + README.md | 3 ++- setup.py | 4 +--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55ab9246..768d63bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Changelog ### 1.0.2 (April 17, 2013) +* [Improved] Installs pycrypto by default * [Improved] Removed clint in favour of colorama ### 1.0.1 (March 12, 2013) diff --git a/README.md b/README.md index 55ef0d70..35d32bf5 100644 --- a/README.md +++ b/README.md @@ -222,4 +222,5 @@ Known Issues ------------ - The Windows shell prior to Windows 7 has issues with unicode encoding. If you want to use non-ascii characters, change the codepage with `chcp 1252` before using `jrnl` (Thanks to Yves Pouplard for solving this!) -- _jrnl_ relies on the `Crypto` package to encrypt journals, which has some known problems with installing within virtual environments. +- _jrnl_ relies on the `PyCrypto` package to encrypt journals, which has some known problems with installing within virtual environments. If you want to install __jrnl__ within a virtual environment, you need to [install PyCyrypto manually](https://www.dlitz.net/software/pycrypto/) first. + diff --git a/setup.py b/setup.py index eb1ac563..cdf8ee38 100644 --- a/setup.py +++ b/setup.py @@ -56,12 +56,10 @@ setup( version = "1.0.1", description = "A command line journal application that stores your journal in a plain text file", packages = ['jrnl'], - extras_require = { - 'encryption': ["pycrypto"], - }, install_requires = [ "parsedatetime >= 1.1.2", "colorama >= 0.2.5", + "pycrypto >= 2.6" ], long_description=__doc__, entry_points={ From b2a5b7b219531686cf3897f6ce7d9555a74d04c7 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Wed, 17 Apr 2013 10:19:38 +0200 Subject: [PATCH 04/28] Automatic versioning --- CHANGELOG.md | 2 ++ README.md | 4 ++-- jrnl/__init__.py | 11 +++++++++++ setup.py | 9 ++++++++- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 768d63bf..f83c9c6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ Changelog * [Improved] Installs pycrypto by default * [Improved] Removed clint in favour of colorama +* [Fixed] Smaller fixes and typos + ### 1.0.1 (March 12, 2013) * [Fixed] Requires parsedatetime 1.1.2 or newer diff --git a/README.md b/README.md index 35d32bf5..f3c1d2ba 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ The first time you run `jrnl` you will be asked where your journal file should b Usage ----- -_jrnl_ has to modes: __composing__ and __viewing__. +_jrnl_ has two modes: __composing__ and __viewing__. ### Viewing: @@ -168,7 +168,7 @@ The configuration file is a simple JSON file with the following options. 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 + * `~/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: diff --git a/jrnl/__init__.py b/jrnl/__init__.py index 2c6ba2a0..9ef435f4 100644 --- a/jrnl/__init__.py +++ b/jrnl/__init__.py @@ -1,5 +1,16 @@ #!/usr/bin/env python # encoding: utf-8 + +""" +jrnl is a simple journal application for your command line. +""" + +__title__ = 'jrnl' +__version__ = '1.0.2' +__author__ = 'Manuel Ebert' +__license__ = 'MIT License' +__copyright__ = 'Copyright 2013 Manuel Ebert' + from Journal import Journal from jrnl import cli diff --git a/setup.py b/setup.py index cdf8ee38..67bf45e1 100644 --- a/setup.py +++ b/setup.py @@ -51,9 +51,16 @@ if sys.argv[-1] == 'publish': base_dir = os.path.dirname(os.path.abspath(__file__)) +def get_version(filename="kerouac/__init__.py"): + with open(os.path.join(base_dir, filename)) as initfile: + for line in initfile.readlines(): + m = re.match("__version__ *= *['\"](.*)['\"]", line) + if m: + return m.group(1) + setup( name = "jrnl", - version = "1.0.1", + version = get_version(), description = "A command line journal application that stores your journal in a plain text file", packages = ['jrnl'], install_requires = [ From 8ed2e3666932456a85cfc4aba3354c1c03c82a4b Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Wed, 17 Apr 2013 10:27:17 +0200 Subject: [PATCH 05/28] Fixes bug when showing tags when no tags are defined. Closes #63 --- CHANGELOG.md | 1 + jrnl/jrnl.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f83c9c6a..c68a3ab1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Changelog * [Improved] Installs pycrypto by default * [Improved] Removed clint in favour of colorama +* [Fixed] Fixed a bug where showing tags failed when no tags are defined. * [Fixed] Smaller fixes and typos ### 1.0.1 (March 12, 2013) diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index 2de3fd16..ac3ba67e 100755 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -106,7 +106,9 @@ def print_tags(journal): ] # To be read: [for entry in journal.entries: for tag in set(entry.tags): tag] tag_counts = {(tags.count(tag), tag) for tag in tags} - if min(tag_counts)[0] == 0: + if not tag_counts: + print('[No tags found in journal.]') + elif min(tag_counts)[0] == 0: tag_counts = filter(lambda x: x[0] > 1, tag_counts) print('[Removed tags that appear only once.]') for n, tag in sorted(tag_counts, reverse=False): From 9b0bee06376372ea6ecaa3c1c0caad14647c86bd Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Wed, 17 Apr 2013 10:50:03 +0200 Subject: [PATCH 06/28] Fixes readline support on windows Closes #60 --- CHANGELOG.md | 2 ++ jrnl/Journal.py | 4 ++++ setup.py | 8 ++++---- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c68a3ab1..6f478d9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ Changelog * [Improved] Installs pycrypto by default * [Improved] Removed clint in favour of colorama * [Fixed] Fixed a bug where showing tags failed when no tags are defined. +* [Fixed] Improvements to config parsing (Thanks [alapolloni](https://github.com/alapolloni)) +* [Fixed] Fixes readline support on Windows * [Fixed] Smaller fixes and typos ### 1.0.1 (March 12, 2013) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 29856975..7c8b8f6d 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -16,6 +16,10 @@ try: from Crypto.Random import random, atfork except ImportError: pass +try: + import pyreadline as readline +except ImportError: + import readline import hashlib import getpass try: diff --git a/setup.py b/setup.py index 67bf45e1..d325dfc4 100644 --- a/setup.py +++ b/setup.py @@ -43,15 +43,15 @@ except ImportError: from distutils.core import setup import os import sys +import re if sys.argv[-1] == 'publish': - os.system("python setup.py bdist-egg upload") os.system("python setup.py sdist upload") sys.exit() base_dir = os.path.dirname(os.path.abspath(__file__)) -def get_version(filename="kerouac/__init__.py"): +def get_version(filename="jrnl/__init__.py"): with open(os.path.join(base_dir, filename)) as initfile: for line in initfile.readlines(): m = re.match("__version__ *= *['\"](.*)['\"]", line) @@ -67,7 +67,7 @@ setup( "parsedatetime >= 1.1.2", "colorama >= 0.2.5", "pycrypto >= 2.6" - ], + ] + ["pyreadline >= 2.0"] if "win" in sys.platform else [], long_description=__doc__, entry_points={ 'console_scripts': [ @@ -88,7 +88,7 @@ setup( ], # metadata for upload to PyPI author = "Manuel Ebert", - author_email = "manuel@herelabs.com", + author_email = "manuel@hey.com", license = "MIT License", keywords = "journal todo todo.txt jrnl".split(), url = "http://maebert.github.com/jrnl", From 41706c3cb5ae87f303256b99d5949d31a7a9b489 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Wed, 17 Apr 2013 12:02:26 +0200 Subject: [PATCH 07/28] Fixes install routine --- CHANGELOG.md | 2 +- jrnl/__init__.py | 2 +- jrnl/install.py | 2 +- requirements.txt | 5 +++-- setup.py | 10 +++++----- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f478d9b..f6d92863 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Changelog ========= -### 1.0.2 (April 17, 2013) +### 1.0.3 (April 17, 2013) * [Improved] Installs pycrypto by default * [Improved] Removed clint in favour of colorama diff --git a/jrnl/__init__.py b/jrnl/__init__.py index 9ef435f4..6716b4ea 100644 --- a/jrnl/__init__.py +++ b/jrnl/__init__.py @@ -7,7 +7,7 @@ jrnl is a simple journal application for your command line. """ __title__ = 'jrnl' -__version__ = '1.0.2' +__version__ = '1.0.3' __author__ = 'Manuel Ebert' __license__ = 'MIT License' __copyright__ = 'Copyright 2013 Manuel Ebert' diff --git a/jrnl/install.py b/jrnl/install.py index f0d7d48b..e0b1d068 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -77,7 +77,7 @@ def install_jrnl(config_path='~/.jrnl_config'): print("PyCrypto not found. To encrypt your journal, install the PyCrypto package from http://www.pycrypto.org and run 'jrnl --encrypt'. For now, your journal will be stored in plain text.") # Use highlighting: - if module_exists("colorama"): + if not module_exists("colorama"): print("colorama not found. To turn on highlighting, install colorama and set highlight to true in your .jrnl_conf.") default_config['highlight'] = False diff --git a/requirements.txt b/requirements.txt index 9b78f344..7b7648ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ -clint >= 0.3.1 -parsedatetime == 1.1.2 +parsedatetime >= 1.1.2 +colorama >= 0.2.5 +pycrypto >= 2.6 diff --git a/setup.py b/setup.py index d325dfc4..bc94ae85 100644 --- a/setup.py +++ b/setup.py @@ -64,10 +64,10 @@ setup( description = "A command line journal application that stores your journal in a plain text file", packages = ['jrnl'], install_requires = [ - "parsedatetime >= 1.1.2", - "colorama >= 0.2.5", - "pycrypto >= 2.6" - ] + ["pyreadline >= 2.0"] if "win" in sys.platform else [], + "parsedatetime>=1.1.2", + "colorama>=0.2.5", + "pycrypto>=2.6" + ] + ["pyreadline>=2.0"] if "win" in sys.platform else [], long_description=__doc__, entry_points={ 'console_scripts': [ @@ -75,7 +75,7 @@ setup( ], }, classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Intended Audience :: End Users/Desktop', 'License :: OSI Approved :: MIT License', From 80da7326b84d50bc8c53c164902637fafe3af29b Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 18 Apr 2013 11:51:42 +0200 Subject: [PATCH 08/28] Python 2.6 compatibility Closes #67 --- CHANGELOG.md | 4 ++++ jrnl/jrnl.py | 12 ++++++------ requirements.txt | 1 + setup.py | 7 ++++++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6d92863..84ba1c93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ Changelog ========= +### 1.0.4 + +* [Improved] Python 2.6 compatibility + ### 1.0.3 (April 17, 2013) * [Improved] Installs pycrypto by default diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index a6e76c9c..cbe4c3aa 100755 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -87,14 +87,14 @@ def encrypt(journal, filename=None): journal.make_key(prompt="Enter new password:") journal.config['encrypt'] = True journal.write(filename) - print("Journal encrypted to {}.".format(filename or journal.config['journal'])) + print("Journal encrypted to {0}.".format(filename or journal.config['journal'])) def decrypt(journal, filename=None): """ Decrypts into new file. If filename is not set, we encrypt the journal file itself. """ journal.config['encrypt'] = False journal.config['password'] = "" journal.write(filename) - print("Journal decrypted to {}.".format(filename or journal.config['journal'])) + print("Journal decrypted to {0}.".format(filename or journal.config['journal'])) def print_tags(journal): """Prints a list of all tags and the number of occurances.""" @@ -105,20 +105,20 @@ def print_tags(journal): for tag in set(entry.tags) ] # To be read: [for entry in journal.entries: for tag in set(entry.tags): tag] - tag_counts = {(tags.count(tag), tag) for tag in tags} + tag_counts = set([(tags.count(tag), tag) for tag in tags]) if not tag_counts: print('[No tags found in journal.]') elif min(tag_counts)[0] == 0: tag_counts = filter(lambda x: x[0] > 1, tag_counts) print('[Removed tags that appear only once.]') for n, tag in sorted(tag_counts, reverse=False): - print("{:20} : {}".format(tag, n)) + print("{0:20} : {1}".format(tag, n)) def touch_journal(filename): """If filename does not exist, touch the file""" if not os.path.exists(filename): - print("[Journal created at {}]".format(filename)) + print("[Journal created at {0}]".format(filename)) open(filename, 'a').close() def update_config(config, new_config, scope): @@ -185,7 +185,7 @@ def cli(): raw = " ".join(args.text).strip() entry = journal.new_entry(raw, args.date) entry.starred = args.star - print("[Entry added to {} journal]").format(journal_name) + print("[Entry added to {0} journal]").format(journal_name) journal.write() # Reading mode diff --git a/requirements.txt b/requirements.txt index 7b7648ac..a4a16eb3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ parsedatetime >= 1.1.2 colorama >= 0.2.5 pycrypto >= 2.6 +argparse==1.2.1 diff --git a/setup.py b/setup.py index bc94ae85..e9208c11 100644 --- a/setup.py +++ b/setup.py @@ -58,6 +58,11 @@ def get_version(filename="jrnl/__init__.py"): if m: return m.group(1) +conditional_dependencies = { + "pyreadline>=2.0": "win32" in sys.platform, + "argparse==1.2.1": sys.version.startswith("2.6") +} + setup( name = "jrnl", version = get_version(), @@ -67,7 +72,7 @@ setup( "parsedatetime>=1.1.2", "colorama>=0.2.5", "pycrypto>=2.6" - ] + ["pyreadline>=2.0"] if "win" in sys.platform else [], + ] + [p for p, cond in conditional_dependencies.items() if cond], long_description=__doc__, entry_points={ 'console_scripts': [ From 52b5b4848d2e3d21b11fa0f3bb3751b95c45172e Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Thu, 18 Apr 2013 11:52:53 +0200 Subject: [PATCH 09/28] Experimental build settings for travis --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a917d223..d3b77e98 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: python python: + - "2.6" - "2.7" -# - "3.2" + - "3.2" install: "pip install -r requirements.txt --use-mirrors" # command to run tests script: nosetests From 8200ebb6fe09b972670be948b73bf79028718e83 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Fri, 19 Apr 2013 14:46:05 +0200 Subject: [PATCH 10/28] Python 3 compatibility --- CHANGELOG.md | 1 + jrnl/Journal.py | 6 +++--- jrnl/__init__.py | 4 ++-- jrnl/install.py | 3 ++- jrnl/jrnl.py | 3 ++- jrnl/util.py | 10 ++++++++++ 6 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 jrnl/util.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 84ba1c93..9338542d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Changelog ### 1.0.4 * [Improved] Python 2.6 compatibility +* [New] Python 3 compatibility ### 1.0.3 (April 17, 2013) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 7c8b8f6d..60be81ff 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # encoding: utf-8 -from Entry import Entry +import Entry import os import parsedatetime.parsedatetime as pdt import re @@ -135,7 +135,7 @@ class Journal(object): # parsing successfull => save old entry and create new one if new_date and current_entry: entries.append(current_entry) - current_entry = Entry(self, date=new_date, title=line[date_length+1:]) + current_entry = Entry.Entry(self, date=new_date, title=line[date_length+1:]) except ValueError: # Happens when we can't parse the start of the line as an date. # In this case, just append line to our body. @@ -271,7 +271,7 @@ class Journal(object): if not date: # Still nothing? Meh, just live in the moment. date = self.parse_date("now") - entry = Entry(self, date, title, body) + entry = Entry.Entry(self, date, title, body) self.entries.append(entry) if sort: self.sort() diff --git a/jrnl/__init__.py b/jrnl/__init__.py index 6716b4ea..b1116eed 100644 --- a/jrnl/__init__.py +++ b/jrnl/__init__.py @@ -12,5 +12,5 @@ __author__ = 'Manuel Ebert' __license__ = 'MIT License' __copyright__ = 'Copyright 2013 Manuel Ebert' -from Journal import Journal -from jrnl import cli +from jrnl.Journal import Journal +from jrnl.jrnl import cli diff --git a/jrnl/install.py b/jrnl/install.py index e0b1d068..ca83b8de 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -6,6 +6,7 @@ import getpass try: import simplejson as json except ImportError: import json import os +import util def module_exists(module_name): """Checks if a module exists and can be imported""" @@ -62,7 +63,7 @@ def install_jrnl(config_path='~/.jrnl_config'): # Where to create the journal? path_query = 'Path to your journal file (leave blank for ~/journal.txt): ' - journal_path = raw_input(path_query).strip() or os.path.expanduser('~/journal.txt') + journal_path = util.py23_input(path_query).strip() or os.path.expanduser('~/journal.txt') default_config['journals']['default'] = os.path.expanduser(journal_path) # Encrypt it? diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index cbe4c3aa..3b88b43e 100755 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -8,6 +8,7 @@ """ import Journal +import util import exporters import install import os @@ -174,7 +175,7 @@ def cli(): if config['editor']: raw = get_text_from_editor(config) else: - raw = raw_input("[Compose Entry] ") + raw = util.py23_input("[Compose Entry] ") if raw: args.text = [raw] else: diff --git a/jrnl/util.py b/jrnl/util.py new file mode 100644 index 00000000..391cbc17 --- /dev/null +++ b/jrnl/util.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +# encoding: utf-8 +import sys + +def py23_input(msg): + if sys.version_info[0] == 3: + try: return input(msg) + except SyntaxError: return "" + else: + return raw_input(msg) From 9f33c9c9c4418ed1ebead9c1a4ccac549e94eb30 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Fri, 19 Apr 2013 15:19:21 +0200 Subject: [PATCH 11/28] Fixes for Python 3 Support --- jrnl/Journal.py | 17 ++++++++++++----- jrnl/__init__.py | 4 ++-- jrnl/install.py | 4 +++- jrnl/jrnl.py | 16 +++++++++++----- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 60be81ff..a95207f5 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -1,7 +1,9 @@ #!/usr/bin/env python # encoding: utf-8 -import Entry +try: from . import Entry +except (SystemError, ValueError): import Entry +import codecs import os import parsedatetime.parsedatetime as pdt import re @@ -14,8 +16,9 @@ import readline, glob try: from Crypto.Cipher import AES from Crypto.Random import random, atfork + crypto_installed = True except ImportError: - pass + crypto_installed = False try: import pyreadline as readline except ImportError: @@ -63,6 +66,8 @@ class Journal(object): def _decrypt(self, cipher): """Decrypts a cipher string using self.key as the key and the first 16 byte of the cipher as the IV""" + if not crypto_installed: + sys.exit("Error: PyCrypto is not installed.") if not cipher: return "" crypto = AES.new(self.key, AES.MODE_CBC, cipher[:16]) @@ -78,6 +83,8 @@ class Journal(object): def _encrypt(self, plain): """Encrypt a plaintext string using self.key as the key""" + if not crypto_installed: + sys.exit("Error: PyCrypto is not installed.") atfork() # A seed for PyCrypto iv = ''.join(chr(random.randint(0, 0xFF)) for i in range(16)) crypto = AES.new(self.key, AES.MODE_CBC, iv) @@ -90,14 +97,14 @@ class Journal(object): def make_key(self, prompt="Password: "): """Creates an encryption key from the default password or prompts for a new password.""" password = self.config['password'] or getpass.getpass(prompt) - self.key = hashlib.sha256(password).digest() + self.key = hashlib.sha256(password.encode('utf-8')).digest() def open(self, filename=None): """Opens the journal file defined in the config and parses it into a list of Entries. Entries have the form (date, title, body).""" filename = filename or self.config['journal'] journal = None - with open(filename) as f: + with codecs.open(filename, "r", "utf-8") as f: journal = f.read() if self.config['encrypt']: decrypted = None @@ -174,7 +181,7 @@ class Journal(object): journal = "\n".join([str(e) for e in self.entries]) if self.config['encrypt']: journal = self._encrypt(journal) - with open(filename, 'w') as journal_file: + with codecs.open(filename, 'w', "utf-8") as journal_file: journal_file.write(journal) def sort(self): diff --git a/jrnl/__init__.py b/jrnl/__init__.py index b1116eed..a450d39f 100644 --- a/jrnl/__init__.py +++ b/jrnl/__init__.py @@ -12,5 +12,5 @@ __author__ = 'Manuel Ebert' __license__ = 'MIT License' __copyright__ = 'Copyright 2013 Manuel Ebert' -from jrnl.Journal import Journal -from jrnl.jrnl import cli +from . import Journal +from . import jrnl diff --git a/jrnl/install.py b/jrnl/install.py index ca83b8de..acb519a9 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -6,7 +6,9 @@ import getpass try: import simplejson as json except ImportError: import json import os -import util +try: from . import util +except (SystemError, ValueError): import util + def module_exists(module_name): """Checks if a module exists and can be imported""" diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index 3b88b43e..f19e8da7 100755 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -7,10 +7,16 @@ license: MIT, see LICENSE for more details. """ -import Journal -import util -import exporters -import install +try: + from . import Journal + from . import util + from . import exporters + from . import install +except (SystemError, ValueError): + import Journal + import util + import exporters + import install import os import tempfile import subprocess @@ -186,7 +192,7 @@ def cli(): raw = " ".join(args.text).strip() entry = journal.new_entry(raw, args.date) entry.starred = args.star - print("[Entry added to {0} journal]").format(journal_name) + print("[Entry added to {0} journal]".format(journal_name)) journal.write() # Reading mode From 8865816d60aca2a96e53fa9b0e2216c4aea133ad Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Fri, 19 Apr 2013 16:58:19 +0200 Subject: [PATCH 12/28] Updated readme and changelog and changed config path according to discussion --- CHANGELOG.md | 1 + README.md | 2 +- jrnl/jrnl.py | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9338542d..6288f3fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Changelog * [Improved] Python 2.6 compatibility * [New] Python 3 compatibility +* [New] Respects the `XDG_CONFIG_HOME` environment variable for storing your configuration file ### 1.0.3 (April 17, 2013) diff --git a/README.md b/README.md index f3c1d2ba..60040080 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ will replace your encrypted journal file by a Journal in plain text. You can als Advanced usages -------------- -The first time launched, _jrnl_ will create a file called `.jrnl_config` in your home directory. +The first time launched, _jrnl_ will create a file configuration file at `~/.jrnl_config` or, if the `XDG_CONFIG_HOME` environment variable is set, `$XDG_CONFIG_HOME/jrnl`. ### .jrnl_config diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index 72e97fa7..182eae00 100755 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -31,7 +31,8 @@ __version__ = '1.0.0-rc1' __author__ = 'Manuel Ebert, Stephan Gabler' __license__ = 'MIT' -CONFIG_PATH = os.environ.get('JRNL_CONFIG', os.path.expanduser('~/.jrnl_config')) +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") def parse_args(): From 3abe14bab10ecbed96e41753dc55a3c62b867213 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Fri, 19 Apr 2013 17:32:32 +0200 Subject: [PATCH 13/28] Better utf8 support --- CHANGELOG.md | 3 ++- jrnl/Journal.py | 14 ++++++++++---- jrnl/jrnl.py | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6288f3fc..24dfbd6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,9 @@ Changelog ### 1.0.4 * [Improved] Python 2.6 compatibility +* [Improved] Better utf-8 support * [New] Python 3 compatibility -* [New] Respects the `XDG_CONFIG_HOME` environment variable for storing your configuration file +* [New] Respects the `XDG_CONFIG_HOME` environment variable for storing your configuration file (Thanks [evaryont](https://github.com/evaryont)) ### 1.0.3 (April 17, 2013) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index a95207f5..7b1d10dc 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -104,9 +104,9 @@ class Journal(object): Entries have the form (date, title, body).""" filename = filename or self.config['journal'] journal = None - with codecs.open(filename, "r", "utf-8") as f: - journal = f.read() if self.config['encrypt']: + with open(filename, "rb") as f: + journal = f.read() decrypted = None attempts = 0 while decrypted is None: @@ -121,6 +121,9 @@ class Journal(object): print("Extremely wrong password.") sys.exit(-1) journal = decrypted + else: + with codecs.open(filename, "r", "utf-8") as f: + journal = f.read() return journal def parse(self, journal): @@ -181,8 +184,11 @@ class Journal(object): journal = "\n".join([str(e) for e in self.entries]) if self.config['encrypt']: journal = self._encrypt(journal) - with codecs.open(filename, 'w', "utf-8") as journal_file: - journal_file.write(journal) + with open(filename, 'wb') as journal_file: + journal_file.write(journal) + else: + with codecs.open(filename, 'w', "utf-8") as journal_file: + journal_file.write(journal) def sort(self): """Sorts the Journal's entries by date""" diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index 182eae00..20fa7e0f 100755 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -32,7 +32,7 @@ __author__ = 'Manuel Ebert, Stephan Gabler' __license__ = 'MIT' 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')) +CONFIG_PATH = os.path.join(xdg_config, "jrnl") if xdg_config else os.path.expanduser('~/.jrnl_config') PYCRYPTO = install.module_exists("Crypto") def parse_args(): From dbddb7d6c9571b3031fc99238a9fa716aa26d909 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Fri, 19 Apr 2013 17:58:07 +0200 Subject: [PATCH 14/28] Build status --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 60040080..3be408a8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -jrnl +jrnl [![Build Status](https://travis-ci.org/maebert/jrnl.png?branch=master)](https://travis-ci.org/maebert/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 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. @@ -222,5 +222,5 @@ Known Issues ------------ - The Windows shell prior to Windows 7 has issues with unicode encoding. If you want to use non-ascii characters, change the codepage with `chcp 1252` before using `jrnl` (Thanks to Yves Pouplard for solving this!) -- _jrnl_ relies on the `PyCrypto` package to encrypt journals, which has some known problems with installing within virtual environments. If you want to install __jrnl__ within a virtual environment, you need to [install PyCyrypto manually](https://www.dlitz.net/software/pycrypto/) first. +- _jrnl_ relies on the `PyCrypto` package to encrypt journals, which has some known problems with installing on Windows and within virtual environments. If you have trouble installing __jrnl__, [install PyCyrypto manually](https://www.dlitz.net/software/pycrypto/) first. From dab192db863fdd694bb0adbce10fa2dd6c05353b Mon Sep 17 00:00:00 2001 From: Niko Wenselowski Date: Sun, 21 Apr 2013 22:03:49 +0200 Subject: [PATCH 15/28] Make the cli work again. --- jrnl/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jrnl/__init__.py b/jrnl/__init__.py index a450d39f..ca93c1c9 100644 --- a/jrnl/__init__.py +++ b/jrnl/__init__.py @@ -14,3 +14,4 @@ __copyright__ = 'Copyright 2013 Manuel Ebert' from . import Journal from . import jrnl +from .jrnl import cli From 56a9c0bbccf3b8f484b9369679e244cc00563b72 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Mon, 22 Apr 2013 09:58:45 +0200 Subject: [PATCH 16/28] pycrypto back to extras. Fixes 68 --- CHANGELOG.md | 1 - README.md | 6 +++++- jrnl/__init__.py | 2 +- setup.py | 6 ++++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24dfbd6c..91fd3af6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,6 @@ Changelog ### 1.0.3 (April 17, 2013) -* [Improved] Installs pycrypto by default * [Improved] Removed clint in favour of colorama * [Fixed] Fixed a bug where showing tags failed when no tags are defined. * [Fixed] Improvements to config parsing (Thanks [alapolloni](https://github.com/alapolloni)) diff --git a/README.md b/README.md index 3be408a8..e0bbfca3 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,11 @@ Install _jrnl_ using pip: pip install jrnl -Alternatively, install manually by cloning the repository: +Or, if you want the option to encrypt your journal, + + pip install jrnl[encrypted] + +To install `pycrypto` as well (Note: this requires a `gcc` compiler. You can also [install PyCyrypto manually](https://www.dlitz.net/software/pycrypto/) first)). Alternatively, install _jrnl_ manually by cloning the repository: git clone git://github.com/maebert/jrnl.git cd jrnl diff --git a/jrnl/__init__.py b/jrnl/__init__.py index ca93c1c9..bc168105 100644 --- a/jrnl/__init__.py +++ b/jrnl/__init__.py @@ -7,7 +7,7 @@ jrnl is a simple journal application for your command line. """ __title__ = 'jrnl' -__version__ = '1.0.3' +__version__ = '1.0.4' __author__ = 'Manuel Ebert' __license__ = 'MIT License' __copyright__ = 'Copyright 2013 Manuel Ebert' diff --git a/setup.py b/setup.py index e9208c11..2b0bbd11 100644 --- a/setup.py +++ b/setup.py @@ -70,9 +70,11 @@ setup( packages = ['jrnl'], install_requires = [ "parsedatetime>=1.1.2", - "colorama>=0.2.5", - "pycrypto>=2.6" + "colorama>=0.2.5" ] + [p for p, cond in conditional_dependencies.items() if cond], + extras_require = { + "encrypted": "pycrypto>=2.6" + }, long_description=__doc__, entry_points={ 'console_scripts': [ From 4528d9f612f05977ccb43ef444915e228f239e69 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Tue, 23 Apr 2013 09:13:00 -0700 Subject: [PATCH 17/28] Conditional import for pyreadline Fixes #71 --- jrnl/Journal.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 7b1d10dc..991631bc 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -19,10 +19,8 @@ try: crypto_installed = True except ImportError: crypto_installed = False -try: - import pyreadline as readline -except ImportError: - import readline +if "win32" in sys.platform: import pyreadline as readline +else: import readline import hashlib import getpass try: From 1417305c2ed9d06209de5c5e41bf81f3ebaa1db5 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Tue, 23 Apr 2013 09:13:06 -0700 Subject: [PATCH 18/28] Formatting --- jrnl/Journal.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 991631bc..bcc09adc 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -31,6 +31,7 @@ except ImportError: import plistlib import uuid + class Journal(object): def __init__(self, **kwargs): self.config = { @@ -48,9 +49,9 @@ class Journal(object): # Set up date parser consts = pdt.Constants() - consts.DOWParseStyle = -1 # "Monday" will be either today or the last Monday + consts.DOWParseStyle = -1 # "Monday" will be either today or the last Monday self.dateparse = pdt.Calendar(consts) - self.key = None # used to decrypt and encrypt the journal + self.key = None # used to decrypt and encrypt the journal journal_txt = self.open() self.entries = self.parse(journal_txt) @@ -74,7 +75,7 @@ class Journal(object): except ValueError: print("ERROR: Your journal file seems to be corrupted. You do have a backup, don't you?") sys.exit(-1) - if plain[-1] != " ": # Journals are always padded + if plain[-1] != " ": # Journals are always padded return None else: return plain @@ -83,12 +84,12 @@ class Journal(object): """Encrypt a plaintext string using self.key as the key""" if not crypto_installed: sys.exit("Error: PyCrypto is not installed.") - atfork() # A seed for PyCrypto + atfork() # A seed for PyCrypto iv = ''.join(chr(random.randint(0, 0xFF)) for i in range(16)) crypto = AES.new(self.key, AES.MODE_CBC, iv) if len(plain) % 16 != 0: plain += " " * (16 - len(plain) % 16) - else: # Always pad so we can detect properly decrypted files :) + else: # Always pad so we can detect properly decrypted files :) plain += " " * 16 return iv + crypto.encrypt(plain) @@ -112,7 +113,7 @@ class Journal(object): decrypted = self._decrypt(journal) if decrypted is None: attempts += 1 - self.config['password'] = None # This password doesn't work. + self.config['password'] = None # This password doesn't work. if attempts < 3: print("Wrong password, try again.") else: @@ -160,7 +161,7 @@ class Journal(object): """Prettyprints the journal's entries""" sep = "\n" pp = sep.join([e.pprint() for e in self.entries]) - if self.config['highlight']: # highlight tags + if self.config['highlight']: # highlight tags if self.search_tags: for tag in self.search_tags: tagre = re.compile(re.escape(tag), re.IGNORECASE) @@ -176,17 +177,17 @@ class Journal(object): def __repr__(self): return "" % len(self.entries) - def write(self, filename = None): + def write(self, filename=None): """Dumps the journal into the config file, overwriting it""" filename = filename or self.config['journal'] journal = "\n".join([str(e) for e in self.entries]) if self.config['encrypt']: journal = self._encrypt(journal) with open(filename, 'wb') as journal_file: - journal_file.write(journal) + journal_file.write(journal) else: with codecs.open(filename, 'w', "utf-8") as journal_file: - journal_file.write(journal) + journal_file.write(journal) def sort(self): """Sorts the Journal's entries by date""" @@ -243,10 +244,10 @@ class Journal(object): date, flag = self.dateparse.parse(date) - if not flag: # Oops, unparsable. + if not flag: # Oops, unparsable. return None - if flag is 1: # Date found, but no time. Use the default time. + if flag is 1: # Date found, but no time. Use the default time. date = datetime(*date[:3], hour=self.config['default_hour'], minute=self.config['default_minute']) else: date = datetime(*date[:6]) @@ -268,7 +269,7 @@ class Journal(object): # Split raw text into title and body title_end = len(raw) - for separator in ["\n",". ","? ","! "]: + for separator in ["\n", ". ", "? ", "! "]: sep_pos = raw.find(separator) if 1 < sep_pos < title_end: title_end = sep_pos @@ -277,9 +278,9 @@ class Journal(object): if not date: if title.find(":") > 0: date = self.parse_date(title[:title.find(":")]) - if date: # Parsed successfully, strip that from the raw text + if date: # Parsed successfully, strip that from the raw text title = title[title.find(":")+1:].strip() - 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") entry = Entry.Entry(self, date, title, body) @@ -288,6 +289,7 @@ class Journal(object): self.sort() return entry + class DayOne(Journal): """A special Journal handling DayOne files""" def __init__(self, **kwargs): @@ -314,7 +316,6 @@ class DayOne(Journal): # 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: @@ -331,4 +332,3 @@ class DayOne(Journal): 'UUID': new_uuid } plistlib.writePlist(entry_plist, filename) - From aef9a0ee7eaf7c7315e47756e3a645d2bf99f695 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Tue, 23 Apr 2013 16:29:32 -0700 Subject: [PATCH 19/28] Fixes double readline import Should finally fix #71 --- jrnl/Journal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index bcc09adc..b259d662 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -12,7 +12,7 @@ import time try: import simplejson as json except ImportError: import json import sys -import readline, glob +import glob try: from Crypto.Cipher import AES from Crypto.Random import random, atfork From fde308e4bab11c9397d54078683312fb6fe029f1 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Wed, 8 May 2013 09:56:49 -0700 Subject: [PATCH 20/28] Backwards compatibility with parsedatetime 0.8.7 Closes #76 --- CHANGELOG.md | 10 +++++++--- jrnl/Journal.py | 3 ++- jrnl/__init__.py | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91fd3af6..a0a5e984 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,18 @@ Changelog ========= -### 1.0.4 +#### 1.0.5 + +* Backwards compatibility with `parsedatetime` 0.8.7 + +#### 1.0.4 * [Improved] Python 2.6 compatibility * [Improved] Better utf-8 support * [New] Python 3 compatibility * [New] Respects the `XDG_CONFIG_HOME` environment variable for storing your configuration file (Thanks [evaryont](https://github.com/evaryont)) -### 1.0.3 (April 17, 2013) +#### 1.0.3 (April 17, 2013) * [Improved] Removed clint in favour of colorama * [Fixed] Fixed a bug where showing tags failed when no tags are defined. @@ -16,7 +20,7 @@ Changelog * [Fixed] Fixes readline support on Windows * [Fixed] Smaller fixes and typos -### 1.0.1 (March 12, 2013) +#### 1.0.1 (March 12, 2013) * [Fixed] Requires parsedatetime 1.1.2 or newer diff --git a/jrnl/Journal.py b/jrnl/Journal.py index b259d662..1e8e905f 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -5,7 +5,8 @@ try: from . import Entry except (SystemError, ValueError): import Entry import codecs import os -import parsedatetime.parsedatetime as pdt +try: import parsedatetime.parsedatetime_consts as pdt +except ImportError: import parsedatetime.parsedatetime as pdt import re from datetime import datetime import time diff --git a/jrnl/__init__.py b/jrnl/__init__.py index bc168105..8e629323 100644 --- a/jrnl/__init__.py +++ b/jrnl/__init__.py @@ -7,7 +7,7 @@ jrnl is a simple journal application for your command line. """ __title__ = 'jrnl' -__version__ = '1.0.4' +__version__ = '1.0.5' __author__ = 'Manuel Ebert' __license__ = 'MIT License' __copyright__ = 'Copyright 2013 Manuel Ebert' From 0bf2354bc2ba33e8f5345aa5e3323a1e31241419 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Wed, 8 May 2013 10:08:48 -0700 Subject: [PATCH 21/28] Disables PyICU use for now until proper localization is implemented. Fixes #52 --- jrnl/Journal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 1e8e905f..35f59f3a 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -49,7 +49,7 @@ class Journal(object): self.config.update(kwargs) # Set up date parser - consts = pdt.Constants() + consts = pdt.Constants(usePyICU=False) consts.DOWParseStyle = -1 # "Monday" will be either today or the last Monday self.dateparse = pdt.Calendar(consts) self.key = None # used to decrypt and encrypt the journal From 48c5dd8aa1dc27e300d518576923e79e0a7f6b9b Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sun, 9 Jun 2013 14:48:56 -0700 Subject: [PATCH 22/28] JSON exports tags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #78. This changes the export format to { tags: {tag: count}, entries: [{…}, {…}, …] } --- CHANGELOG.md | 5 ++++- jrnl/__init__.py | 2 +- jrnl/exporters.py | 8 +++++++- jrnl/jrnl.py | 38 ++++++++++++++++++++++---------------- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0a5e984..0f7cd1b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,12 @@ Changelog ========= +#### 1.1.0 + +* [New] JSON export exports tags as well. #### 1.0.5 -* Backwards compatibility with `parsedatetime` 0.8.7 +* [Improved] Backwards compatibility with `parsedatetime` 0.8.7 #### 1.0.4 diff --git a/jrnl/__init__.py b/jrnl/__init__.py index 8e629323..4c3a2b79 100644 --- a/jrnl/__init__.py +++ b/jrnl/__init__.py @@ -7,7 +7,7 @@ jrnl is a simple journal application for your command line. """ __title__ = 'jrnl' -__version__ = '1.0.5' +__version__ = '1.1.0' __author__ = 'Manuel Ebert' __license__ = 'MIT License' __copyright__ = 'Copyright 2013 Manuel Ebert' diff --git a/jrnl/exporters.py b/jrnl/exporters.py index dfca5d78..d7a730d6 100644 --- a/jrnl/exporters.py +++ b/jrnl/exporters.py @@ -3,10 +3,16 @@ try: import simplejson as json except ImportError: import json +from jrnl import get_tags_count def to_json(journal): """Returns a JSON representation of the Journal.""" - return json.dumps([e.to_dict() for e in journal.entries], indent=2) + tags = get_tags_count(journal) + result = { + "tags": dict((tag, count) for count, tag in tags), + "entries": [e.to_dict() for e in journal.entries] + } + return json.dumps(result, indent=2) def to_md(journal): """Returns a markdown representation of the Journal""" diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index 20fa7e0f..5b705d17 100755 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -104,23 +104,29 @@ def decrypt(journal, filename=None): journal.write(filename) print("Journal decrypted to {0}.".format(filename or journal.config['journal'])) + +def get_tags_count(journal): + """Returns a set of tuples (count, tag) for all tags present in the journal.""" + # Astute reader: should the following line leave you as puzzled as me the first time + # I came across this construction, worry not and embrace the ensuing moment of enlightment. + tags = [tag + for entry in journal.entries + for tag in set(entry.tags) + ] + # To be read: [for entry in journal.entries: for tag in set(entry.tags): tag] + tag_counts = set([(tags.count(tag), tag) for tag in tags]) + return tag_counts + def print_tags(journal): - """Prints a list of all tags and the number of occurances.""" - # Astute reader: should the following line leave you as puzzled as me the first time - # I came across this construction, worry not and embrace the ensuing moment of enlightment. - tags = [tag - for entry in journal.entries - for tag in set(entry.tags) - ] - # To be read: [for entry in journal.entries: for tag in set(entry.tags): tag] - tag_counts = set([(tags.count(tag), tag) for tag in tags]) - if not tag_counts: - print('[No tags found in journal.]') - elif min(tag_counts)[0] == 0: - tag_counts = filter(lambda x: x[0] > 1, tag_counts) - print('[Removed tags that appear only once.]') - for n, tag in sorted(tag_counts, reverse=False): - print("{0:20} : {1}".format(tag, n)) + """Prints a list of all tags and the number of occurances.""" + tag_counts = get_tags_count(journal) + if not tag_counts: + print('[No tags found in journal.]') + elif min(tag_counts)[0] == 0: + tag_counts = filter(lambda x: x[0] > 1, tag_counts) + print('[Removed tags that appear only once.]') + for n, tag in sorted(tag_counts, reverse=False): + print("{0:20} : {1}".format(tag, n)) def touch_journal(filename): From 32f3d23b90a3493ad3b67d8050d5f8f999c63b6d Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sun, 9 Jun 2013 14:49:02 -0700 Subject: [PATCH 23/28] Nicer error message when there is a syntactical error in your config file. --- CHANGELOG.md | 2 ++ jrnl/jrnl.py | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f7cd1b4..02b3e293 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Changelog #### 1.1.0 * [New] JSON export exports tags as well. +* [Improved] Nicer error message when there is a syntactical error in your config file. + #### 1.0.5 * [Improved] Backwards compatibility with `parsedatetime` 0.8.7 diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index 5b705d17..6f071a47 100755 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -150,7 +150,12 @@ def cli(): config = install.install_jrnl(CONFIG_PATH) else: with open(CONFIG_PATH) as f: - config = json.load(f) + try: + config = json.load(f) + except ValueError as e: + print("[There seems to be something wrong with your jrnl config at {}: {}]".format(CONFIG_PATH, e.message)) + print("[Entry was NOT added to your journal]") + sys.exit(-1) install.update_config(config, config_path=CONFIG_PATH) original_config = config.copy() From cf7581c0c01cb53b60f12d7753fff333a48789b8 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sun, 9 Jun 2013 15:07:27 -0700 Subject: [PATCH 24/28] Moves tag export to exporters --- jrnl/exporters.py | 25 ++++++++++++++++++++++++- jrnl/jrnl.py | 33 +-------------------------------- 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/jrnl/exporters.py b/jrnl/exporters.py index d7a730d6..490af6e2 100644 --- a/jrnl/exporters.py +++ b/jrnl/exporters.py @@ -3,7 +3,30 @@ try: import simplejson as json except ImportError: import json -from jrnl import get_tags_count + +def get_tags_count(journal): + """Returns a set of tuples (count, tag) for all tags present in the journal.""" + # Astute reader: should the following line leave you as puzzled as me the first time + # I came across this construction, worry not and embrace the ensuing moment of enlightment. + tags = [tag + for entry in journal.entries + for tag in set(entry.tags) + ] + # To be read: [for entry in journal.entries: for tag in set(entry.tags): tag] + tag_counts = set([(tags.count(tag), tag) for tag in tags]) + return tag_counts + +def to_tag_list(journal): + """Prints a list of all tags and the number of occurances.""" + tag_counts = get_tags_count(journal) + result = "" + if not tag_counts: + return '[No tags found in journal.]' + elif min(tag_counts)[0] == 0: + tag_counts = filter(lambda x: x[0] > 1, tag_counts) + result += '[Removed tags that appear only once.]\n' + result += "\n".join("{0:20} : {1}".format(tag, n) for n, tag in sorted(tag_counts, reverse=False)) + return result def to_json(journal): """Returns a JSON representation of the Journal.""" diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index 6f071a47..4fc368c8 100755 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -25,12 +25,6 @@ import sys try: import simplejson as json except ImportError: import json - -__title__ = 'jrnl' -__version__ = '1.0.0-rc1' -__author__ = 'Manuel Ebert, Stephan Gabler' -__license__ = 'MIT' - 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") @@ -104,31 +98,6 @@ def decrypt(journal, filename=None): journal.write(filename) print("Journal decrypted to {0}.".format(filename or journal.config['journal'])) - -def get_tags_count(journal): - """Returns a set of tuples (count, tag) for all tags present in the journal.""" - # Astute reader: should the following line leave you as puzzled as me the first time - # I came across this construction, worry not and embrace the ensuing moment of enlightment. - tags = [tag - for entry in journal.entries - for tag in set(entry.tags) - ] - # To be read: [for entry in journal.entries: for tag in set(entry.tags): tag] - tag_counts = set([(tags.count(tag), tag) for tag in tags]) - return tag_counts - -def print_tags(journal): - """Prints a list of all tags and the number of occurances.""" - tag_counts = get_tags_count(journal) - if not tag_counts: - print('[No tags found in journal.]') - elif min(tag_counts)[0] == 0: - tag_counts = filter(lambda x: x[0] > 1, tag_counts) - print('[Removed tags that appear only once.]') - for n, tag in sorted(tag_counts, reverse=False): - print("{0:20} : {1}".format(tag, n)) - - def touch_journal(filename): """If filename does not exist, touch the file""" if not os.path.exists(filename): @@ -218,7 +187,7 @@ def cli(): # Various export modes elif args.tags: - print_tags(journal) + print(exporters.to_tag_list(journal)) elif args.json: # export to json print(exporters.to_json(journal)) From 5bb8f9c567f0c70191a56866e6e597ee91ff312d Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sun, 9 Jun 2013 15:55:03 -0700 Subject: [PATCH 25/28] Better Unicode support Closes #72 --- CHANGELOG.md | 1 + jrnl/Entry.py | 10 +++++----- jrnl/Journal.py | 8 ++++---- jrnl/exporters.py | 2 +- jrnl/jrnl.py | 5 +++-- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02b3e293..f028be36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Changelog * [New] JSON export exports tags as well. * [Improved] Nicer error message when there is a syntactical error in your config file. +* [Improved] Unicode support #### 1.0.5 diff --git a/jrnl/Entry.py b/jrnl/Entry.py index 8f3663ab..624b6197 100644 --- a/jrnl/Entry.py +++ b/jrnl/Entry.py @@ -15,16 +15,16 @@ class Entry: def parse_tags(self): fulltext = " ".join([self.title, self.body]).lower() - tags = re.findall(r"([%s]\w+)" % self.journal.config['tagsymbols'], fulltext) + tags = re.findall(ur'([{}]\w+)'.format(self.journal.config['tagsymbols']), fulltext, re.UNICODE) self.tags = set(tags) - def __str__(self): + def __unicode__(self): """Returns a string representation of the entry to be written into a journal file.""" date_str = self.date.strftime(self.journal.config['timeformat']) title = date_str + " " + self.title body = self.body.strip() - return "{title}{sep}{body}\n".format( + return u"{title}{sep}{body}\n".format( title=title, sep="\n" if self.body else "", body=body @@ -50,7 +50,7 @@ class Entry: # Suppress bodies that are just blanks and new lines. has_body = len(self.body) > 20 or not all(char in (" ", "\n") for char in self.body) - return "{title}{sep}{body}\n".format( + return u"{title}{sep}{body}\n".format( title=title, sep="\n" if has_body else "", body=body if has_body else "", @@ -74,7 +74,7 @@ class Entry: space = "\n" md_head = "###" - return "{md} {date}, {title} {body} {space}".format( + return u"{md} {date}, {title} {body} {space}".format( md=md_head, date=date_str, title=self.title, diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 35f59f3a..9cbe41e2 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -158,7 +158,7 @@ class Journal(object): entry.parse_tags() return entries - def __str__(self): + def __unicode__(self): """Prettyprints the journal's entries""" sep = "\n" pp = sep.join([e.pprint() for e in self.entries]) @@ -168,9 +168,9 @@ class Journal(object): tagre = re.compile(re.escape(tag), re.IGNORECASE) pp = re.sub(tagre, lambda match: self._colorize(match.group(0)), - pp) + pp, re.UNICODE) else: - pp = re.sub(r"([%s]\w+)" % self.config['tagsymbols'], + pp = re.sub(ur"(?u)([{}]\w+)".format(self.config['tagsymbols']), lambda match: self._colorize(match.group(0)), pp) return pp @@ -181,7 +181,7 @@ class Journal(object): def write(self, filename=None): """Dumps the journal into the config file, overwriting it""" filename = filename or self.config['journal'] - journal = "\n".join([str(e) for e in self.entries]) + journal = "\n".join([unicode(e) for e in self.entries]) if self.config['encrypt']: journal = self._encrypt(journal) with open(filename, 'wb') as journal_file: diff --git a/jrnl/exporters.py b/jrnl/exporters.py index 490af6e2..4a4b0245 100644 --- a/jrnl/exporters.py +++ b/jrnl/exporters.py @@ -25,7 +25,7 @@ def to_tag_list(journal): elif min(tag_counts)[0] == 0: tag_counts = filter(lambda x: x[0] > 1, tag_counts) result += '[Removed tags that appear only once.]\n' - result += "\n".join("{0:20} : {1}".format(tag, n) for n, tag in sorted(tag_counts, reverse=False)) + result += "\n".join(u"{0:20} : {1}".format(tag, n) for n, tag in sorted(tag_counts, reverse=False)) return result def to_json(journal): diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index 4fc368c8..2af9b60c 100755 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -171,7 +171,8 @@ def cli(): # Writing mode if mode_compose: raw = " ".join(args.text).strip() - entry = journal.new_entry(raw, args.date) + unicode_raw = raw.decode(sys.getfilesystemencoding()) + entry = journal.new_entry(unicode_raw, args.date) entry.starred = args.star print("[Entry added to {0} journal]".format(journal_name)) journal.write() @@ -183,7 +184,7 @@ def cli(): strict=args.strict, short=args.short) journal.limit(args.limit) - print(journal) + print(unicode(journal)) # Various export modes elif args.tags: From b4a28d3c7b644fc70437fa129825b94a48956489 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sun, 9 Jun 2013 15:59:11 -0700 Subject: [PATCH 26/28] Changelog cleanup --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f028be36..c4de8d5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,11 +38,11 @@ Changelog * [Fixed] A bug where jrnl would not add entries without timestamp * [Fixed] Support for parsedatetime 1.x -### 0.3.2 (July 5, 2012) +#### 0.3.2 (July 5, 2012) * [Improved] Converts `\n` to new lines (if using directly on a command line, make sure to wrap your entry with quotes). -### 0.3.1 (June 16, 2012) +#### 0.3.1 (June 16, 2012) * [Improved] Supports deleting of last entry. * [Fixed] Fixes a bug where --encrypt or --decrypt without a target file would not work. From cd2eebab345e576819ddd40b125172d49124293a Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sun, 9 Jun 2013 15:59:49 -0700 Subject: [PATCH 27/28] Typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0bbfca3..903c3618 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ With jrnl --tags -you'll get a list of all tags you used in your journal, sorted by most frequent. Tags occuring several times in the same entry are only counted as one. +you'll get a list of all tags you used in your journal, sorted by most frequent. Tags occurring several times in the same entry are only counted as one. ### JSON export From 868d29a92ea2e5672f7fd3c9dc10c54550b4f248 Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Sun, 9 Jun 2013 16:04:17 -0700 Subject: [PATCH 28/28] Allow python 3 to fail on travis --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index d3b77e98..b1ed5780 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,11 @@ python: - "2.6" - "2.7" - "3.2" + - "3.3" install: "pip install -r requirements.txt --use-mirrors" # command to run tests script: nosetests +matrix: + allow_failures: # python 3 support for travis is shaky.... + - python: 3.2 + - python: 3.3