Merge pull request #85 from maebert/tests

New unit testing suit using behave and several smaller fixes
This commit is contained in:
Manuel Ebert 2013-07-23 21:14:06 -07:00
commit 0957622d67
24 changed files with 429 additions and 88 deletions

View file

@ -3,9 +3,13 @@ python:
- "2.6"
- "2.7"
- "3.3"
install: "pip install -r requirements.txt --use-mirrors"
install:
- "pip install -q -r requirements.txt --use-mirrors"
- "pip install -q behave"
# command to run tests
script: nosetests
script:
- python --version
- behave
matrix:
allow_failures: # python 3 support for travis is shaky....
- python: 3.3

View file

@ -1,6 +1,18 @@
Changelog
=========
### 1.4.1
* [Fixed] Tagging works again
### 1.4.0
* [Improved] Unifies encryption between Python 2 and 3. If you have problems reading encrypted journals afterwards, first decrypt your journal with the __old__ jrnl version (install with `pip install jrnl==1.3.1`, then `jrnl --decrypt`), upgrade jrnl (`pip install jrnl --upgrade`) and encrypt it again (`jrnl --encrypt`).
### 1.3.2
* [Improved] Everything that is not direct output of jrnl will be written stderr to improve integration
### 1.3.0
* [New] Export to multiple files

36
features/core.feature Normal file
View file

@ -0,0 +1,36 @@
Feature: Basic reading and writing to a journal
Scenario: Loading a sample journal
Given we use the config "basic.json"
When we run "jrnl -n 2"
Then we should get no error
and the output should be
"""
2013-06-09 15:39 My first entry.
| Everything is alright
2013-06-10 15:40 Life is good.
| But I'm better.
"""
Scenario: Writing an entry from command line
Given we use the config "basic.json"
When we run "jrnl 23 july 2013: A cold and stormy day. I ate crisps on the sofa."
Then we should see the message "Entry added"
When we run "jrnl -n 1"
Then the output should contain "2013-07-23 09:00 A cold and stormy day."
Scenario: Emoji support
Given we use the config "basic.json"
When we run "jrnl 23 july 2013: 🌞 sunny day. Saw an 🐘"
Then we should see the message "Entry added"
When we run "jrnl -n 1"
Then the output should contain "🌞"
and the output should contain "🐘"
Scenario: Writing an entry at the prompt
Given we use the config "basic.json"
When we run "jrnl" and enter "25 jul 2013: I saw Elvis. He's alive."
Then we should get no error
and the journal should contain "2013-07-25 09:00 I saw Elvis."
and the journal should contain "He's alive."

View file

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

View file

@ -0,0 +1,14 @@
{
"default_hour": 9,
"timeformat": "%Y-%m-%d %H:%M",
"linewrap": 80,
"encrypt": true,
"editor": "",
"default_minute": 0,
"highlight": true,
"password": "",
"journals": {
"default": "features/journals/encrypted.journal"
},
"tagsymbols": "@"
}

View file

@ -0,0 +1,14 @@
{
"default_hour": 9,
"timeformat": "%Y-%m-%d %H:%M",
"linewrap": 80,
"encrypt": true,
"editor": "",
"default_minute": 0,
"highlight": true,
"password": "bad doggie no biscuit",
"journals": {
"default": "features/journals/encrypted.journal"
},
"tagsymbols": "@"
}

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": {
"default": "features/journals/simple.journal",
"work": "features/journals/work.journal",
"ideas": "features/journals/nothing.journal"
},
"tagsymbols": "@"
}

View file

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

Binary file not shown.

View file

@ -0,0 +1,5 @@
2013-06-09 15:39 My first entry.
Everything is alright
2013-06-10 15:40 Life is good.
But I'm better.

View file

@ -0,0 +1,7 @@
2013-06-09 15:39 I have an @idea:
(1) write a command line @journal software
(2) ???
(3) PROFIT!
2013-06-10 15:40 I met with @dan.
As alway's he shared his latest @idea on how to rule the world with me.

View file

View file

@ -0,0 +1,29 @@
Feature: Multiple journals
Scenario: Loading an encrypted journal
Given we use the config "encrypted.json"
When we run "jrnl -n 1" and enter "bad doggie no biscuit"
Then we should see the message "Password"
and the output should contain "2013-06-10 15:40 Life is good"
Scenario: Loading an encrypted journal with password in config
Given we use the config "encrypted_with_pw.json"
When we run "jrnl -n 1"
Then the output should contain "2013-06-10 15:40 Life is good"
Scenario: Decrypting a journal
Given we use the config "encrypted.json"
When we run "jrnl --decrypt" and enter "bad doggie no biscuit"
Then we should see the message "Journal decrypted"
and the journal should have 2 entries
and the config should have "encrypt" set to "bool:False"
Scenario: Encrypting a journal
Given we use the config "basic.json"
When we run "jrnl --encrypt" and enter "swordfish"
Then we should see the message "Journal encrypted"
and the config should have "encrypt" set to "bool:True"
When we run "jrnl -n 1" and enter "swordfish"
Then we should see the message "Password"
and the output should contain "2013-06-10 15:40 Life is good"

