Merge pull request #756 from jrnl-org/develop

Merge develop into master for beta release
This commit is contained in:
micahellison 2019-11-25 20:56:48 -08:00 committed by GitHub
commit b5d33e3423
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 171 additions and 34 deletions

View file

@ -1,24 +1,27 @@
Contributing # Contributing
============
If you use jrnl, you can totally make our day by just saying "thanks for the code." It's your chance to make a programmer happy today! If you have a moment, let us know what you use jrnl for and how; it'll help us to make it even better! If you use jrnl, you can totally make our day by just saying "thanks for the code." It's your chance to make a programmer happy today! If you have a moment, let us know what you use jrnl for and how; it'll help us to make it even better!
Docs & Typos # Table of Contents
------------ * [Docs and Typos](#docs-and-typos)
* [Bugs](#bugs)
* [Feature requests and ideas](#feature-requests-and-ideas)
* [New programmers and programmers new to python](#new-programmers-and-programmers-new-to-python)
* [Developing jrnl](#developing-jrnl)
## Docs and Typos
If you find a typo or a mistake in the docs, please fix it right away and send a pull request. The Right Way™ to fix the docs is to edit the `docs/*.md` files on the **master** branch. You can see the result if you run `make html` inside the project's root directory, which will open a browser that hot-reloads as you change the docs. This requires [mkdocs](https://www.mkdocs.org) to be installed. The `gh-pages` branch is automatically maintained and updates from `master`; you should never have to edit that. If you find a typo or a mistake in the docs, please fix it right away and send a pull request. The Right Way™ to fix the docs is to edit the `docs/*.md` files on the **master** branch. You can see the result if you run `make html` inside the project's root directory, which will open a browser that hot-reloads as you change the docs. This requires [mkdocs](https://www.mkdocs.org) to be installed. The `gh-pages` branch is automatically maintained and updates from `master`; you should never have to edit that.
Bugs ## Bugs
----
Unfortunately, bugs happen. If you found one, please [open a new issue](https://github.com/jrnl-org/jrnl/issues/new) and describe it as well as possible. If you're a programmer with some time, go ahead and send us a pull request! We'll review as quickly as we can. Unfortunately, bugs happen. If you found one, please [open a new issue](https://github.com/jrnl-org/jrnl/issues/new/choose) and describe it as well as possible. If you're a programmer with some time, go ahead and send us a pull request that references the issue! We'll review as quickly as we can.
## Feature requests and ideas
Feature requests and ideas So, you have an idea for a great feature? Awesome! We'd love to hear from you! Please [open a new issue](https://github.com/jrnl-org/jrnl/issues/new/choose) and describe the goal of the feature, and any relevant use cases. We'll discuss the issue with you, and decide if it's a good fit for the project.
--------------------------
So, you have an idea for a great feature? Awesome! We'd love to hear from you! Please [open a new issue](https://github.com/jrnl-org/jrnl/issues) and describe the goal of the feature, and any relevant use cases. We'll discuss the issue with you, and decide if it's a good fit for the project.
When discussing new features, please keep in mind our design goals. jrnl strives to do one thing well. To us, that means: When discussing new features, please keep in mind our design goals. jrnl strives to do one thing well. To us, that means:
@ -26,10 +29,17 @@ When discussing new features, please keep in mind our design goals. jrnl strives
* have a simple interface * have a simple interface
* avoid duplicating functionality * avoid duplicating functionality
## New programmers and programmers new to python
A short note for new programmers and programmers new to python Although jrnl has grown quite a bit since its inception, the overall complexity (for an end-user program) is fairly low, and we hope you'll find the code easy enough to understand.
--------------------------------------------------------------
Although jrnl has grown quite a bit since its inception. The overall complexity (for an end-user program) is fairly low, and we hope you'll find the code easy enough to understand.
If you have a question, please don't hesitate to ask! Python is known for its welcoming community and openness to novice programmers, so feel free to fork the code and play around with it! If you create something you want to share with us, please create a pull request. We never expect pull requests to be perfect, idiomatic, instantly mergeable code. We can work through it together! If you have a question, please don't hesitate to ask! Python is known for its welcoming community and openness to novice programmers, so feel free to fork the code and play around with it! If you create something you want to share with us, please create a pull request. We never expect pull requests to be perfect, idiomatic, instantly mergeable code. We can work through it together!
## Developing jrnl
The jrnl source uses [poetry](https://poetry.eustace.io/) for dependency management. You will need to install it to develop journal.
* To run tests: `make test` (or `poetry run behave` if on Windows)
* To run the source: `poetry install` then `poetry shell` then run `jrnl` with or without arguments as necessary
For testing, jrnl uses [behave](https://behave.readthedocs.io/).

View file

@ -62,3 +62,57 @@ Feature: Zapped bugs should stay dead.
Then the output should contain "I'm going to activate the machine." Then the output should contain "I'm going to activate the machine."
Then the output should contain "I've crossed so many timelines. Is there any going back?" Then the output should contain "I've crossed so many timelines. Is there any going back?"
Scenario: Viewing today's entries does not print the entire journal
# https://github.com/jrnl-org/jrnl/issues/741
Given we use the config "basic.yaml"
When we run "jrnl -on today"
Then the output should not contain "Life is good"
Then the output should not contain "But I'm better."
Scenario: Create entry using day of the week as entry date.
Given we use the config "basic.yaml"
When we run "jrnl monday: This is an entry on a Monday."
Then we should see the message "Entry added"
When we run "jrnl -1"
Then the output should contain "monday at 9am" in the local time
Then the output should contain "This is an entry on a Monday."
Scenario: Create entry using day of the week abbreviations as entry date.
Given we use the config "basic.yaml"
When we run "jrnl fri: This is an entry on a Friday."
Then we should see the message "Entry added"
When we run "jrnl -1"
Then the output should contain "friday at 9am" in the local time
Scenario: Displaying entries using -on today should display entries created today.
Given we use the config "basic.yaml"
When we run "jrnl today: Adding an entry right now."
Then we should see the message "Entry added"
When we run "jrnl -on today"
Then the output should contain "Adding an entry right now."
Scenario: Displaying entries using -from day should display correct entries
Given we use the config "basic.yaml"
When we run "jrnl yesterday: This thing happened yesterday"
Then we should see the message "Entry added"
When we run "jrnl today at 11:59pm: Adding an entry right now."
Then we should see the message "Entry added"
When we run "jrnl tomorrow: A future entry."
Then we should see the message "Entry added"
When we run "jrnl -from today"
Then the output should contain "Adding an entry right now."
Then the output should contain "A future entry."
Then the output should not contain "This thing happened yesterday"
Scenario: Displaying entries using -from and -to day should display correct entries
Given we use the config "basic.yaml"
When we run "jrnl yesterday: This thing happened yesterday"
Then we should see the message "Entry added"
When we run "jrnl today at 11:59pm: Adding an entry right now."
Then we should see the message "Entry added"
When we run "jrnl tomorrow: A future entry."
Then we should see the message "Entry added"
When we run "jrnl -from yesterday -to today"
Then the output should contain "This thing happened yesterday"
Then the output should contain "Adding an entry right now."
Then the output should not contain "A future entry."

View file

@ -5,6 +5,9 @@ from jrnl import cli, install, Journal, util, plugins
from jrnl import __version__ from jrnl import __version__
from dateutil import parser as date_parser from dateutil import parser as date_parser
from collections import defaultdict from collections import defaultdict
try: import parsedatetime.parsedatetime_consts as pdt
except ImportError: import parsedatetime as pdt
import time
import os import os
import json import json
import yaml import yaml
@ -13,6 +16,10 @@ import tzlocal
import shlex import shlex
import sys import sys
consts = pdt.Constants(usePyICU=False)
consts.DOWParseStyle = -1 # Prefers past weekdays
CALENDAR = pdt.Calendar(consts)
class TestKeyring(keyring.backend.KeyringBackend): class TestKeyring(keyring.backend.KeyringBackend):
"""A test keyring that just stores its values in a hash""" """A test keyring that just stores its values in a hash"""
@ -221,9 +228,9 @@ def check_output(context, text=None):
def check_output_time_inline(context, text): def check_output_time_inline(context, text):
out = context.stdout_capture.getvalue() out = context.stdout_capture.getvalue()
local_tz = tzlocal.get_localzone() local_tz = tzlocal.get_localzone()
utc_time = date_parser.parse(text) date, flag = CALENDAR.parse(text)
local_date = utc_time.astimezone(local_tz).strftime("%Y-%m-%d %H:%M") output_date = time.strftime("%Y-%m-%d %H:%M",date)
assert local_date in out, local_date assert output_date in out, output_date
@then('the output should contain') @then('the output should contain')

View file

@ -122,7 +122,8 @@ class Journal:
try: try:
new_date = datetime.strptime(date_blob, self.config["timeformat"]) new_date = datetime.strptime(date_blob, self.config["timeformat"])
except ValueError: except ValueError:
new_date = time.parse(date_blob) # Passing in a date that had brackets around it
new_date = time.parse(date_blob, bracketed=True)
if new_date: if new_date:
if entries: if entries:

View file

@ -1,6 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
import readline
import glob import glob
import getpass import getpass
import os import os
@ -14,6 +13,9 @@ from .util import UserAbort
import yaml import yaml
import logging import logging
import sys import sys
if "win32" not in sys.platform:
# readline is not included in Windows Active Python
import readline
DEFAULT_CONFIG_NAME = 'jrnl.yaml' DEFAULT_CONFIG_NAME = 'jrnl.yaml'
DEFAULT_JOURNAL_NAME = 'journal.txt' DEFAULT_JOURNAL_NAME = 'journal.txt'
@ -108,14 +110,10 @@ def load_or_install_jrnl():
def install(): def install():
def autocomplete(text, state): if "win32" not in sys.platform:
expansions = glob.glob(os.path.expanduser(os.path.expandvars(text)) + '*') readline.set_completer_delims(' \t\n;')
expansions = [e + "/" if os.path.isdir(e) else e for e in expansions] readline.parse_and_bind("tab: complete")
expansions.append(None) readline.set_completer(autocomplete)
return expansions[state]
readline.set_completer_delims(' \t\n;')
readline.parse_and_bind("tab: complete")
readline.set_completer(autocomplete)
# Where to create the journal? # Where to create the journal?
path_query = f'Path to your journal file (leave blank for {JOURNAL_FILE_PATH}): ' path_query = f'Path to your journal file (leave blank for {JOURNAL_FILE_PATH}): '
@ -146,3 +144,9 @@ def install():
if password: if password:
config['password'] = password config['password'] = password
return config return config
def autocomplete(text, state):
expansions = glob.glob(os.path.expanduser(os.path.expandvars(text)) + '*')
expansions = [e + "/" if os.path.isdir(e) else e for e in expansions]
expansions.append(None)
return expansions[state]

View file

@ -9,8 +9,9 @@ from .tag_exporter import TagExporter
from .xml_exporter import XMLExporter from .xml_exporter import XMLExporter
from .yaml_exporter import YAMLExporter from .yaml_exporter import YAMLExporter
from .template_exporter import __all__ as template_exporters from .template_exporter import __all__ as template_exporters
from .fancy_exporter import FancyExporter
__exporters =[JSONExporter, MarkdownExporter, TagExporter, TextExporter, XMLExporter, YAMLExporter] + template_exporters __exporters =[JSONExporter, MarkdownExporter, TagExporter, TextExporter, XMLExporter, YAMLExporter, FancyExporter] + template_exporters
__importers =[JRNLImporter] __importers =[JRNLImporter]
__exporter_types = {name: plugin for plugin in __exporters for name in plugin.names} __exporter_types = {name: plugin for plugin in __exporters for name in plugin.names}

View file

@ -0,0 +1,56 @@
#!/usr/bin/env python
# encoding: utf-8
from __future__ import absolute_import, unicode_literals, print_function
from .text_exporter import TextExporter
from textwrap import TextWrapper
class FancyExporter(TextExporter):
"""This Exporter can convert entries and journals into text with unicode box drawing characters."""
names = ["fancy", "boxed"]
extension = "txt"
border_a=""
border_b=""
border_c=""
border_d=""
border_e=""
border_f=""
border_g=""
border_h=""
border_i=""
border_j=""
border_k=""
border_l=""
border_m=""
@classmethod
def export_entry(cls, entry):
"""Returns a fancy unicode representation of a single entry."""
date_str = entry.date.strftime(entry.journal.config['timeformat'])
linewrap = entry.journal.config['linewrap'] or 78
initial_linewrap = linewrap - len(date_str) - 2
body_linewrap = linewrap - 2
card = [cls.border_a + cls.border_b*(initial_linewrap) + cls.border_c + date_str]
w = TextWrapper(width=initial_linewrap, initial_indent=cls.border_g+' ', subsequent_indent=cls.border_g+' ')
title_lines = w.wrap(entry.title)
card.append(title_lines[0].ljust(initial_linewrap+1) + cls.border_d + cls.border_e*(len(date_str)-1) + cls.border_f)
w.width = body_linewrap
if len(title_lines) > 1:
for line in w.wrap(' '.join([title_line[len(w.subsequent_indent):]
for title_line in title_lines[1:]])):
card.append(line.ljust(body_linewrap+1) + cls.border_h)
if entry.body:
card.append(cls.border_i + cls.border_j*body_linewrap + cls.border_k)
for line in entry.body.splitlines():
body_lines = w.wrap(line) or [cls.border_g]
for body_line in body_lines:
card.append(body_line.ljust(body_linewrap+1) + cls.border_h)
card.append(cls.border_l + cls.border_b*body_linewrap + cls.border_m)
return "\n".join(card)
@classmethod
def export_journal(cls, journal):
"""Returns a unicode representation of an entire journal."""
return "\n".join(cls.export_entry(entry) for entry in journal)

View file

@ -12,15 +12,16 @@ consts.DOWParseStyle = -1 # "Monday" will be either today or the last Monday
CALENDAR = pdt.Calendar(consts) CALENDAR = pdt.Calendar(consts)
def parse(date_str, inclusive=False, default_hour=None, default_minute=None): def parse(date_str, inclusive=False, default_hour=None, default_minute=None, bracketed=False):
"""Parses a string containing a fuzzy date and returns a datetime.datetime object""" """Parses a string containing a fuzzy date and returns a datetime.datetime object"""
if not date_str: if not date_str:
return None return None
elif isinstance(date_str, datetime): elif isinstance(date_str, datetime):
return date_str return date_str
# Don't try to parse anything with 6 or less characters. It's probably a markdown footnote # Don't try to parse anything with 6 or less characters and was parsed from the existing journal.
if len(date_str) <= 6: # It's probably a markdown footnote
if len(date_str) <= 6 and bracketed:
return None return None
default_date = DEFAULT_FUTURE if inclusive else DEFAULT_PAST default_date = DEFAULT_FUTURE if inclusive else DEFAULT_PAST
@ -51,8 +52,11 @@ def parse(date_str, inclusive=False, default_hour=None, default_minute=None):
except TypeError: except TypeError:
return None return None
if flag == 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=default_hour or 0, minute=default_minute or 0) date = datetime(*date[:3],
hour=23 if inclusive else default_hour or 0,
minute=59 if inclusive else default_minute or 0,
second=59 if inclusive else 0)
else: else:
date = datetime(*date[:6]) date = datetime(*date[:6])