diff --git a/.travis.yml b/.travis.yml index a917d223..b1ed5780 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,13 @@ language: python python: + - "2.6" - "2.7" -# - "3.2" + - "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 diff --git a/CHANGELOG.md b/CHANGELOG.md index b96c6de2..c4de8d5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,32 @@ Changelog ========= -### 1.0.1 (March 12, 2013) +#### 1.1.0 + +* [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 + +* [Improved] 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) + +* [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) * [Fixed] Requires parsedatetime 1.1.2 or newer @@ -13,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. diff --git a/README.md b/README.md index 3e581e08..903c3618 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. @@ -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 @@ -44,7 +48,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: @@ -104,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 @@ -137,7 +141,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 @@ -150,7 +154,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 @@ -168,7 +172,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: @@ -222,4 +226,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 on Windows and within virtual environments. If you have trouble installing __jrnl__, [install PyCyrypto manually](https://www.dlitz.net/software/pycrypto/) first. + 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 8147b536..9cbe41e2 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -1,30 +1,38 @@ #!/usr/bin/env python # encoding: utf-8 -from Entry import Entry +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 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 + crypto_installed = True except ImportError: - pass + crypto_installed = False +if "win32" in sys.platform: import pyreadline as readline +else: import readline import hashlib import getpass try: - import clint + import colorama + colorama.init() except ImportError: - clint = None + colorama = None import plistlib import uuid + class Journal(object): def __init__(self, **kwargs): self.config = { @@ -41,23 +49,25 @@ class Journal(object): self.config.update(kwargs) # Set up date parser - consts = pdt.Constants() - consts.DOWParseStyle = -1 # "Monday" will be either today or the last Monday + 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 + self.key = None # used to decrypt and encrypt the journal journal_txt = self.open() 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 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]) @@ -66,35 +76,37 @@ 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 def _encrypt(self, plain): """Encrypt a plaintext string using self.key as the key""" - atfork() # A seed for PyCrypto + 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) 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) 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: - journal = f.read() if self.config['encrypt']: + with open(filename, "rb") as f: + journal = f.read() decrypted = None attempts = 0 while decrypted is None: @@ -102,13 +114,16 @@ 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: 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): @@ -130,7 +145,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. @@ -143,33 +158,36 @@ 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]) - 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) pp = re.sub(tagre, - lambda match: self._colorize(match.group(0), 'cyan'), - pp) + lambda match: self._colorize(match.group(0)), + pp, re.UNICODE) else: - pp = re.sub(r"([%s]\w+)" % self.config['tagsymbols'], - lambda match: self._colorize(match.group(0), 'cyan'), + pp = re.sub(ur"(?u)([{}]\w+)".format(self.config['tagsymbols']), + lambda match: self._colorize(match.group(0)), pp) return pp 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]) + journal = "\n".join([unicode(e) for e in self.entries]) if self.config['encrypt']: journal = self._encrypt(journal) - with open(filename, 'w') as journal_file: + 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): @@ -227,10 +245,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]) @@ -252,7 +270,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 @@ -261,17 +279,18 @@ 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(self, date, title, body) + entry = Entry.Entry(self, date, title, body) self.entries.append(entry) if sort: self.sort() return entry + class DayOne(Journal): """A special Journal handling DayOne files""" def __init__(self, **kwargs): @@ -298,7 +317,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: @@ -315,4 +333,3 @@ class DayOne(Journal): 'UUID': new_uuid } plistlib.writePlist(entry_plist, filename) - diff --git a/jrnl/__init__.py b/jrnl/__init__.py index 2c6ba2a0..4c3a2b79 100644 --- a/jrnl/__init__.py +++ b/jrnl/__init__.py @@ -1,5 +1,17 @@ #!/usr/bin/env python # encoding: utf-8 -from Journal import Journal -from jrnl import cli + +""" +jrnl is a simple journal application for your command line. +""" + +__title__ = 'jrnl' +__version__ = '1.1.0' +__author__ = 'Manuel Ebert' +__license__ = 'MIT License' +__copyright__ = 'Copyright 2013 Manuel Ebert' + +from . import Journal +from . import jrnl +from .jrnl import cli diff --git a/jrnl/exporters.py b/jrnl/exporters.py index dfca5d78..4a4b0245 100644 --- a/jrnl/exporters.py +++ b/jrnl/exporters.py @@ -4,9 +4,38 @@ try: import simplejson as json except ImportError: import json +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(u"{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.""" - 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/install.py b/jrnl/install.py index 90b5939e..acb519a9 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -6,6 +6,9 @@ import getpass try: import simplejson as json except ImportError: import json import os +try: from . import util +except (SystemError, ValueError): import util + def module_exists(module_name): """Checks if a module exists and can be imported""" @@ -62,7 +65,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? @@ -77,8 +80,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 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 open(default_config['journals']['default'], 'a').close() # Touch to make sure it's there @@ -91,4 +94,4 @@ def install_jrnl(config_path='~/.jrnl_config'): config['password'] = password return config - \ No newline at end of file + diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index 74520f3a..2af9b60c 100755 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -7,9 +7,16 @@ license: MIT, see LICENSE for more details. """ -import Journal -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 @@ -18,13 +25,8 @@ 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' - -CONFIG_PATH = 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(): @@ -87,36 +89,19 @@ 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'])) - -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 = {(tags.count(tag), tag) for tag in tags} - if 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("Journal decrypted to {0}.".format(filename or journal.config['journal'])) 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): @@ -134,7 +119,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() @@ -172,7 +162,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: @@ -181,9 +171,10 @@ 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 {} journal]").format(journal_name) + print("[Entry added to {0} journal]".format(journal_name)) journal.write() # Reading mode @@ -193,11 +184,11 @@ def cli(): strict=args.strict, short=args.short) journal.limit(args.limit) - print(journal) + print(unicode(journal)) # 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)) 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) diff --git a/requirements.txt b/requirements.txt index 9b78f344..a4a16eb3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ -clint >= 0.3.1 -parsedatetime == 1.1.2 +parsedatetime >= 1.1.2 +colorama >= 0.2.5 +pycrypto >= 2.6 +argparse==1.2.1 diff --git a/setup.py b/setup.py index ab80113b..2b0bbd11 100644 --- a/setup.py +++ b/setup.py @@ -43,23 +43,37 @@ 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="jrnl/__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) + +conditional_dependencies = { + "pyreadline>=2.0": "win32" in sys.platform, + "argparse==1.2.1": sys.version.startswith("2.6") +} + 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 = ["parsedatetime >= 1.1.2"], + install_requires = [ + "parsedatetime>=1.1.2", + "colorama>=0.2.5" + ] + [p for p, cond in conditional_dependencies.items() if cond], extras_require = { - 'encryption': ["pycrypto"], - 'highlight': ["clint"] + "encrypted": "pycrypto>=2.6" }, long_description=__doc__, entry_points={ @@ -68,7 +82,7 @@ setup( ], }, classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Intended Audience :: End Users/Desktop', 'License :: OSI Approved :: MIT License', @@ -81,7 +95,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",