29
features/environment.py Normal file
View file

@ -0,0 +1,29 @@
from behave import *
import shutil
import os
from jrnl import jrnl
try:
from io import StringIO
except ImportError:
from cStringIO import StringIO
def before_scenario(context, scenario):
"""Before each scenario, backup all config and journal test data."""
context.messages = StringIO()
jrnl.util.STDERR = context.messages
jrnl.util.TEST = True
for folder in ("configs", "journals"):
original = os.path.join("features", "data", folder)
working_dir = os.path.join("features", folder)
if not os.path.exists(working_dir):
os.mkdir(working_dir)
for filename in os.listdir(original):
shutil.copy2(os.path.join(original, filename), working_dir)
def after_scenario(context, scenario):
"""After each scenario, restore all test data and remove working_dirs."""
context.messages.close()
context.messages = None
for folder in ("configs", "journals"):
working_dir = os.path.join("features", folder)
shutil.rmtree(working_dir)

View file

@ -0,0 +1,36 @@
Feature: Multiple journals
Scenario: Loading a config with two journals
Given we use the config "multiple.json"
Then journal "default" should have 2 entries
and journal "work" should have 0 entries
Scenario: Write to default config by default
Given we use the config "multiple.json"
When we run "jrnl this goes to default"
Then journal "default" should have 3 entries
and journal "work" should have 0 entries
Scenario: Write to specified journal
Given we use the config "multiple.json"
When we run "jrnl work a long day in the office"
Then journal "default" should have 2 entries
and journal "work" should have 1 entry
Scenario: Tell user which journal was used
Given we use the config "multiple.json"
When we run "jrnl work a long day in the office"
Then we should see the message "Entry added to work journal"
Scenario: Write to specified journal with a timestamp
Given we use the config "multiple.json"
When we run "jrnl work 23 july 2012: a long day in the office"
Then journal "default" should have 2 entries
and journal "work" should have 1 entry
and journal "work" should contain "2012-07-23"
Scenario: Create new journals as required
Given we use the config "multiple.json"
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

106
features/steps/core.py Normal file
View file

@ -0,0 +1,106 @@
from behave import *
from jrnl import jrnl, Journal
import os
import sys
import json
try:
from io import StringIO
except ImportError:
from cStringIO import StringIO
def read_journal(journal_name="default"):
with open(jrnl.CONFIG_PATH) as config_file:
config = json.load(config_file)
with open(config['journals'][journal_name]) as journal_file:
journal = journal_file.read()
return journal
def open_journal(journal_name="default"):
with open(jrnl.CONFIG_PATH) as config_file:
config = json.load(config_file)
journals = config['journals']
if type(journals) is dict: # We can override the default config on a by-journal basis
config['journal'] = journals.get(journal_name)
else: # But also just give them a string to point to the journal file
config['journal'] = journal
return Journal.Journal(**config)
@given('we use the config "{config_file}"')
def set_config(context, config_file):
full_path = os.path.join("features/configs", config_file)
jrnl.CONFIG_PATH = os.path.abspath(full_path)
@when('we run "{command}" and enter')
@when('we run "{command}" and enter "{inputs}"')
def run_with_input(context, command, inputs=None):
text = inputs or context.text
args = command.split()[1:]
buffer = StringIO(text.strip())
jrnl.util.STDIN = buffer
jrnl.cli(args)
@when('we run "{command}"')
def run(context, command):
args = command.split()[1:]
jrnl.cli(args or None)
@then('we should get no error')
def no_error(context):
assert context.failed is False
@then('the output should be')
def check_output(context):
text = context.text.strip().splitlines()
out = context.stdout_capture.getvalue().strip().splitlines()
for line_text, line_out in zip(text, out):
assert line_text.strip() == line_out.strip()
@then('the output should contain "{text}"')
def check_output_inline(context, text):
out = context.stdout_capture.getvalue()
assert text in out
@then('we should see the message "{text}"')
def check_message(context, text):
out = context.messages.getvalue()
assert text in out
@then('the journal should contain "{text}"')
@then('journal "{journal_name}" should contain "{text}"')
def check_journal_content(context, text, journal_name="default"):
journal = read_journal(journal_name)
assert text in journal
@then('journal "{journal_name}" should not exist')
def journal_doesnt_exist(context, journal_name="default"):
with open(jrnl.CONFIG_PATH) as config_file:
config = json.load(config_file)
journal_path = config['journals'][journal_name]
assert not os.path.exists(journal_path)
@then('the config should have "{key}" set to "{value}"')
def config_var(context, key, value):
t, value = value.split(":")
value = {
"bool": lambda v: v.lower() == "true",
"int": int,
"str": str
}[t](value)
with open(jrnl.CONFIG_PATH) as config_file:
config = json.load(config_file)
assert key in config
assert config[key] == value
@then('the journal should have {number:d} entries')
@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"):
journal = open_journal(journal_name)
assert len(journal.entries) == number
@then('fail')
def debug_fail(context):
assert False

12
features/tagging.feature Normal file
View file

@ -0,0 +1,12 @@
Feature: Tagging
Scenario: Displaying tags
Given we use the config "tags.json"
When we run "jrnl --tags"
Then we should get no error
and the output should be
"""
@idea : 2
@journal : 1
@dan : 1
"""

View file

@ -15,7 +15,8 @@ class Entry:
def parse_tags(self):
fulltext = " ".join([self.title, self.body]).lower()
tags = re.findall(r'(?u)([{}]\w+)'.format(self.journal.config['tagsymbols']), fulltext, re.UNICODE)
tags = re.findall(r'(?u)([{tags}]\w+)'.format(tags=self.journal.config['tagsymbols']), fulltext, re.UNICODE)
self.tags = tags
return set(tags)
def __unicode__(self):

View file

@ -3,8 +3,8 @@
try: from . import Entry
except (SystemError, ValueError): import Entry
try: from .util import get_local_timezone
except (SystemError, ValueError): from util import get_local_timezone
try: from . import util
except (SystemError, ValueError): import util
import codecs
import os
try: import parsedatetime.parsedatetime_consts as pdt
@ -18,14 +18,13 @@ import sys
import glob
try:
from Crypto.Cipher import AES
from Crypto.Random import random, atfork
from Crypto import Random
crypto_installed = True
except ImportError:
crypto_installed = False
if "win32" in sys.platform: import pyreadline as readline
else: import readline
import hashlib
import getpass
try:
import colorama
colorama.init()
@ -50,7 +49,6 @@ class Journal(object):
'linewrap': 80,
}
self.config.update(kwargs)
# Set up date parser
consts = pdt.Constants(usePyICU=False)
consts.DOWParseStyle = -1 # "Monday" will be either today or the last Monday
@ -77,30 +75,29 @@ class Journal(object):
try:
plain = crypto.decrypt(cipher[16:])
except ValueError:
print("ERROR: Your journal file seems to be corrupted. You do have a backup, don't you?")
util.prompt("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
padding = " ".encode("utf-8")
if not plain.endswith(padding): # Journals are always padded
return None
else:
return plain
return plain.decode("utf-8")
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))
Random.atfork() # A seed for PyCrypto
iv = Random.new().read(AES.block_size)
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 :)
plain += " " * 16
plain = plain.encode("utf-8")
plain += b" " * (AES.block_size - len(plain) % AES.block_size)
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.encode('utf-8')).digest()
password = self.config['password'] or util.getpass(prompt)
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.
@ -119,9 +116,9 @@ class Journal(object):
attempts += 1
self.config['password'] = None # This password doesn't work.
if attempts < 3:
print("Wrong password, try again.")
util.prompt("Wrong password, try again.")
else:
print("Extremely wrong password.")
util.prompt("Extremely wrong password.")
sys.exit(-1)
journal = decrypted
else:
@ -152,6 +149,7 @@ class Journal(object):
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.
if current_entry:
current_entry.body += line + "\n"
# Append last entry
@ -173,18 +171,21 @@ class Journal(object):
lambda match: self._colorize(match.group(0)),
pp, re.UNICODE)
else:
pp = re.sub(r"(?u)([{}]\w+)".format(self.config['tagsymbols']),
pp = re.sub(r"(?u)([{tags}]\w+)".format(tags=self.config['tagsymbols']),
lambda match: self._colorize(match.group(0)),
pp)
return pp
def pprint(self):
return self.__unicode__()
def __repr__(self):
return "<Journal with {} entries>".format(len(self.entries))
def write(self, filename=None):
"""Dumps the journal into the config file, overwriting it"""
filename = filename or self.config['journal']
journal = "\n".join([unicode(e) for e in self.entries])
journal = "\n".join([e.__unicode__() for e in self.entries])
if self.config['encrypt']:
journal = self._encrypt(journal)
with open(filename, 'wb') as journal_file:
@ -314,7 +315,7 @@ class DayOne(Journal):
try:
timezone = pytz.timezone(dict_entry['Time Zone'])
except pytz.exceptions.UnknownTimeZoneError:
timezone = pytz.timezone(get_local_timezone())
timezone = pytz.timezone(util.get_local_timezone())
date = dict_entry['Creation Date']
date = date + timezone.utcoffset(date)
entry = self.new_entry(raw=dict_entry['Entry Text'], date=date, sort=False)
@ -340,7 +341,7 @@ class DayOne(Journal):
'Creation Date': utc_time,
'Starred': entry.starred if hasattr(entry, 'starred') else False,
'Entry Text': entry.title+"\n"+entry.body,
'Time Zone': get_local_timezone(),
'Time Zone': util.get_local_timezone(),
'UUID': new_uuid
}
plistlib.writePlist(entry_plist, filename)

View file

@ -7,7 +7,7 @@ jrnl is a simple journal application for your command line.
"""
__title__ = 'jrnl'
__version__ = '1.3.1'
__version__ = '1.4.1'
__author__ = 'Manuel Ebert'
__license__ = 'MIT License'
__copyright__ = 'Copyright 2013 Manuel Ebert'

View file

@ -7,6 +7,9 @@ try: from slugify import slugify
except ImportError: import slugify
try: import simplejson as json
except ImportError: import json
try: from .util import u
except (SystemError, ValueError): from util import u
def get_tags_count(journal):
"""Returns a set of tuples (count, tag) for all tags present in the journal."""
@ -29,7 +32,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(u"{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=True))
return result
def to_json(journal):
@ -60,7 +63,7 @@ def to_md(journal):
def to_txt(journal):
"""Returns the complete text of the Journal."""
return unicode(journal)
return journal.pprint()
def export(journal, format, output=None):
"""Exports the journal to various formats.
@ -93,7 +96,7 @@ def export(journal, format, output=None):
def write_files(journal, path, format):
"""Turns your journal into separate files for each entry.
Format should be either json, md or txt."""
make_filename = lambda entry: e.date.strftime("%C-%m-%d_{}.{}".format(slugify(unicode(e.title)), format))
make_filename = lambda entry: e.date.strftime("%C-%m-%d_{}.{}".format(slugify(u(e.title)), format))
for e in journal.entries:
full_path = os.path.join(path, make_filename(e))
if format == 'json':
@ -101,7 +104,7 @@ def write_files(journal, path, format):
elif format == 'md':
content = e.to_md()
elif format == 'txt':
content = unicode(e)
content = u(e)
with open(full_path, 'w') as f:
f.write(content)
return "[Journal exported individual files in {}]".format(path)

View file

@ -29,7 +29,8 @@ 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():
def parse_args(args=None):
parser = argparse.ArgumentParser()
composing = parser.add_argument_group('Composing', 'Will make an entry out of whatever follows as arguments')
composing.add_argument('-date', dest='date', help='Date, e.g. "yesterday at 5pm"')
@ -51,7 +52,7 @@ def parse_args():
exporting.add_argument('--decrypt', metavar='FILENAME', dest='decrypt', help='Decrypts your journal and stores it in plain text', nargs='?', default=False, const=None)
exporting.add_argument('--delete-last', dest='delete_last', help='Deletes the last entry from your journal file.', action="store_true")
return parser.parse_args()
return parser.parse_args(args)
def guess_mode(args, config):
"""Guesses the mode (compose, read or export) from the given arguments"""
@ -77,7 +78,7 @@ def get_text_from_editor(config):
raw = f.read()
os.remove(tmpfile)
else:
print('[Nothing saved to file]')
util.prompt('[Nothing saved to file]')
raw = ''
return raw
@ -89,19 +90,19 @@ def encrypt(journal, filename=None):
journal.make_key(prompt="Enter new password:")
journal.config['encrypt'] = True
journal.write(filename)
print("Journal encrypted to {0}.".format(filename or journal.config['journal']))
util.prompt("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 {0}.".format(filename or journal.config['journal']))
util.prompt("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 {0}]".format(filename))
util.prompt("[Journal created at {0}]".format(filename))
open(filename, 'a').close()
def update_config(config, new_config, scope):
@ -114,7 +115,7 @@ def update_config(config, new_config, scope):
config.update(new_config)
def cli():
def cli(manual_args=None):
if not os.path.exists(CONFIG_PATH):
config = install.install_jrnl(CONFIG_PATH)
else:
@ -122,18 +123,18 @@ def cli():
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]")
util.prompt("[There seems to be something wrong with your jrnl config at {}: {}]".format(CONFIG_PATH, e.message))
util.prompt("[Entry was NOT added to your journal]")
sys.exit(-1)
install.update_config(config, config_path=CONFIG_PATH)
original_config = config.copy()
# check if the configuration is supported by available modules
if config['encrypt'] and not PYCRYPTO:
print("According to your jrnl_conf, your journal is encrypted, however PyCrypto was not found. To open your journal, install the PyCrypto package from http://www.pycrypto.org.")
util.prompt("According to your jrnl_conf, your journal is encrypted, however PyCrypto was not found. To open your journal, install the PyCrypto package from http://www.pycrypto.org.")
sys.exit(-1)
args = parse_args()
args = parse_args(manual_args)
# If the first textual argument points to a journal file,
# use this!
@ -149,8 +150,6 @@ def cli():
mode_compose, mode_export = guess_mode(args, config)
# open journal file or folder
if os.path.isdir(config['journal']) and ( config['journal'].endswith(".dayone") or \
config['journal'].endswith(".dayone/")):
journal = Journal.DayOne(**config)
@ -171,10 +170,11 @@ def cli():
# Writing mode
if mode_compose:
raw = " ".join(args.text).strip()
unicode_raw = raw.decode(sys.getfilesystemencoding())
entry = journal.new_entry(unicode_raw, args.date)
if util.PY2 and type(raw) is not unicode:
raw = raw.decode(sys.getfilesystemencoding())
entry = journal.new_entry(raw, args.date)
entry.starred = args.star
print("[Entry added to {0} journal]".format(journal_name))
util.prompt("[Entry added to {0} journal]".format(journal_name))
journal.write()
# Reading mode
@ -184,7 +184,7 @@ def cli():
strict=args.strict,
short=args.short)
journal.limit(args.limit)
print(unicode(journal))
print(journal.pprint())
# Various export modes
elif args.tags:
@ -194,7 +194,7 @@ def cli():
print(exporters.export(journal, args.export, args.output))
elif (args.encrypt is not False or args.decrypt is not False) and not PYCRYPTO:
print("PyCrypto not found. To encrypt or decrypt your journal, install the PyCrypto package from http://www.pycrypto.org.")
util.prompt("PyCrypto not found. To encrypt or decrypt your journal, install the PyCrypto package from http://www.pycrypto.org.")
elif args.encrypt is not False:
encrypt(journal, filename=args.encrypt)
@ -212,7 +212,7 @@ def cli():
elif args.delete_last:
last_entry = journal.entries.pop()
print("[Deleted Entry:]")
util.prompt("[Deleted Entry:]")
print(last_entry)
journal.write()

View file

@ -3,15 +3,36 @@
import sys
import os
from tzlocal import get_localzone
import getpass as gp
PY3 = sys.version_info[0] == 3
PY2 = sys.version_info[0] == 2
STDIN = sys.stdin
STDERR = sys.stderr
STDOUT = sys.stdout
TEST = False
__cached_tz = None
def py23_input(msg):
if sys.version_info[0] == 3:
try: return input(msg)
except SyntaxError: return ""
def getpass(prompt):
if not TEST:
return gp.getpass(prompt)
else:
return raw_input(msg)
return py23_input(prompt)
def u(s):
"""Mock unicode function for python 2 and 3 compatibility."""
return s if PY3 or type(s) is unicode else unicode(s, "unicode_escape")
def prompt(msg):
"""Prints a message to the std err stream defined in util."""
if not msg.endswith("\n"):
msg += "\n"
STDERR.write(u(msg))
def py23_input(msg):
STDERR.write(u(msg))
return STDIN.readline().strip()
def get_local_timezone():
"""Returns the Olson identifier of the local timezone.

View file

@ -1,33 +0,0 @@
#!/usr/bin/env python
# encoding: utf-8
import unittest
class TestClasses(unittest.TestCase):
"""Test the behavior of the classes.
tests related to the Journal and the Entry Classes which can
be tested withouth command-line interaction
"""
def setUp(self):
pass
def test_colon_in_textbody(self):
"""colons should not cause problems in the text body"""
pass
class TestCLI(unittest.TestCase):
"""test the command-line interaction part of the program"""
def setUp(self):
pass
def test_something(self):
"""first test"""
pass
if __name__ == '__main__':
unittest.main()