From cefc2db8b7f7f65599869dadbcf1f62d56b2a3b2 Mon Sep 17 00:00:00 2001 From: Craig Moyer Date: Fri, 13 Jan 2017 19:55:41 -0500 Subject: [PATCH 01/40] Add support for folder base journal. Adds feature for issue #170 (and #398) where you configure your journal to be a directory and entries are added as sub-directories and files: yyyy/mm/dd.txt. Multiple entries in a day will go in the same file, but a new entry for a specific day will create a new file (and directory structure). --- features/folder.feature | 43 ++++++++++++++++ features/regression.feature | 6 --- features/steps/core.py | 9 ++++ jrnl/FolderJournal.py | 84 +++++++++++++++++++++++++++++++ jrnl/Journal.py | 4 +- jrnl/plugins/template_exporter.py | 2 +- 6 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 features/folder.feature create mode 100644 jrnl/FolderJournal.py diff --git a/features/folder.feature b/features/folder.feature new file mode 100644 index 00000000..b747e6c1 --- /dev/null +++ b/features/folder.feature @@ -0,0 +1,43 @@ +Feature: Testing a journal with a root directory and multiple files in the format of yyyy/mm/dd.txt + + Scenario: Opening an folder that's not a DayOne folder should treat as folder journal + Given we use the config "empty_folder.yaml" + When we run "jrnl 23 july 2013: Testing folder journal." + Then we should see the message "Entry added" + When we run "jrnl -1" + Then the output should be + """ + 2013-07-23 09:00 Testing folder journal. + """ + + Scenario: Adding entries to a Folder journal should generate date files + Given we use the config "empty_folder.yaml" + When we run "jrnl 23 July 2013: Testing folder journal." + Then we should see the message "Entry added" + When the journal directory is listed + Then the output should contain "2013/07/23.txt" + + + Scenario: Adding multiple entries to a Folder journal should generate multiple date files + Given we use the config "empty_folder.yaml" + When we run "jrnl 23 July 2013: Testing folder journal." + And we run "jrnl 3/7/2014: Second entry of journal." + Then we should see the message "Entry added" + When the journal directory is listed + Then the output should contain "2013/07/23.txt" + And the output should contain "2014/03/07.txt" + + + Scenario: Out of order entries to a Folder journal should be listed in date order + Given we use the config "empty_folder.yaml" + When we run "jrnl 3/7/2014 4:37pm: Second entry of journal." + Then we should see the message "Entry added" + When we run "jrnl 23 July 2013: Testing folder journal." + Then we should see the message "Entry added" + When we run "jrnl -2" + Then the output should be + """ + 2013-07-23 09:00 Testing folder journal. + + 2014-03-07 16:37 Second entry of journal. + """ diff --git a/features/regression.feature b/features/regression.feature index 727f7c27..aa78ae3b 100644 --- a/features/regression.feature +++ b/features/regression.feature @@ -8,12 +8,6 @@ Feature: Zapped bugs should stay dead. When we run "jrnl -n 1" Then the output should not contain "Life is good" - Scenario: Opening an folder that's not a DayOne folder gives a nice error message - Given we use the config "empty_folder.yaml" - When we run "jrnl Herro" - Then we should get an error - Then we should see the message "is a directory, but doesn't seem to be a DayOne journal either" - Scenario: Date with time should be parsed correctly # https://github.com/maebert/jrnl/issues/117 Given we use the config "basic.yaml" diff --git a/features/steps/core.py b/features/steps/core.py index 4813b252..4fabfcd9 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -254,6 +254,15 @@ def check_journal_entries(context, number, journal_name="default"): journal = open_journal(journal_name) assert len(journal.entries) == number +@when('the journal directory is listed') +def list_journal_directory(context, journal="default"): + files=[] + with open(install.CONFIG_FILE_PATH) as config_file: + config = yaml.load(config_file) + journal_path = config['journals'][journal] + for root, dirnames, f in os.walk(journal_path): + for file in f: + print(os.path.join(root,file)) @then('fail') def debug_fail(context): diff --git a/jrnl/FolderJournal.py b/jrnl/FolderJournal.py new file mode 100644 index 00000000..4ca55335 --- /dev/null +++ b/jrnl/FolderJournal.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +# encoding: utf-8 + +from __future__ import absolute_import, unicode_literals +from . import Entry +from . import Journal +import codecs +import os +import fnmatch + +def get_files(journal_config): + """Searches through sub directories starting with journal_config and find all text files""" + filenames = [] + for root, dirnames, f in os.walk(journal_config): + for filename in fnmatch.filter(f, '*.txt'): + filenames.append(os.path.join(root, filename)) + return filenames + + +class Folder(Journal.Journal): + """A Journal handling multiple files in a folder""" + + def __init__(self, **kwargs): + self.entries = [] + self._diff_entry_dates = [] + super(Folder, self).__init__(**kwargs) + + + def open(self): + filenames = [] + self.entries = [] + filenames = get_files(self.config['journal']) + for filename in filenames: + with codecs.open(filename, "r", "utf-8") as f: + journal = f.read() + self.entries.extend(self._parse(journal)) + self.sort() + return self + + def write(self): + """Writes only the entries that have been modified into proper files.""" + #Create a list of dates of modified entries. Start with diff_entry_dates + modified_dates = self._diff_entry_dates + seen_dates = set(self._diff_entry_dates) + for e in self.entries: + if e.modified: + if e.date not in seen_dates: + modified_dates.append(e.date) + seen_dates.add(e.date) + + #For every date that had a modified entry, write to a file + for d in modified_dates: + write_entries=[] + filename = os.path.join(self.config['journal'], d.strftime("%Y"), d.strftime("%m"), d.strftime("%d")+".txt") + dirname = os.path.dirname(filename) + #create directory if it doesn't exist + if not os.path.exists(dirname): + os.makedirs(dirname) + for e in self.entries: + if e.date.year == d.year and e.date.month == d.month and e.date.day == d.day: + write_entries.append(e) + journal = "\n".join([e.__unicode__() for e in write_entries]) + with codecs.open(filename, 'w', "utf-8") as journal_file: + journal_file.write(journal) + #look for and delete empty files + filenames = [] + filenames = get_files(self.config['journal']) + for filename in filenames: + if os.stat(filename).st_size <= 0: + #print("empty file: {}".format(filename)) + os.remove(filename) + + def parse_editable_str(self, edited): + """Parses the output of self.editable_str and updates it's entries.""" + mod_entries = self._parse(edited) + diff_entries = set(self.entries) - set(mod_entries) + for e in diff_entries: + self._diff_entry_dates.append(e.date) + # Match those entries that can be found in self.entries and set + # these to modified, so we can get a count of how many entries got + # modified and how many got deleted later. + for entry in mod_entries: + entry.modified = not any(entry == old_entry for old_entry in self.entries) + self.entries = mod_entries diff --git a/jrnl/Journal.py b/jrnl/Journal.py index c36b3760..8eb1d2da 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -314,8 +314,8 @@ def open_journal(name, config, legacy=False): from . import DayOneJournal return DayOneJournal.DayOne(**config).open() else: - util.prompt(u"[Error: {0} is a directory, but doesn't seem to be a DayOne journal either.".format(config['journal'])) - sys.exit(1) + from . import FolderJournal + return FolderJournal.Folder(**config).open() if not config['encrypt']: if legacy: diff --git a/jrnl/plugins/template_exporter.py b/jrnl/plugins/template_exporter.py index 6a5fa86b..85aa2236 100644 --- a/jrnl/plugins/template_exporter.py +++ b/jrnl/plugins/template_exporter.py @@ -36,7 +36,7 @@ def __exporter_from_file(template_file): """Create a template class from a file""" name = os.path.basename(template_file).replace(".template", "") template = Template.from_file(template_file) - return type("{}Exporter".format(name.title()), (GenericTemplateExporter, ), { + return type(str("{}Exporter".format(name.title())), (GenericTemplateExporter, ), { "names": [name], "extension": template.extension, "template": template From 12282b858b0cce8accc4c5b730563835ac4b6c36 Mon Sep 17 00:00:00 2001 From: Craig Moyer Date: Fri, 13 Jan 2017 20:03:10 -0500 Subject: [PATCH 02/40] Update travis to remove --use-mirrors for 2.0-rc1 branch. Was causing errors in builds. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d2eea287..6a195a7c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ python: - "3.3" - "3.4" install: - - "pip install -e . --use-mirrors" + - "pip install -e ." - "pip install -q behave" # command to run tests script: From 889b762247d36bdcd7860b94a4294304c9cde1de Mon Sep 17 00:00:00 2001 From: moyercw Date: Sat, 14 Jan 2017 01:26:22 +0000 Subject: [PATCH 03/40] Add missing dependency for asteval. --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index e96973be..f6097064 100644 --- a/setup.py +++ b/setup.py @@ -89,6 +89,7 @@ setup( "keyring>=7.3", "passlib>=1.6.2", "pyxdg>=0.25", + "asteval>=0.9.8", ] + [p for p, cond in conditional_dependencies.items() if cond], long_description=__doc__, entry_points={ From 866634035a3ab46c2d187a817826e45295b5ebe2 Mon Sep 17 00:00:00 2001 From: Craig Moyer Date: Mon, 23 Sep 2019 17:20:07 -0400 Subject: [PATCH 04/40] Fix failed test scenarios for folder journal. --- features/data/journals/empty_folder/{empty.txt => empty} | 0 features/steps/core.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename features/data/journals/empty_folder/{empty.txt => empty} (100%) diff --git a/features/data/journals/empty_folder/empty.txt b/features/data/journals/empty_folder/empty similarity index 100% rename from features/data/journals/empty_folder/empty.txt rename to features/data/journals/empty_folder/empty diff --git a/features/steps/core.py b/features/steps/core.py index a6fcae16..4387fb39 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -258,7 +258,7 @@ def check_journal_entries(context, number, journal_name="default"): def list_journal_directory(context, journal="default"): files=[] with open(install.CONFIG_FILE_PATH) as config_file: - config = yaml.load(config_file) + config = yaml.load(config_file, Loader=yaml.FullLoader) journal_path = config['journals'][journal] for root, dirnames, f in os.walk(journal_path): for file in f: From 2e7da235284b68ec6435c09fe5856328f04e27c6 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 8 Feb 2020 20:12:49 +0000 Subject: [PATCH 05/40] Updating changelog [ci skip] --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec865924..99ee37ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,13 +10,13 @@ **Build:** +- Remove poetry from build system in pyproject config to fix `brew install` [\#830](https://github.com/jrnl-org/jrnl/pull/830) ([wren](https://github.com/wren)) - Fix all skipped tests on Travis Windows builds by preserving newlines [\#823](https://github.com/jrnl-org/jrnl/pull/823) ([micahellison](https://github.com/micahellison)) **Updated documentation:** - Updating/clarifying template explanation [\#829](https://github.com/jrnl-org/jrnl/pull/829) ([heymajor](https://github.com/heymajor)) - ## [v2.2](https://pypi.org/project/jrnl/v2.2/) (2020-02-01) [Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.2-beta2...v2.2) From 539eafd0bb71c2a9542b838d3ea6ede2a5a7119d Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 8 Feb 2020 12:13:53 -0800 Subject: [PATCH 06/40] Small fixes to changelog --- CHANGELOG.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99ee37ec..7c0493e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ ## [v2.2](https://pypi.org/project/jrnl/v2.2/) (2020-02-01) -[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.2-beta2...v2.2) +[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.1.1...v2.2) **Implemented enhancements:** @@ -59,7 +59,7 @@ ## [v2.1.1](https://pypi.org/project/jrnl/v2.1.1/) (2019-11-26) -[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.1.1-beta...v2.1.1) +[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.1.post2...v2.1.1) **Implemented enhancements:** @@ -82,7 +82,7 @@ ## [v2.1.post2](https://pypi.org/project/jrnl/v2.1.post2/) (2019-11-11) -[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.1-beta6...v2.1.post2) +[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.0.1...v2.1.post2) **Fixed bugs:** @@ -100,7 +100,7 @@ ## [v2.0.1](https://pypi.org/project/jrnl/v2.0.1/) (2019-09-26) -[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.0.1-beta...v2.0.1) +[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.0.0...v2.0.1) **Implemented enhancements:** @@ -118,7 +118,9 @@ ## [v2.0.0](https://pypi.org/project/jrnl/v2.0.0/) (2019-08-24) -[Full Changelog](https://github.com/jrnl-org/jrnl/compare/v2.0-rc4...v2.0.0) +[Full Changelog](https://github.com/jrnl-org/jrnl/compare/1.9.8...v2.0.0) + +🚨 **BREAKING CHANGES** 🚨 **Implemented enhancements:** - Change cryptographic backend from PyCrypto to cryptography.io From 630a8fcaec3d6f3c8c79fae27f462980d11dc5da Mon Sep 17 00:00:00 2001 From: Stav Shamir Date: Sat, 8 Feb 2020 22:18:01 +0200 Subject: [PATCH 07/40] Add test scenarios for the export feature (#824) * Fix behave keyword "and" to correct case "And" * Extract export_steps module * Add scenario for XML export * Add scenario for tags export * Add scenario for fancy export * Add scenario for yaml export * Remove unused module export.py * Run `make format` * Fix `create_directory` step --- features/exporting.feature | 73 ++++++++++++++++--- features/steps/core.py | 48 ------------- features/steps/export_steps.py | 124 +++++++++++++++++++++++++++++++++ jrnl/export.py | 62 ----------------- 4 files changed, 187 insertions(+), 120 deletions(-) create mode 100644 features/steps/export_steps.py delete mode 100644 jrnl/export.py diff --git a/features/exporting.feature b/features/exporting.feature index db2ef5b3..5705fda1 100644 --- a/features/exporting.feature +++ b/features/exporting.feature @@ -4,21 +4,20 @@ Feature: Exporting a Journal Given we use the config "tags.yaml" When we run "jrnl --export json" Then we should get no error - and the output should be parsable as json - and "entries" in the json output should have 2 elements - and "tags" in the json output should contain "@idea" - and "tags" in the json output should contain "@journal" - and "tags" in the json output should contain "@dan" + And the output should be parsable as json + And "entries" in the json output should have 2 elements + And "tags" in the json output should contain "@idea" + And "tags" in the json output should contain "@journal" + And "tags" in the json output should contain "@dan" Scenario: Exporting using filters should only export parts of the journal Given we use the config "tags.yaml" When we run "jrnl -until 'may 2013' --export json" - # Then we should get no error Then the output should be parsable as json - and "entries" in the json output should have 1 element - and "tags" in the json output should contain "@idea" - and "tags" in the json output should contain "@journal" - and "tags" in the json output should not contain "@dan" + And "entries" in the json output should have 1 element + And "tags" in the json output should contain "@idea" + And "tags" in the json output should contain "@journal" + And "tags" in the json output should not contain "@dan" Scenario: Exporting using custom templates Given we use the config "basic.yaml" @@ -83,3 +82,57 @@ Feature: Exporting a Journal More stuff more stuff again """ + + Scenario: Exporting to XML + Given we use the config "tags.yaml" + When we run "jrnl --export xml" + Then the output should be a valid XML string + And "entries" node in the xml output should have 2 elements + And "tags" in the xml output should contain ["@idea", "@journal", "@dan"] + + Scenario: Exporting tags + Given we use the config "tags.yaml" + When we run "jrnl --export tags" + Then the output should be + """ + @idea : 2 + @journal : 1 + @dan : 1 + """ + + Scenario: Exporting fancy + Given we use the config "tags.yaml" + When we run "jrnl --export fancy" + Then the output should be + """ + ┎──────────────────────────────────────────────────────────────╮2013-04-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. │ + ┃ inst │ + ┖──────────────────────────────────────────────────────────────────────────────┘ + """ + + Scenario: Export to yaml + Given we use the config "tags.yaml" + And we created a directory named "exported_journal" + When we run "jrnl --export yaml -o exported_journal" + Then "exported_journal" should contain the files ["2013-04-09_i-have-an-idea.md", "2013-06-10_i-met-with-dan.md"] + And the content of exported yaml "exported_journal/2013-04-09_i-have-an-idea.md" should be + """ + title: I have an @idea: + date: 2013-04-09 15:39 + stared: False + tags: idea, journal + + (1) write a command line @journal software + (2) ??? + (3) PROFIT! + """ diff --git a/features/steps/core.py b/features/steps/core.py index af0a657e..0a3841c4 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -3,7 +3,6 @@ from unittest.mock import patch from behave import given, when, then from jrnl import cli, install, Journal, util, plugins from jrnl import __version__ -from dateutil import parser as date_parser from collections import defaultdict try: @@ -185,53 +184,6 @@ def no_error(context): assert context.exit_status == 0, context.exit_status -@then("the output should be parsable as json") -def check_output_json(context): - out = context.stdout_capture.getvalue() - assert json.loads(out), out - - -@then('"{field}" in the json output should have {number:d} elements') -@then('"{field}" in the json output should have 1 element') -def check_output_field(context, field, number=1): - out = context.stdout_capture.getvalue() - out_json = json.loads(out) - assert field in out_json, [field, out_json] - assert len(out_json[field]) == number, len(out_json[field]) - - -@then('"{field}" in the json output should not contain "{key}"') -def check_output_field_not_key(context, field, key): - out = context.stdout_capture.getvalue() - out_json = json.loads(out) - assert field in out_json - assert key not in out_json[field] - - -@then('"{field}" in the json output should contain "{key}"') -def check_output_field_key(context, field, key): - out = context.stdout_capture.getvalue() - out_json = json.loads(out) - assert field in out_json - assert key in out_json[field] - - -@then('the json output should contain {path} = "{value}"') -def check_json_output_path(context, path, value): - """ E.g. - the json output should contain entries.0.title = "hello" - """ - out = context.stdout_capture.getvalue() - struct = json.loads(out) - - for node in path.split("."): - try: - struct = struct[int(node)] - except ValueError: - struct = struct[node] - assert struct == value, struct - - @then("the output should be") @then('the output should be "{text}"') def check_output(context, text=None): diff --git a/features/steps/export_steps.py b/features/steps/export_steps.py new file mode 100644 index 00000000..b7965ab8 --- /dev/null +++ b/features/steps/export_steps.py @@ -0,0 +1,124 @@ +import json +import os +import shutil +from xml.etree import ElementTree + +from behave import then, given + + +@then("the output should be parsable as json") +def check_output_json(context): + out = context.stdout_capture.getvalue() + assert json.loads(out), out + + +@then('"{field}" in the json output should have {number:d} elements') +@then('"{field}" in the json output should have 1 element') +def check_output_field(context, field, number=1): + out = context.stdout_capture.getvalue() + out_json = json.loads(out) + assert field in out_json, [field, out_json] + assert len(out_json[field]) == number, len(out_json[field]) + + +@then('"{field}" in the json output should not contain "{key}"') +def check_output_field_not_key(context, field, key): + out = context.stdout_capture.getvalue() + out_json = json.loads(out) + assert field in out_json + assert key not in out_json[field] + + +@then('"{field}" in the json output should contain "{key}"') +def check_output_field_key(context, field, key): + out = context.stdout_capture.getvalue() + out_json = json.loads(out) + assert field in out_json + assert key in out_json[field] + + +@then('the json output should contain {path} = "{value}"') +def check_json_output_path(context, path, value): + """ E.g. + the json output should contain entries.0.title = "hello" + """ + out = context.stdout_capture.getvalue() + struct = json.loads(out) + + for node in path.split("."): + try: + struct = struct[int(node)] + except ValueError: + struct = struct[node] + assert struct == value, struct + + +@then("the output should be a valid XML string") +def assert_valid_xml_string(context): + output = context.stdout_capture.getvalue() + xml_tree = ElementTree.fromstring(output) + assert xml_tree, output + + +@then('"entries" node in the xml output should have {number:d} elements') +def assert_xml_output_entries_count(context, number): + output = context.stdout_capture.getvalue() + xml_tree = ElementTree.fromstring(output) + + xml_tags = (node.tag for node in xml_tree) + assert "entries" in xml_tags, str(list(xml_tags)) + + actual_entry_count = len(xml_tree.find("entries")) + assert actual_entry_count == number, actual_entry_count + + +@then('"tags" in the xml output should contain {expected_tags_json_list}') +def assert_xml_output_tags(context, expected_tags_json_list): + output = context.stdout_capture.getvalue() + xml_tree = ElementTree.fromstring(output) + + xml_tags = (node.tag for node in xml_tree) + assert "tags" in xml_tags, str(list(xml_tags)) + + expected_tags = json.loads(expected_tags_json_list) + actual_tags = set(t.attrib["name"] for t in xml_tree.find("tags")) + assert actual_tags == set(expected_tags), [actual_tags, set(expected_tags)] + + +@given('we created a directory named "{dir_name}"') +def create_directory(context, dir_name): + if os.path.exists(dir_name): + shutil.rmtree(dir_name) + os.mkdir(dir_name) + + +@then('"{dir_name}" should contain the files {expected_files_json_list}') +def assert_dir_contains_files(context, dir_name, expected_files_json_list): + actual_files = os.listdir(dir_name) + expected_files = json.loads(expected_files_json_list) + assert actual_files == expected_files, [actual_files, expected_files] + + +@then('the content of exported yaml "{file_path}" should be') +def assert_exported_yaml_file_content(context, file_path): + expected_content = context.text.strip().splitlines() + + with open(file_path, "r") as f: + actual_content = f.read().strip().splitlines() + + for actual_line, expected_line in zip(actual_content, expected_content): + if actual_line.startswith("tags: ") and expected_line.startswith("tags: "): + assert_equal_tags_ignoring_order(actual_line, expected_line) + else: + assert actual_line.strip() == expected_line.strip(), [ + actual_line.strip(), + expected_line.strip(), + ] + + +def assert_equal_tags_ignoring_order(actual_line, expected_line): + actual_tags = set(tag.strip() for tag in actual_line[len("tags: ") :].split(",")) + expected_tags = set( + tag.strip() for tag in expected_line[len("tags: ") :].split(",") + ) + assert actual_tags == expected_tags, [actual_tags, expected_tags] diff --git a/jrnl/export.py b/jrnl/export.py deleted file mode 100644 index e95d4c12..00000000 --- a/jrnl/export.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python - -from .util import ERROR_COLOR, RESET_COLOR -from .util import slugify -from .plugins.template import Template -import os - - -class Exporter: - """This Exporter can convert entries and journals into text files.""" - - def __init__(self, format): - with open("jrnl/templates/" + format + ".template") as f: - front_matter, body = f.read().strip("-\n").split("---", 2) - self.template = Template(body) - - def export_entry(self, entry): - """Returns a string representation of a single entry.""" - return str(entry) - - def _get_vars(self, journal): - return {"journal": journal, "entries": journal.entries, "tags": journal.tags} - - def export_journal(self, journal): - """Returns a string representation of an entire journal.""" - return self.template.render_block("journal", **self._get_vars(journal)) - - def write_file(self, journal, path): - """Exports a journal into a single file.""" - try: - with open(path, "w", encoding="utf-8") as f: - f.write(self.export_journal(journal)) - return f"[Journal exported to {path}]" - except OSError as e: - return f"[{ERROR_COLOR}ERROR{RESET_COLOR}: {e.filename} {e.strerror}]" - - def make_filename(self, entry): - return entry.date.strftime( - "%Y-%m-%d_{}.{}".format(slugify(entry.title), self.extension) - ) - - def write_files(self, journal, path): - """Exports a journal into individual files for each entry.""" - for entry in journal.entries: - try: - full_path = os.path.join(path, self.make_filename(entry)) - with open(full_path, "w", encoding="utf-8") as f: - f.write(self.export_entry(entry)) - except OSError as e: - return f"[{ERROR_COLOR}ERROR{RESET_COLOR}: {e.filename} {e.strerror}]" - return f"[Journal exported to {path}]" - - def export(self, journal, format="text", output=None): - """Exports to individual files if output is an existing path, or into - a single file if output is a file name, or returns the exporter's - representation as string if output is None.""" - if output and os.path.isdir(output): # multiple files - return self.write_files(journal, output) - elif output: # single file - return self.write_file(journal, output) - else: - return self.export_journal(journal) From 550347eab1101e3eef38d03dcc0b8531ec5585b5 Mon Sep 17 00:00:00 2001 From: MinchinWeb Date: Sat, 8 Feb 2020 13:34:52 -0700 Subject: [PATCH 08/40] Add UTC support for failing DayOne tests (#785) * [Dayone] don't break if the system timezone is UTC * [DayOne] re-enable tests that were failing on Travis * [DayOne] change as per code review to avoid `except: pass` --- features/dayone.feature | 13 +------------ jrnl/DayOneJournal.py | 6 +++++- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/features/dayone.feature b/features/dayone.feature index 51aa2033..8e50b42b 100644 --- a/features/dayone.feature +++ b/features/dayone.feature @@ -1,7 +1,5 @@ Feature: Dayone specific implementation details. - # fails when system time is UTC (as on Travis-CI) - @skip Scenario: Loading a DayOne Journal Given we use the config "dayone.yaml" When we run "jrnl -from 'feb 2013'" @@ -15,7 +13,7 @@ Feature: Dayone specific implementation details. 2013-07-17 11:38 This entry is starred! """ - # fails when system time is UTC (as on Travis-CI) + # broken still @skip Scenario: Entries without timezone information will be interpreted as in the current timezone Given we use the config "dayone.yaml" @@ -23,7 +21,6 @@ Feature: Dayone specific implementation details. Then we should get no error and the output should contain "2013-01-17T18:37Z" in the local time - @skip Scenario: Writing into Dayone Given we use the config "dayone.yaml" When we run "jrnl 01 may 1979: Being born hurts." @@ -33,8 +30,6 @@ Feature: Dayone specific implementation details. 1979-05-01 09:00 Being born hurts. """ - # fails when system time is UTC (as on Travis-CI) - @skip Scenario: Loading tags from a DayOne Journal Given we use the config "dayone.yaml" When we run "jrnl --tags" @@ -44,8 +39,6 @@ Feature: Dayone specific implementation details. @play : 1 """ - # fails when system time is UTC (as on Travis-CI) - @skip Scenario: Saving tags from a DayOne Journal Given we use the config "dayone.yaml" When we run "jrnl A hard day at @work" @@ -56,8 +49,6 @@ Feature: Dayone specific implementation details. @play : 1 """ - # fails when system time is UTC (as on Travis-CI) - @skip Scenario: Filtering by tags from a DayOne Journal Given we use the config "dayone.yaml" When we run "jrnl @work" @@ -66,8 +57,6 @@ Feature: Dayone specific implementation details. 2013-05-17 11:39 This entry has tags! """ - # fails when system time is UTC (as on Travis-CI) - @skip Scenario: Exporting dayone to json Given we use the config "dayone.yaml" When we run "jrnl --export json" diff --git a/jrnl/DayOneJournal.py b/jrnl/DayOneJournal.py index 83eb6788..8e8b2cd0 100644 --- a/jrnl/DayOneJournal.py +++ b/jrnl/DayOneJournal.py @@ -52,7 +52,11 @@ class DayOne(Journal.Journal): except (KeyError, pytz.exceptions.UnknownTimeZoneError): timezone = tzlocal.get_localzone() date = dict_entry["Creation Date"] - date = date + timezone.utcoffset(date, is_dst=False) + # convert the date to UTC rather than keep messing with + # timezones + if timezone.zone != "UTC": + date = date + timezone.utcoffset(date, is_dst=False) + entry = Entry.Entry( self, date, From 0cb6f5ec928bfc897b1dd88cc16289f279cf985e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Ooms?= Date: Sat, 8 Feb 2020 16:53:01 -0400 Subject: [PATCH 09/40] Fix typo in encryption docs (#812) * Explain how fish can be configured to exclude jrnl commands from history by default * Fix typo in encryption docs --- docs/encryption.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/encryption.md b/docs/encryption.md index 35a26313..dc3216d1 100644 --- a/docs/encryption.md +++ b/docs/encryption.md @@ -60,7 +60,7 @@ If you are using `fish` instead of `bash` or `zsh`, you can get the same behavio adding this to your `fish` configuration: ``` sh -abbr jrnl=" jrnl" +abbr jrnl " jrnl" ``` To delete existing `jrnl` commands from `fish`’s history, run From 7ac3e3160d7e7afaf1025cdd9309d048b1f1d7ac Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 8 Feb 2020 21:06:40 +0000 Subject: [PATCH 10/40] Updating changelog [ci skip] --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c0493e6..c25fd5fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,12 @@ - Update YAML exporter to handle Dayone format [\#773](https://github.com/jrnl-org/jrnl/pull/773) ([MinchinWeb](https://github.com/MinchinWeb)) +**Fixed bugs:** + +- Add UTC support for failing DayOne tests [\#785](https://github.com/jrnl-org/jrnl/pull/785) ([MinchinWeb](https://github.com/MinchinWeb)) + **Build:** -- Remove poetry from build system in pyproject config to fix `brew install` [\#830](https://github.com/jrnl-org/jrnl/pull/830) ([wren](https://github.com/wren)) - Fix all skipped tests on Travis Windows builds by preserving newlines [\#823](https://github.com/jrnl-org/jrnl/pull/823) ([micahellison](https://github.com/micahellison)) **Updated documentation:** From e5d7845f180d7b6f44d9e0bbd4d7aa2bfa7d425f Mon Sep 17 00:00:00 2001 From: MinchinWeb Date: Sat, 8 Feb 2020 14:14:33 -0700 Subject: [PATCH 11/40] Listing all entries in DayOne Classic journal throws IndexError (#786) * Reproduce bug in #780 :musical_note: I have no body, no body to love me... :musical_note: The bug is cause by a DayOne entry that has to entry body. * Deal with empty bodies Close #780. * [Travis-CI] add "tree" command to debug missing files * Fix file location I have no idea why, but it ran locally fine without issue. Travis is more particular... --- .travis.yml | 6 ++++ features/data/configs/bug780.yaml | 12 +++++++ .../48A25033B34047C591160A4480197D8B.doentry | 33 +++++++++++++++++++ features/regression.feature | 20 +++++++---- jrnl/Entry.py | 2 +- 5 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 features/data/configs/bug780.yaml create mode 100644 features/data/journals/bug780.dayone/entries/48A25033B34047C591160A4480197D8B.doentry diff --git a/.travis.yml b/.travis.yml index af825d4f..5f977e76 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,14 @@ git: depth: false autocrlf: false +addons: + apt: + packages: + - tree + before_install: - date + - tree install: - pip install poetry diff --git a/features/data/configs/bug780.yaml b/features/data/configs/bug780.yaml new file mode 100644 index 00000000..e1d830c2 --- /dev/null +++ b/features/data/configs/bug780.yaml @@ -0,0 +1,12 @@ +default_hour: 9 +default_minute: 0 +editor: '' +encrypt: false +highlight: true +journals: + default: features/journals/bug780.dayone +linewrap: 80 +tagsymbols: '@' +template: false +timeformat: '%Y-%m-%d %H:%M' +indent_character: "|" diff --git a/features/data/journals/bug780.dayone/entries/48A25033B34047C591160A4480197D8B.doentry b/features/data/journals/bug780.dayone/entries/48A25033B34047C591160A4480197D8B.doentry new file mode 100644 index 00000000..426f1ea8 --- /dev/null +++ b/features/data/journals/bug780.dayone/entries/48A25033B34047C591160A4480197D8B.doentry @@ -0,0 +1,33 @@ + + + + + Activity + Stationary + Creation Date + 2019-12-30T21:28:54Z + Entry Text + + Starred + + UUID + 48A25033B34047C591160A4480197D8B + Creator + + Device Agent + PC + Generation Date + 2019-12-30T21:28:54Z + Host Name + LE-TREPORT + OS Agent + Microsoft Windows/10 Home + Software Agent + Journaley/2.1 + + Tags + + i_have_no_body + + + diff --git a/features/regression.feature b/features/regression.feature index bedc1295..a8c1e237 100644 --- a/features/regression.feature +++ b/features/regression.feature @@ -15,13 +15,6 @@ Feature: Zapped bugs should stay dead. Then we should see the message "Entry added" and the journal should contain "[2013-11-30 15:42] Project Started." - Scenario: Date in the future should be parsed correctly - # https://github.com/maebert/jrnl/issues/185 - Given we use the config "basic.yaml" - When we run "jrnl 26/06/2019: Planet? Earth. Year? 2019." - Then we should see the message "Entry added" - and the journal should contain "[2019-06-26 09:00] Planet?" - Scenario: Loading entry with ambiguous time stamp #https://github.com/maebert/jrnl/issues/153 Given we use the config "bug153.yaml" @@ -32,6 +25,19 @@ Feature: Zapped bugs should stay dead. 2013-10-27 03:27 Some text. """ + Scenario: Date in the future should be parsed correctly + # https://github.com/maebert/jrnl/issues/185 + Given we use the config "basic.yaml" + When we run "jrnl 26/06/2019: Planet? Earth. Year? 2019." + Then we should see the message "Entry added" + and the journal should contain "[2019-06-26 09:00] Planet?" + + Scenario: Empty DayOne entry bodies should not error + # https://github.com/jrnl-org/jrnl/issues/780 + Given we use the config "bug780.yaml" + When we run "jrnl --short" + Then we should get no error + Scenario: Title with an embedded period. Given we use the config "basic.yaml" When we run "jrnl 04-24-2014: Created a new website - empty.com. Hope to get a lot of traffic." diff --git a/jrnl/Entry.py b/jrnl/Entry.py index 80754e05..01f56d5a 100755 --- a/jrnl/Entry.py +++ b/jrnl/Entry.py @@ -22,7 +22,7 @@ class Entry: def _parse_text(self): raw_text = self.text lines = raw_text.splitlines() - if lines[0].strip().endswith("*"): + if lines and lines[0].strip().endswith("*"): self.starred = True raw_text = lines[0].strip("\n *") + "\n" + "\n".join(lines[1:]) self._title, self._body = split_title(raw_text) From 4ab9fa573c2387fe5d0219391a3178fed768197e Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 8 Feb 2020 21:26:08 +0000 Subject: [PATCH 12/40] Updating changelog [ci skip] --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c25fd5fb..e0af57a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,14 @@ **Fixed bugs:** +- Listing all entries in DayOne Classic journal throws IndexError [\#786](https://github.com/jrnl-org/jrnl/pull/786) ([MinchinWeb](https://github.com/MinchinWeb)) - Add UTC support for failing DayOne tests [\#785](https://github.com/jrnl-org/jrnl/pull/785) ([MinchinWeb](https://github.com/MinchinWeb)) **Build:** +- Remove poetry from build system in pyproject config to fix `brew install` [\#830](https://github.com/jrnl-org/jrnl/pull/830) ([wren](https://github.com/wren)) - Fix all skipped tests on Travis Windows builds by preserving newlines [\#823](https://github.com/jrnl-org/jrnl/pull/823) ([micahellison](https://github.com/micahellison)) +- Update Black version and lock file to fix builds on develop branch [\#784](https://github.com/jrnl-org/jrnl/pull/784) ([wren](https://github.com/wren)) **Updated documentation:** From 89e0bbc3fb46b4f17b619692dc87ecbd6e573e9e Mon Sep 17 00:00:00 2001 From: Micah Ellison <4383304+micahellison@users.noreply.github.com> Date: Sat, 8 Feb 2020 13:26:44 -0800 Subject: [PATCH 13/40] Fix crashing unicode Travis tests on Windows and fail build if Windows tests fail --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index af825d4f..0ddf4c61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,7 +51,6 @@ jobs: fast_finish: true allow_failures: - python: nightly - - os: windows include: - name: Lint, via Black @@ -74,6 +73,7 @@ jobs: env: - JRNL_PYTHON_VERSION=3.6.8 - PATH=/c/Python36:/c/Python36/Scripts:$PATH + - PYTHONIOENCODING=UTF-8 # Python 3.7 Tests - name: Python 3.7 on Linux @@ -89,6 +89,7 @@ jobs: env: - JRNL_PYTHON_VERSION=3.7.5 - PATH=/c/Python37:/c/Python37/Scripts:$PATH + - PYTHONIOENCODING=UTF-8 # Python 3.8 Tests - name: Python 3.8 on Linux @@ -104,6 +105,7 @@ jobs: env: - JRNL_PYTHON_VERSION=3.8.0 - PATH=/c/Python38:/c/Python38/Scripts:$PATH + - PYTHONIOENCODING=UTF-8 # ... and beyond! - name: Python nightly on Linux From 40b330e5ee26861b4df0aceab70cc6b74e8ac86b Mon Sep 17 00:00:00 2001 From: micahellison <4383304+micahellison@users.noreply.github.com> Date: Sat, 8 Feb 2020 13:51:14 -0800 Subject: [PATCH 14/40] Fix crashing unicode Travis tests on Windows and fail build if Windows tests fail (#836) --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5f977e76..4753a731 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,7 +57,6 @@ jobs: fast_finish: true allow_failures: - python: nightly - - os: windows include: - name: Lint, via Black @@ -80,6 +79,7 @@ jobs: env: - JRNL_PYTHON_VERSION=3.6.8 - PATH=/c/Python36:/c/Python36/Scripts:$PATH + - PYTHONIOENCODING=UTF-8 # Python 3.7 Tests - name: Python 3.7 on Linux @@ -95,6 +95,7 @@ jobs: env: - JRNL_PYTHON_VERSION=3.7.5 - PATH=/c/Python37:/c/Python37/Scripts:$PATH + - PYTHONIOENCODING=UTF-8 # Python 3.8 Tests - name: Python 3.8 on Linux @@ -110,6 +111,7 @@ jobs: env: - JRNL_PYTHON_VERSION=3.8.0 - PATH=/c/Python38:/c/Python38/Scripts:$PATH + - PYTHONIOENCODING=UTF-8 # ... and beyond! - name: Python nightly on Linux From aaa2efddec9e0da2e8fcbabb0e2f7689eace2251 Mon Sep 17 00:00:00 2001 From: Micah Ellison <4383304+micahellison@users.noreply.github.com> Date: Sat, 8 Feb 2020 13:54:39 -0800 Subject: [PATCH 15/40] Restore emoji test now that it works in Windows Travis builds --- features/core.feature | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/features/core.feature b/features/core.feature index c023cd4c..34539efb 100644 --- a/features/core.feature +++ b/features/core.feature @@ -41,6 +41,14 @@ Feature: Basic reading and writing to a journal When we run "jrnl -on 'june 6 2013' --short" Then the output should be "2013-06-10 15:40 Life is good." + Scenario: Emoji support + Given we use the config "basic.yaml" + 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.yaml" When we run "jrnl" and enter "25 jul 2013: I saw Elvis. He's alive." From d495f6feaa5bccfcac7e4a69c90133517816888f Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 8 Feb 2020 22:03:03 +0000 Subject: [PATCH 16/40] Updating changelog [ci skip] --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0af57a4..12344d7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,9 +15,10 @@ **Build:** +- Fix crashing unicode Travis tests on Windows and fail build if Windows tests fail [\#836](https://github.com/jrnl-org/jrnl/pull/836) ([micahellison](https://github.com/micahellison)) - Remove poetry from build system in pyproject config to fix `brew install` [\#830](https://github.com/jrnl-org/jrnl/pull/830) ([wren](https://github.com/wren)) - Fix all skipped tests on Travis Windows builds by preserving newlines [\#823](https://github.com/jrnl-org/jrnl/pull/823) ([micahellison](https://github.com/micahellison)) -- Update Black version and lock file to fix builds on develop branch [\#784](https://github.com/jrnl-org/jrnl/pull/784) ([wren](https://github.com/wren)) +- Automagically update the changelog you see before your very eyes! [\#806](https://github.com/jrnl-org/jrnl/pull/806) ([wren](https://github.com/wren)) **Updated documentation:** From 686b111e82e126542b27e3eff1ec54783d7304bb Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 8 Feb 2020 17:33:47 -0800 Subject: [PATCH 17/40] Stop multipe changelog generators from crashing into each other --- .build/generate_changelog.sh | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.build/generate_changelog.sh b/.build/generate_changelog.sh index 58867c20..ea586f4c 100755 --- a/.build/generate_changelog.sh +++ b/.build/generate_changelog.sh @@ -1,5 +1,20 @@ #!/usr/bin/env bash +BRANCH=$TRAVIS_BRANCH +if [[ $TRAVIS_BRANCH == $TRAVIS_TAG ]]; then + BRANCH='master' +fi + +# Check if branch has been updated since this build started +# This tends to happen if multiple things have been merged in at the same time. +if [[ -z $TRAVIS_TAG ]]; then + git fetch origin + if [[ $(git rev-parse "origin/${BRANCH}") != $TRAVIS_COMMIT ]]; then + echo "${BRANCH} has been updated since build started. Aborting changelog." + exit 0 + fi +fi + FILENAME='CHANGELOG.md' # get the latest git tags @@ -40,11 +55,6 @@ docker run -it --rm -v "$(pwd)":/usr/local/src/your-app ferrarimarco/github-chan # Put back our link (instead of the broken one) sed -i 's!https://pypi.org/project/jrnl/HEAD/!https://github.com/jrnl-org/jrnl/!' "$FILENAME" -BRANCH=$TRAVIS_BRANCH -if [[ $TRAVIS_BRANCH == $TRAVIS_TAG ]]; then - BRANCH='master' -fi - git config --global user.email "jrnl.bot@gmail.com" git config --global user.name "Jrnl Bot" git checkout $BRANCH From 28c0d848455b99f9ad74e04e3a386f05d14dc6f5 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 8 Feb 2020 18:20:06 -0800 Subject: [PATCH 18/40] get rid of debug code --- .travis.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4753a731..0ddf4c61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,14 +9,8 @@ git: depth: false autocrlf: false -addons: - apt: - packages: - - tree - before_install: - date - - tree install: - pip install poetry From b76cd3dc4960545ea048254899e34a53562cbfbb Mon Sep 17 00:00:00 2001 From: Jonathan Wren <9453067+wren@users.noreply.github.com> Date: Sat, 8 Feb 2020 18:31:47 -0800 Subject: [PATCH 19/40] Update old links (#844) Since we moved the core repo to jrnl-org, the old links no longer work. --- docs/theme/index.html | 6 +++--- features/regression.feature | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/theme/index.html b/docs/theme/index.html index a0f620fb..2221e76f 100755 --- a/docs/theme/index.html +++ b/docs/theme/index.html @@ -34,7 +34,7 @@ "operatingSystem": ["macOS", "Windows", "Linux"], "thumbnailUrl": "https://jrnl.sh/img/banner_og.png", "installUrl": "https://jrnl.sh/installation", - "softwareVersion": "2.0.0rc2" + "softwareVersion": "2.2" } @@ -42,7 +42,7 @@
@@ -58,7 +58,7 @@
diff --git a/features/regression.feature b/features/regression.feature index a8c1e237..5aa1db06 100644 --- a/features/regression.feature +++ b/features/regression.feature @@ -1,7 +1,7 @@ Feature: Zapped bugs should stay dead. Scenario: Writing an entry does not print the entire journal - # https://github.com/maebert/jrnl/issues/87 + # https://github.com/jrnl-org/jrnl/issues/87 Given we use the config "basic.yaml" 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" @@ -9,14 +9,14 @@ Feature: Zapped bugs should stay dead. Then the output should not contain "Life is good" Scenario: Date with time should be parsed correctly - # https://github.com/maebert/jrnl/issues/117 + # https://github.com/jrnl-org/jrnl/issues/117 Given we use the config "basic.yaml" When we run "jrnl 2013-11-30 15:42: Project Started." Then we should see the message "Entry added" and the journal should contain "[2013-11-30 15:42] Project Started." Scenario: Loading entry with ambiguous time stamp - #https://github.com/maebert/jrnl/issues/153 + #https://github.com/jrnl-org/jrnl/issues/153 Given we use the config "bug153.yaml" When we run "jrnl -1" Then we should get no error @@ -26,7 +26,7 @@ Feature: Zapped bugs should stay dead. """ Scenario: Date in the future should be parsed correctly - # https://github.com/maebert/jrnl/issues/185 + # https://github.com/jrnl-org/jrnl/issues/185 Given we use the config "basic.yaml" When we run "jrnl 26/06/2019: Planet? Earth. Year? 2019." Then we should see the message "Entry added" From 2be0436ef17aaf61cdeb23fbf677c2f44f454aa1 Mon Sep 17 00:00:00 2001 From: Jonathan Wren <9453067+wren@users.noreply.github.com> Date: Sat, 8 Feb 2020 18:32:30 -0800 Subject: [PATCH 20/40] Update site description (#841) Don't know why this was what it was. Maybe an old joke I don't get? Either way, new description makes more sense. --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index ed1795d1..4c56567b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,7 +12,7 @@ markdown_extensions: - admonition repo_url: https://github.com/jrnl-org/jrnl/ site_author: Manuel Ebert -site_description: Never Worry about Money Again. +site_description: Collect your thoughts and notes without leaving the command line. nav: - Overview: overview.md - Quickstart: installation.md From b2eab4bad38c551b7cb4c0a2e7b80b01068ae7d1 Mon Sep 17 00:00:00 2001 From: Jonathan Wren <9453067+wren@users.noreply.github.com> Date: Sat, 8 Feb 2020 18:32:58 -0800 Subject: [PATCH 21/40] Get rid of dumb sex joke (#840) These kinds of jokes make us look bad, and have gotten this project bad PR on social media. It's best to get rid of them. We can still be super glib and funny without crass jokes. --- docs/usage.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 18b35d68..deb612be 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -119,10 +119,10 @@ Will print all entries in which either `@pinkie` or `@WorldDomination` occurred. ```sh -jrnl -n 5 -and @pineapple @lubricant +jrnl -n 5 -and @pinkie @WorldDomination ``` -the last five entries containing both `@pineapple` **and** `@lubricant`. +the last five entries containing both `@pinkie` **and** `@worldDomination`. You can change which symbols you'd like to use for tagging in the configuration. @@ -154,7 +154,7 @@ encrypt) your edited journal after you save and exit the editor. You can also use this feature for deleting entries from your journal ```sh -jrnl @girlfriend -until 'june 2012' --edit +jrnl @texas -until 'june 2012' --edit ``` Just select all text, press delete, and everything is gone... From 7f1f878a08901e790ce7a58c54128dcf8f9f3226 Mon Sep 17 00:00:00 2001 From: Jonathan Wren <9453067+wren@users.noreply.github.com> Date: Sat, 8 Feb 2020 18:33:28 -0800 Subject: [PATCH 22/40] Don't re-run tests on deployment (#839) By the time we get to the deployment step, we've already run these tests dozens of times. We don't need to run them yet again at deploy time. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0ddf4c61..a602f51b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -131,6 +131,8 @@ jobs: - poetry version "$TRAVIS_TAG" - echo __version__ = \"$TRAVIS_TAG\" > jrnl/__version__.py - poetry build + script: + - echo "Deployment starting..." deploy: - provider: script script: poetry publish From 74a11cebdd996593162d2f663af10f2510d83cdc Mon Sep 17 00:00:00 2001 From: Jonathan Wren <9453067+wren@users.noreply.github.com> Date: Sat, 8 Feb 2020 18:33:54 -0800 Subject: [PATCH 23/40] Put back build lines in poetry config (#838) Taking out these lines earlier fixed the homebrew release, but broke other things. So, I'm putting them back for now until we can find a better solution. --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 9dc76df4..c8c9984e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,3 +38,7 @@ black = {version = "^19.10b0",allow-prereleases = true} [tool.poetry.scripts] jrnl = 'jrnl.cli:run' +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" + From 95aa3bc1aec9da88678981741f859de5e54cc52a Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sun, 9 Feb 2020 03:31:51 +0000 Subject: [PATCH 24/40] Updating changelog [ci skip] --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12344d7c..026c1718 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,13 +15,18 @@ **Build:** +- Stop multipe changelog generators from crashing into each other [\#845](https://github.com/jrnl-org/jrnl/pull/845) ([wren](https://github.com/wren)) +- Don't re-run tests on deployment [\#839](https://github.com/jrnl-org/jrnl/pull/839) ([wren](https://github.com/wren)) +- Put back build lines in Poetry config [\#838](https://github.com/jrnl-org/jrnl/pull/838) ([wren](https://github.com/wren)) +- Restore emoji test [\#837](https://github.com/jrnl-org/jrnl/pull/837) ([micahellison](https://github.com/micahellison)) - Fix crashing unicode Travis tests on Windows and fail build if Windows tests fail [\#836](https://github.com/jrnl-org/jrnl/pull/836) ([micahellison](https://github.com/micahellison)) - Remove poetry from build system in pyproject config to fix `brew install` [\#830](https://github.com/jrnl-org/jrnl/pull/830) ([wren](https://github.com/wren)) - Fix all skipped tests on Travis Windows builds by preserving newlines [\#823](https://github.com/jrnl-org/jrnl/pull/823) ([micahellison](https://github.com/micahellison)) -- Automagically update the changelog you see before your very eyes! [\#806](https://github.com/jrnl-org/jrnl/pull/806) ([wren](https://github.com/wren)) **Updated documentation:** +- Update site description [\#841](https://github.com/jrnl-org/jrnl/pull/841) ([wren](https://github.com/wren)) +- Get rid of dumb sex joke [\#840](https://github.com/jrnl-org/jrnl/pull/840) ([wren](https://github.com/wren)) - Updating/clarifying template explanation [\#829](https://github.com/jrnl-org/jrnl/pull/829) ([heymajor](https://github.com/heymajor)) ## [v2.2](https://pypi.org/project/jrnl/v2.2/) (2020-02-01) From d605f4d011e4079aad919c3ee8511df5fcc1fe87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Ooms?= Date: Sun, 9 Feb 2020 15:23:53 +0100 Subject: [PATCH 25/40] docs: Fix fish history instructions. Use `--add` flag in fish `abbr` command. --- docs/encryption.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/encryption.md b/docs/encryption.md index dc3216d1..9909af67 100644 --- a/docs/encryption.md +++ b/docs/encryption.md @@ -60,7 +60,7 @@ If you are using `fish` instead of `bash` or `zsh`, you can get the same behavio adding this to your `fish` configuration: ``` sh -abbr jrnl " jrnl" +abbr --add jrnl " jrnl" ``` To delete existing `jrnl` commands from `fish`’s history, run From fb97bd19f5cf37cdc423af28fdfd038436a53de9 Mon Sep 17 00:00:00 2001 From: Leroy van Logchem Date: Mon, 17 Feb 2020 23:24:24 +0100 Subject: [PATCH 26/40] Update recipes.md Fixed links to various documentation files --- docs/recipes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/recipes.md b/docs/recipes.md index d688b429..a702b9d6 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -71,7 +71,7 @@ jrnlimport () { ### Using templates !!! note - Templates require an [external editor](../advanced) be configured. + Templates require an [external editor](./advanced.md) be configured. A template is a code snippet that makes it easier to enter use repeated text each time a new journal entry is started. There are two ways you can utilize @@ -113,7 +113,7 @@ logged as a new entry in the journal you specified in the original argument. !!! tip To read your journal entry or to verify the entry saved, you can use this - command: `jrnl -n 1` (Check out [Import and Export](../export/#export-to-files) for more export options). + command: `jrnl -n 1` (Check out [Import and Export](./export.md) for more export options). ```sh jrnl -n 1 @@ -155,7 +155,7 @@ jrnl -on "$(jrnl --short | shuf -n 1 | cut -d' ' -f1,2)" ## External editors Configure your preferred external editor by updating the `editor` option -in your `jrnl.yaml` file. (See [advanced usage](../advanced) for details). +in your `jrnl.yaml` file. (See [advanced usage](./advanced.md) for details). !!! note To save and log any entry edits, save and close the file. From 6ea974f82a2b0c101df7361022178e0730177af2 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 22 Feb 2020 19:22:51 +0000 Subject: [PATCH 27/40] Updating changelog [ci skip] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 026c1718..3759b650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ **Updated documentation:** +- Docs: Fix broken links in recipes.md [\#854](https://github.com/jrnl-org/jrnl/pull/854) ([lrvl](https://github.com/lrvl)) - Update site description [\#841](https://github.com/jrnl-org/jrnl/pull/841) ([wren](https://github.com/wren)) - Get rid of dumb sex joke [\#840](https://github.com/jrnl-org/jrnl/pull/840) ([wren](https://github.com/wren)) - Updating/clarifying template explanation [\#829](https://github.com/jrnl-org/jrnl/pull/829) ([heymajor](https://github.com/heymajor)) From 6e9f95623a9652e064121187a28beac661d36203 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 22 Feb 2020 19:50:50 +0000 Subject: [PATCH 28/40] Updating changelog [ci skip] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3759b650..a5211462 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ **Updated documentation:** - Docs: Fix broken links in recipes.md [\#854](https://github.com/jrnl-org/jrnl/pull/854) ([lrvl](https://github.com/lrvl)) +- docs: Fix fish history instructions. [\#846](https://github.com/jrnl-org/jrnl/pull/846) ([aureooms](https://github.com/aureooms)) - Update site description [\#841](https://github.com/jrnl-org/jrnl/pull/841) ([wren](https://github.com/wren)) - Get rid of dumb sex joke [\#840](https://github.com/jrnl-org/jrnl/pull/840) ([wren](https://github.com/wren)) - Updating/clarifying template explanation [\#829](https://github.com/jrnl-org/jrnl/pull/829) ([heymajor](https://github.com/heymajor)) From 10d2178d5abcc5ec9352fde9eac77b63805abfdd Mon Sep 17 00:00:00 2001 From: Manuel Ebert Date: Fri, 21 Feb 2020 18:36:16 -0800 Subject: [PATCH 29/40] Clean up shortcut for Limit This piece of code has bothered me for more than 6 years! (See #131) - this moves parsing arguments to where it belongs. --- jrnl/cli.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/jrnl/cli.py b/jrnl/cli.py index 1c53dc29..408f3518 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -15,6 +15,7 @@ from .util import ERROR_COLOR, RESET_COLOR, UserAbort import jrnl import argparse import sys +import re import logging log = logging.getLogger(__name__) @@ -173,7 +174,11 @@ def parse_args(args=None): action="store_true", ) - return parser.parse_args(args) + # Handle '-123' as a shortcut for '-n 123' + num = re.compile(r"^-(\d+)$") + if args is None: + args = sys.argv[1:] + return parser.parse_args([num.sub(r"-n \1", a) for a in args]) def guess_mode(args, config): @@ -309,14 +314,6 @@ def run(manual_args=None): config = util.scope_config(config, journal_name) - # If the first remaining argument looks like e.g. '-3', interpret that as a limiter - if not args.limit and args.text and args.text[0].startswith("-"): - try: - args.limit = int(args.text[0].lstrip("-")) - args.text = args.text[1:] - except ValueError: - pass - log.debug('Using journal "%s"', journal_name) mode_compose, mode_export, mode_import = guess_mode(args, config) From 53c557005c00f5d0fa235baa66c50cf1afccab52 Mon Sep 17 00:00:00 2001 From: Micah Ellison <4383304+micahellison@users.noreply.github.com> Date: Sat, 29 Feb 2020 12:35:05 -0800 Subject: [PATCH 30/40] Update FolderJournal reference to entry __unicode__ method to __str__ --- jrnl/FolderJournal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jrnl/FolderJournal.py b/jrnl/FolderJournal.py index 4ca55335..ac3dabd8 100644 --- a/jrnl/FolderJournal.py +++ b/jrnl/FolderJournal.py @@ -59,7 +59,7 @@ class Folder(Journal.Journal): for e in self.entries: if e.date.year == d.year and e.date.month == d.month and e.date.day == d.day: write_entries.append(e) - journal = "\n".join([e.__unicode__() for e in write_entries]) + journal = "\n".join([e.__str__() for e in write_entries]) with codecs.open(filename, 'w', "utf-8") as journal_file: journal_file.write(journal) #look for and delete empty files From 71f918d87827d790327a6f62805730d39952a444 Mon Sep 17 00:00:00 2001 From: Micah Ellison <4383304+micahellison@users.noreply.github.com> Date: Sat, 29 Feb 2020 12:45:47 -0800 Subject: [PATCH 31/40] Remove DayOne test made obsolete by FolderJournal --- features/dayone_regressions.feature | 6 ------ 1 file changed, 6 deletions(-) diff --git a/features/dayone_regressions.feature b/features/dayone_regressions.feature index 62c8cc24..3e98f9e9 100644 --- a/features/dayone_regressions.feature +++ b/features/dayone_regressions.feature @@ -23,9 +23,3 @@ Feature: Zapped Dayone bugs stay dead! 2014-04-24 09:00 Ran 6.2 miles today in 1:02:03. | I'm feeling sore because I forgot to stretch. """ - - Scenario: Opening an folder that's not a DayOne folder gives a nice error message - Given we use the config "empty_folder.yaml" - When we run "jrnl Herro" - Then we should get an error - Then we should see the message "is a directory, but doesn't seem to be a DayOne journal either" From 8a4fe0ec81760db06e119fd374fd74d0e4837985 Mon Sep 17 00:00:00 2001 From: Micah Ellison <4383304+micahellison@users.noreply.github.com> Date: Sat, 29 Feb 2020 13:29:19 -0800 Subject: [PATCH 32/40] Fix FolderJournal path tests for Windows paths --- features/folder.feature | 7 +++---- features/steps/core.py | 5 +++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/features/folder.feature b/features/folder.feature index b747e6c1..650ac53e 100644 --- a/features/folder.feature +++ b/features/folder.feature @@ -15,7 +15,7 @@ Feature: Testing a journal with a root directory and multiple files in the forma When we run "jrnl 23 July 2013: Testing folder journal." Then we should see the message "Entry added" When the journal directory is listed - Then the output should contain "2013/07/23.txt" + Then the output should contain "2013/07/23.txt" or "2013\07\23.txt" Scenario: Adding multiple entries to a Folder journal should generate multiple date files @@ -24,9 +24,8 @@ Feature: Testing a journal with a root directory and multiple files in the forma And we run "jrnl 3/7/2014: Second entry of journal." Then we should see the message "Entry added" When the journal directory is listed - Then the output should contain "2013/07/23.txt" - And the output should contain "2014/03/07.txt" - + Then the output should contain "2013/07/23.txt" or "2013\07\23.txt" + Then the output should contain "2014/03/07.txt" or "2014\03\07.txt" Scenario: Out of order entries to a Folder journal should be listed in date order Given we use the config "empty_folder.yaml" diff --git a/features/steps/core.py b/features/steps/core.py index 6ada7b2f..98822813 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -210,10 +210,11 @@ def check_output_time_inline(context, text): @then("the output should contain") @then('the output should contain "{text}"') -def check_output_inline(context, text=None): +@then('the output should contain "{text}" or "{text2}"') +def check_output_inline(context, text=None, text2=None): text = text or context.text out = context.stdout_capture.getvalue() - assert text in out, text + assert (text in out or text2 in out), text or text2 @then('the output should not contain "{text}"') From da30eaa62ffeef05ae0899dbc61b095889eafff3 Mon Sep 17 00:00:00 2001 From: Micah Ellison <4383304+micahellison@users.noreply.github.com> Date: Sat, 29 Feb 2020 14:03:54 -0800 Subject: [PATCH 33/40] Apply Black linting --- features/steps/core.py | 14 ++++++++------ jrnl/EncryptedJournal.py | 2 +- jrnl/FolderJournal.py | 35 ++++++++++++++++++++++------------- jrnl/Journal.py | 1 + 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/features/steps/core.py b/features/steps/core.py index 98822813..82cae225 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -214,7 +214,7 @@ def check_output_time_inline(context, text): def check_output_inline(context, text=None, text2=None): text = text or context.text out = context.stdout_capture.getvalue() - assert (text in out or text2 in out), text or text2 + assert text in out or text2 in out, text or text2 @then('the output should not contain "{text}"') @@ -270,15 +270,17 @@ def check_journal_entries(context, number, journal_name="default"): journal = open_journal(journal_name) assert len(journal.entries) == number -@when('the journal directory is listed') + +@when("the journal directory is listed") def list_journal_directory(context, journal="default"): - files=[] + files = [] with open(install.CONFIG_FILE_PATH) as config_file: config = yaml.load(config_file, Loader=yaml.FullLoader) - journal_path = config['journals'][journal] + journal_path = config["journals"][journal] for root, dirnames, f in os.walk(journal_path): - for file in f: - print(os.path.join(root,file)) + for file in f: + print(os.path.join(root, file)) + @then("fail") def debug_fail(context): diff --git a/jrnl/EncryptedJournal.py b/jrnl/EncryptedJournal.py index d6681a47..cc5af748 100644 --- a/jrnl/EncryptedJournal.py +++ b/jrnl/EncryptedJournal.py @@ -23,7 +23,7 @@ def make_key(password): length=32, # Salt is hard-coded salt=b"\xf2\xd5q\x0e\xc1\x8d.\xde\xdc\x8e6t\x89\x04\xce\xf8", - iterations=100000, + iterations=100_000, backend=default_backend(), ) key = kdf.derive(password) diff --git a/jrnl/FolderJournal.py b/jrnl/FolderJournal.py index ac3dabd8..19519a14 100644 --- a/jrnl/FolderJournal.py +++ b/jrnl/FolderJournal.py @@ -8,11 +8,12 @@ import codecs import os import fnmatch + def get_files(journal_config): """Searches through sub directories starting with journal_config and find all text files""" filenames = [] for root, dirnames, f in os.walk(journal_config): - for filename in fnmatch.filter(f, '*.txt'): + for filename in fnmatch.filter(f, "*.txt"): filenames.append(os.path.join(root, filename)) return filenames @@ -25,11 +26,10 @@ class Folder(Journal.Journal): self._diff_entry_dates = [] super(Folder, self).__init__(**kwargs) - def open(self): filenames = [] self.entries = [] - filenames = get_files(self.config['journal']) + filenames = get_files(self.config["journal"]) for filename in filenames: with codecs.open(filename, "r", "utf-8") as f: journal = f.read() @@ -39,7 +39,7 @@ class Folder(Journal.Journal): def write(self): """Writes only the entries that have been modified into proper files.""" - #Create a list of dates of modified entries. Start with diff_entry_dates + # Create a list of dates of modified entries. Start with diff_entry_dates modified_dates = self._diff_entry_dates seen_dates = set(self._diff_entry_dates) for e in self.entries: @@ -48,26 +48,35 @@ class Folder(Journal.Journal): modified_dates.append(e.date) seen_dates.add(e.date) - #For every date that had a modified entry, write to a file + # For every date that had a modified entry, write to a file for d in modified_dates: - write_entries=[] - filename = os.path.join(self.config['journal'], d.strftime("%Y"), d.strftime("%m"), d.strftime("%d")+".txt") + write_entries = [] + filename = os.path.join( + self.config["journal"], + d.strftime("%Y"), + d.strftime("%m"), + d.strftime("%d") + ".txt", + ) dirname = os.path.dirname(filename) - #create directory if it doesn't exist + # create directory if it doesn't exist if not os.path.exists(dirname): os.makedirs(dirname) for e in self.entries: - if e.date.year == d.year and e.date.month == d.month and e.date.day == d.day: + if ( + e.date.year == d.year + and e.date.month == d.month + and e.date.day == d.day + ): write_entries.append(e) journal = "\n".join([e.__str__() for e in write_entries]) - with codecs.open(filename, 'w', "utf-8") as journal_file: + with codecs.open(filename, "w", "utf-8") as journal_file: journal_file.write(journal) - #look for and delete empty files + # look for and delete empty files filenames = [] - filenames = get_files(self.config['journal']) + filenames = get_files(self.config["journal"]) for filename in filenames: if os.stat(filename).st_size <= 0: - #print("empty file: {}".format(filename)) + # print("empty file: {}".format(filename)) os.remove(filename) def parse_editable_str(self, edited): diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 2310579a..9d868807 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -379,6 +379,7 @@ def open_journal(name, config, legacy=False): return DayOneJournal.DayOne(**config).open() else: from . import FolderJournal + return FolderJournal.Folder(**config).open() if not config["encrypt"]: From 47e0305aa002143e7a3aadc7e1bc0ba85d966479 Mon Sep 17 00:00:00 2001 From: Aaron Lichtman Date: Tue, 19 Nov 2019 04:56:57 +0100 Subject: [PATCH 34/40] Pretty print journal entries (#692) * Pretty print journal entry titles and dates. Changes appearance of all jrnl viewing commands, such as $ jrnl --short and $ jrnl -n {NUM}. Fix #508 * Removed extra newline at end of title * Use ansiwrap to properly wrap strings with ANSI escapes * Add ansiwrap to pyproject.toml * Allow configuration of colors - Replaced raw escapes with colorama - Added colors key to config - Add checks for validity of color values * Add color configuration documentation * Fix broken tests due to config change * Add tests for colors in configs - Identifying invalid color configs - Upgrading config from no colors -> colors * Add colorama dependency for all platforms * Allow users to disable colorization of output * Update poetry.lock * Add tag and body color customization options * Fix colorization of tags in title and body * Updated tests to use no color by default * Change pass to continue in verify_config() * Better style in Entry.py * Reduce code duplication for tag highlighting - Breaks "unreadable date" regression test for unknown reason * Properly colorize tags and print body * Reformatting and clean up * Replace list comprehension with generator * Handle invalid colors by not using a color * Process ANSI escapes properly with behave * Fixed the 'spaces after tags directly next to punctuation' bug Broke processing of tags next to any punctuation at all * Closer to working tag colorization but not perfect * Add tests printing for multiline journals Fix #717 * Correctly indent first line of multiline entry * Add test for multiline entries with tags * Remove redundant UNICODE flag * Progress towards proper tag colorization and body formatting * Fix newline colorization bug Debug code left intact since there are more bugs to fix :/ * And now the space just ends up before the tag instead of after it * Fix assertion syntax warning * Moved tag test to tagging.feature file * Strip out debug code and clean up * Bold datetimes in title * Bold all titles Fix #720 * Remove PY2 and PY3 constants * Fix regression in features/steps/core.py * Fix tag_regex * Remove redundant re.UNICODE flag * Remove extraneous code --- docs/advanced.md | 5 +- features/core.feature | 21 + features/data/configs/basic.yaml | 5 + features/data/configs/bug153.yaml | 5 + features/data/configs/invalid_color.yaml | 17 + .../data/configs/markdown-headings-335.yaml | 5 + features/data/configs/multiline-tags.yaml | 17 + features/data/configs/multiline.yaml | 17 + features/data/configs/no_colors.yaml | 12 + features/data/configs/tags-216.yaml | 5 + features/data/configs/tags-237.yaml | 5 + features/data/configs/tags.yaml | 5 + features/data/configs/unreadabledates.yaml | 5 + features/data/journals/multiline-tags.journal | 7 + features/data/journals/multiline.journal | 5 + features/steps/core.py | 77 ++- features/tagging.feature | 16 + features/upgrade.feature | 6 + jrnl/Entry.py | 74 ++- jrnl/Journal.py | 15 +- jrnl/install.py | 6 +- jrnl/util.py | 107 +++- poetry.lock | 514 +++++++++++++++--- pyproject.toml | 3 +- 24 files changed, 835 insertions(+), 119 deletions(-) create mode 100644 features/data/configs/invalid_color.yaml create mode 100644 features/data/configs/multiline-tags.yaml create mode 100644 features/data/configs/multiline.yaml create mode 100644 features/data/configs/no_colors.yaml create mode 100644 features/data/journals/multiline-tags.journal create mode 100644 features/data/journals/multiline.journal diff --git a/docs/advanced.md b/docs/advanced.md index 0375da10..84bf022d 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -35,8 +35,9 @@ and can be edited with a plain text editor. - `highlight` if `true`, tags will be highlighted in cyan. - `linewrap` - controls the width of the output. Set to `false` if you don't - want to wrap long lines. + controls the width of the output. Set to `false` if you don't want to wrap long lines. + - `colors` + dictionary that controls the colors used to display journal entries. It has two subkeys, which are: `date` and `title`. Current valid values are: `BLACK`, `RED`, `GREEN`, `YELLOW`, `BLUE`, `MAGENTA`, `CYAN`, and `WHITE`. `colorama.Fore` is used for colorization, and you can find the [docs here](https://github.com/tartley/colorama#colored-output). To disable colored output, set the value to `NONE`. If you set the value of any color subkey to an invalid color, no color will be used. !!! note Although it seems intuitive to use the `#` diff --git a/features/core.feature b/features/core.feature index 34539efb..3c49324c 100644 --- a/features/core.feature +++ b/features/core.feature @@ -13,6 +13,19 @@ Feature: Basic reading and writing to a journal | But I'm better. """ + Scenario: Printing a journal that has multiline entries + Given we use the config "multiline.yaml" + When we run "jrnl -n 1" + Then we should get no error + and the output should be + """ + 2013-06-09 15:39 Multiple line entry. + | This is the first line. + | This line doesn't have any ending punctuation + | + | There is a blank line above this. + """ + Scenario: Writing an entry from command line Given we use the config "basic.yaml" When we run "jrnl 23 july 2013: A cold and stormy day. I ate crisps on the sofa." @@ -72,3 +85,11 @@ Feature: Basic reading and writing to a journal When we run "jrnl -on 2013-06-10 -s" Then the output should be "2013-06-10 15:40 Life is good." + Scenario: Invalid color configuration + Given we use the config "invalid_color.yaml" + When we run "jrnl -on 2013-06-10 -s" + Then the output should be + """ + 2013-06-10 15:40 Life is good. + """ + And we should get no error diff --git a/features/data/configs/basic.yaml b/features/data/configs/basic.yaml index 9111b561..020bab18 100644 --- a/features/data/configs/basic.yaml +++ b/features/data/configs/basic.yaml @@ -10,3 +10,8 @@ tagsymbols: "@" template: false timeformat: "%Y-%m-%d %H:%M" indent_character: "|" +colors: + date: none + title: none + body: none + tags: none diff --git a/features/data/configs/bug153.yaml b/features/data/configs/bug153.yaml index 5bbcbf4a..ff645ab6 100644 --- a/features/data/configs/bug153.yaml +++ b/features/data/configs/bug153.yaml @@ -10,3 +10,8 @@ tagsymbols: '@' template: false timeformat: '%Y-%m-%d %H:%M' indent_character: "|" +colors: + date: none + title: none + body: none + tags: none diff --git a/features/data/configs/invalid_color.yaml b/features/data/configs/invalid_color.yaml new file mode 100644 index 00000000..25c0e58d --- /dev/null +++ b/features/data/configs/invalid_color.yaml @@ -0,0 +1,17 @@ +default_hour: 9 +default_minute: 0 +editor: "" +encrypt: false +highlight: true +journals: + default: features/journals/simple.journal +linewrap: 80 +tagsymbols: "@" +template: false +timeformat: "%Y-%m-%d %H:%M" +indent_character: "|" +colors: + date: not-a-color + title: also-not-a-color + body: still-no-color + tags: me-too diff --git a/features/data/configs/markdown-headings-335.yaml b/features/data/configs/markdown-headings-335.yaml index 5c33c53e..4368f641 100644 --- a/features/data/configs/markdown-headings-335.yaml +++ b/features/data/configs/markdown-headings-335.yaml @@ -10,3 +10,8 @@ linewrap: 80 tagsymbols: '@' timeformat: '%Y-%m-%d %H:%M' indent_character: "|" +colors: + date: none + title: none + body: none + tags: none diff --git a/features/data/configs/multiline-tags.yaml b/features/data/configs/multiline-tags.yaml new file mode 100644 index 00000000..033aaa27 --- /dev/null +++ b/features/data/configs/multiline-tags.yaml @@ -0,0 +1,17 @@ +default_hour: 9 +default_minute: 0 +editor: "" +encrypt: false +highlight: true +journals: + default: features/journals/multiline-tags.journal +linewrap: 80 +tagsymbols: "@" +template: false +timeformat: "%Y-%m-%d %H:%M" +indent_character: "|" +colors: + date: none + title: none + body: none + tags: none diff --git a/features/data/configs/multiline.yaml b/features/data/configs/multiline.yaml new file mode 100644 index 00000000..aa35b3f5 --- /dev/null +++ b/features/data/configs/multiline.yaml @@ -0,0 +1,17 @@ +default_hour: 9 +default_minute: 0 +editor: "" +encrypt: false +highlight: true +journals: + default: features/journals/multiline.journal +linewrap: 80 +tagsymbols: "@" +template: false +timeformat: "%Y-%m-%d %H:%M" +indent_character: "|" +colors: + date: none + title: none + body: none + tags: none diff --git a/features/data/configs/no_colors.yaml b/features/data/configs/no_colors.yaml new file mode 100644 index 00000000..9111b561 --- /dev/null +++ b/features/data/configs/no_colors.yaml @@ -0,0 +1,12 @@ +default_hour: 9 +default_minute: 0 +editor: "" +encrypt: false +highlight: true +journals: + default: features/journals/simple.journal +linewrap: 80 +tagsymbols: "@" +template: false +timeformat: "%Y-%m-%d %H:%M" +indent_character: "|" diff --git a/features/data/configs/tags-216.yaml b/features/data/configs/tags-216.yaml index b71abea5..81b3865f 100644 --- a/features/data/configs/tags-216.yaml +++ b/features/data/configs/tags-216.yaml @@ -10,3 +10,8 @@ linewrap: 80 tagsymbols: '@' timeformat: '%Y-%m-%d %H:%M' indent_character: "|" +colors: + date: none + title: none + body: none + tags: none diff --git a/features/data/configs/tags-237.yaml b/features/data/configs/tags-237.yaml index a5a70d99..5aecd61e 100644 --- a/features/data/configs/tags-237.yaml +++ b/features/data/configs/tags-237.yaml @@ -10,3 +10,8 @@ linewrap: 80 tagsymbols: '@' timeformat: '%Y-%m-%d %H:%M' indent_character: "|" +colors: + date: none + title: none + body: none + tags: none diff --git a/features/data/configs/tags.yaml b/features/data/configs/tags.yaml index 28ef5f69..4b55952c 100644 --- a/features/data/configs/tags.yaml +++ b/features/data/configs/tags.yaml @@ -10,3 +10,8 @@ linewrap: 80 tagsymbols: '@' timeformat: '%Y-%m-%d %H:%M' indent_character: "|" +colors: + date: none + title: none + body: none + tags: none diff --git a/features/data/configs/unreadabledates.yaml b/features/data/configs/unreadabledates.yaml index 474e7aae..99304e5a 100644 --- a/features/data/configs/unreadabledates.yaml +++ b/features/data/configs/unreadabledates.yaml @@ -10,3 +10,8 @@ tagsymbols: "@" template: false timeformat: "%Y-%m-%d %H:%M" indent_character: "|" +colors: + date: none + title: none + body: none + tags: none diff --git a/features/data/journals/multiline-tags.journal b/features/data/journals/multiline-tags.journal new file mode 100644 index 00000000..1fb8706f --- /dev/null +++ b/features/data/journals/multiline-tags.journal @@ -0,0 +1,7 @@ +[2013-06-09 15:39] Multiple @line entry with @tags. +Tag with @punctuation. afterwards +@TagOnLineAloneWithOutPunctuation +@TagOnLineAloneWithPunctuation. +Text before @tag. And After. +@hi. Hello +hi Hello \ No newline at end of file diff --git a/features/data/journals/multiline.journal b/features/data/journals/multiline.journal new file mode 100644 index 00000000..294ed141 --- /dev/null +++ b/features/data/journals/multiline.journal @@ -0,0 +1,5 @@ +[2013-06-09 15:39] Multiple line entry. +This is the first line. +This line doesn't have any ending punctuation + +There is a blank line above this. diff --git a/features/steps/core.py b/features/steps/core.py index 82cae225..e37c0a08 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -10,7 +10,9 @@ try: except ImportError: import parsedatetime as pdt import time +from codecs import encode, decode import os +import ast import json import yaml import keyring @@ -184,11 +186,69 @@ def no_error(context): assert context.exit_status == 0, context.exit_status +@then("the output should be parsable as json") +def check_output_json(context): + out = context.stdout_capture.getvalue() + assert json.loads(out), out + + +@then('"{field}" in the json output should have {number:d} elements') +@then('"{field}" in the json output should have 1 element') +def check_output_field(context, field, number=1): + out = context.stdout_capture.getvalue() + out_json = json.loads(out) + assert field in out_json, [field, out_json] + assert len(out_json[field]) == number, len(out_json[field]) + + +@then('"{field}" in the json output should not contain "{key}"') +def check_output_field_not_key(context, field, key): + out = context.stdout_capture.getvalue() + out_json = json.loads(out) + assert field in out_json + assert key not in out_json[field] + + +@then('"{field}" in the json output should contain "{key}"') +def check_output_field_key(context, field, key): + out = context.stdout_capture.getvalue() + out_json = json.loads(out) + assert field in out_json + assert key in out_json[field] + + +@then('the json output should contain {path} = "{value}"') +def check_json_output_path(context, path, value): + """ E.g. + the json output should contain entries.0.title = "hello" + """ + out = context.stdout_capture.getvalue() + struct = json.loads(out) + + for node in path.split("."): + try: + struct = struct[int(node)] + except ValueError: + struct = struct[node] + assert struct == value, struct + + +def process_ANSI_escapes(text): + """Escapes and 'unescapes' a string with ANSI escapes so that behave stdout + comparisons work properly. This will render colors, and works with unicode + characters. https://stackoverflow.com/a/57192592 + :param str text: The text to be escaped and unescaped + :return: Colorized / escaped text + :rtype: str + """ + return decode(encode(text, "latin-1", "backslashreplace"), "unicode-escape") + + @then("the output should be") @then('the output should be "{text}"') def check_output(context, text=None): text = (text or context.text).strip().splitlines() - out = context.stdout_capture.getvalue().strip().splitlines() + out = process_ANSI_escapes(context.stdout_capture.getvalue().strip()).splitlines() assert len(text) == len(out), "Output has {} lines (expected: {})".format( len(out), len(text) ) @@ -201,7 +261,7 @@ def check_output(context, text=None): @then('the output should contain "{text}" in the local time') def check_output_time_inline(context, text): - out = context.stdout_capture.getvalue() + out = process_ANSI_escapes(context.stdout_capture.getvalue()) local_tz = tzlocal.get_localzone() date, flag = CALENDAR.parse(text) output_date = time.strftime("%Y-%m-%d %H:%M", date) @@ -213,7 +273,7 @@ def check_output_time_inline(context, text): @then('the output should contain "{text}" or "{text2}"') def check_output_inline(context, text=None, text2=None): text = text or context.text - out = context.stdout_capture.getvalue() + out = process_ANSI_escapes(context.stdout_capture.getvalue()) assert text in out or text2 in out, text or text2 @@ -253,8 +313,15 @@ def journal_doesnt_exist(context, journal_name="default"): @then('the config should have "{key}" set to "{value}"') @then('the config for journal "{journal}" should have "{key}" set to "{value}"') def config_var(context, key, value, journal=None): - t, value = value.split(":") - value = {"bool": lambda v: v.lower() == "true", "int": int, "str": str}[t](value) + if not value[0] == "{": + t, value = value.split(":") + value = {"bool": lambda v: v.lower() == "true", "int": int, "str": str}[t]( + value + ) + else: + # Handle value being a dictionary + value = ast.literal_eval(value) + config = util.load_config(install.CONFIG_FILE_PATH) if journal: config = config["journals"][journal] diff --git a/features/tagging.feature b/features/tagging.feature index 2cbf7ce1..8b4cefb7 100644 --- a/features/tagging.feature +++ b/features/tagging.feature @@ -82,3 +82,19 @@ Feature: Tagging """ @thought : 2 """ + + + Scenario: Printing a journal that has multiline entries with tags + Given we use the config "multiline-tags.yaml" + When we run "jrnl -n 1" + Then we should get no error + and the output should be + """ + 2013-06-09 15:39 Multiple @line entry with @tags. + | Tag with @punctuation. afterwards + | @TagOnLineAloneWithOutPunctuation + | @TagOnLineAloneWithPunctuation. + | Text before @tag. And After. + | @hi. Hello + | hi Hello + """ \ No newline at end of file diff --git a/features/upgrade.feature b/features/upgrade.feature index ef597d4f..e4377970 100644 --- a/features/upgrade.feature +++ b/features/upgrade.feature @@ -22,6 +22,11 @@ Feature: Upgrading Journals from 1.x.x to 2.x.x Then the output should contain "Password" and the output should contain "2013-06-10 15:40 Life is good" + Scenario: Upgrading a config without colors to colors + Given we use the config "no_colors.yaml" + When we run "jrnl -n 1" + Then the config should have "colors" set to "{'date':'none', 'title':'none', 'body':'none', 'tags':'none'}" + Scenario: Upgrade and parse journals with little endian date format Given we use the config "upgrade_from_195_little_endian_dates.json" When we run "jrnl -9" and enter "Y" @@ -32,3 +37,4 @@ Feature: Upgrading Journals from 1.x.x to 2.x.x 10.06.2013 15:40 He said "[this] is the best time to be alive". """ Then the journal should have 2 entries + diff --git a/jrnl/Entry.py b/jrnl/Entry.py index 01f56d5a..02f7c468 100755 --- a/jrnl/Entry.py +++ b/jrnl/Entry.py @@ -1,9 +1,9 @@ #!/usr/bin/env python import re -import textwrap +import ansiwrap from datetime import datetime -from .util import split_title +from .util import split_title, colorize, highlight_tags_with_background_color class Entry: @@ -49,7 +49,7 @@ class Entry: @staticmethod def tag_regex(tagsymbols): - pattern = fr"(?u)(?:^|\s)([{tagsymbols}][-+*#/\w]+)" + pattern = fr"(? 20 or not all( diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 9d868807..3302cfdd 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -153,20 +153,7 @@ class Journal: def pprint(self, short=False): """Prettyprints the journal's entries""" - sep = "\n" - pp = sep.join([e.pprint(short=short) for e in self.entries]) - 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: util.colorize(match.group(0)), pp) - else: - pp = re.sub( - Entry.Entry.tag_regex(self.config["tagsymbols"]), - lambda match: util.colorize(match.group(0)), - pp, - ) - return pp + return "\n".join([e.pprint(short=short) for e in self.entries]) def __str__(self): return self.pprint() diff --git a/jrnl/install.py b/jrnl/install.py index 118b7c32..d8b3255f 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -9,7 +9,7 @@ from . import upgrade from . import __version__ from .Journal import PlainJournal from .EncryptedJournal import EncryptedJournal -from .util import UserAbort +from .util import UserAbort, verify_config import yaml import logging import sys @@ -47,7 +47,7 @@ def module_exists(module_name): default_config = { "version": __version__, - "journals": {DEFAULT_JOURNAL_KEY: JOURNAL_FILE_PATH}, + "journals": {"default": JOURNAL_FILE_PATH}, "editor": os.getenv("VISUAL") or os.getenv("EDITOR") or "", "encrypt": False, "template": False, @@ -58,6 +58,7 @@ default_config = { "highlight": True, "linewrap": 79, "indent_character": "|", + "colors": {"date": "none", "title": "none", "body": "none", "tags": "none",}, } @@ -114,6 +115,7 @@ def load_or_install_jrnl(): sys.exit(1) upgrade_config(config) + verify_config(config) return config else: diff --git a/jrnl/util.py b/jrnl/util.py index add70ff3..9bc6e4b6 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -4,24 +4,24 @@ import sys import os import getpass as gp import yaml +import colorama if "win32" in sys.platform: - import colorama - colorama.init() import re import tempfile import subprocess import unicodedata import shlex +from string import punctuation, whitespace import logging from typing import Optional, Callable log = logging.getLogger(__name__) -WARNING_COLOR = "\033[33m" -ERROR_COLOR = "\033[31m" -RESET_COLOR = "\033[0m" +WARNING_COLOR = colorama.Fore.YELLOW +ERROR_COLOR = colorama.Fore.RED +RESET_COLOR = colorama.Fore.RESET # Based on Segtok by Florian Leitner # https://github.com/fnl/segtok @@ -140,6 +140,27 @@ def scope_config(config, journal_name): return config +def verify_config(config): + """ + Ensures the keys set for colors are valid colorama.Fore attributes, or "None" + :return: True if all keys are set correctly, False otherwise + """ + all_valid_colors = True + for key, color in config["colors"].items(): + upper_color = color.upper() + if upper_color == "NONE": + continue + if not getattr(colorama.Fore, upper_color, None): + print( + "[{2}ERROR{3}: {0} set to invalid color: {1}]".format( + key, color, ERROR_COLOR, RESET_COLOR + ), + file=sys.stderr, + ) + all_valid_colors = False + return all_valid_colors + + def get_text_from_editor(config, template=""): filehandle, tmpfile = tempfile.mkstemp(prefix="jrnl", text=True, suffix=".txt") os.close(filehandle) @@ -165,9 +186,79 @@ def get_text_from_editor(config, template=""): return raw -def colorize(string): - """Returns the string wrapped in cyan ANSI escape""" - return f"\033[36m{string}\033[39m" +def colorize(string, color, bold=False): + """Returns the string colored with colorama.Fore.color. If the color set by + the user is "NONE" or the color doesn't exist in the colorama.Fore attributes, + it returns the string without any modification.""" + color_escape = getattr(colorama.Fore, color.upper(), None) + if not color_escape: + return string + elif not bold: + return color_escape + string + colorama.Fore.RESET + else: + return colorama.Style.BRIGHT + color_escape + string + colorama.Style.RESET_ALL + + +def highlight_tags_with_background_color(entry, text, color, is_title=False): + """ + Takes a string and colorizes the tags in it based upon the config value for + color.tags, while colorizing the rest of the text based on `color`. + :param entry: Entry object, for access to journal config + :param text: Text to be colorized + :param color: Color for non-tag text, passed to colorize() + :param is_title: Boolean flag indicating if the text is a title or not + :return: Colorized str + """ + + def colorized_text_generator(fragments): + """Efficiently generate colorized tags / text from text fragments. + Taken from @shobrook. Thanks, buddy :) + :param fragments: List of strings representing parts of entry (tag or word). + :rtype: List of tuples + :returns [(colorized_str, original_str)]""" + for part in fragments: + if part and part[0] not in config["tagsymbols"]: + yield (colorize(part, color, bold=is_title), part) + elif part: + yield (colorize(part, config["colors"]["tags"], bold=True), part) + + config = entry.journal.config + if config["highlight"]: # highlight tags + if entry.journal.search_tags: + text_fragments = [] + for tag in entry.search_tags: + text_fragments.extend( + re.split( + re.compile(re.escape(tag), re.IGNORECASE), + text, + flags=re.UNICODE, + ) + ) + else: + text_fragments = re.split(entry.tag_regex(config["tagsymbols"]), text) + + # Colorizing tags inside of other blocks of text + final_text = "" + previous_piece = "" + for colorized_piece, piece in colorized_text_generator(text_fragments): + # If this piece is entirely punctuation or whitespace or the start + # of a line or the previous piece was a tag or this piece is a tag, + # then add it to the final text without a leading space. + if ( + all(char in punctuation + whitespace for char in piece) + or previous_piece.endswith("\n") + or (previous_piece and previous_piece[0] in config["tagsymbols"]) + or piece[0] in config["tagsymbols"] + ): + final_text += colorized_piece + else: + # Otherwise add a leading space and then append the piece. + final_text += " " + colorized_piece + + previous_piece = piece + return final_text.lstrip() + else: + return text def slugify(string): diff --git a/poetry.lock b/poetry.lock index 16be3709..1a24ab9b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,14 @@ +[[package]] +category = "main" +description = "textwrap, but savvy to ANSI colors and styles" +name = "ansiwrap" +optional = false +python-versions = "*" +version = "0.8.4" + +[package.dependencies] +textwrap3 = ">=0.9.2" + [[package]] category = "dev" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." @@ -22,6 +33,12 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "19.3.0" +[package.extras] +azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] +dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] +docs = ["sphinx", "zope.interface"] +tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] + [[package]] category = "dev" description = "behave is behaviour-driven development, Python style" @@ -35,6 +52,10 @@ parse = ">=1.8.2" parse-type = ">=0.4.2" six = ">=1.11" +[package.extras] +develop = ["coverage", "pytest (>=3.0)", "pytest-cov", "tox", "invoke (>=0.21.0)", "path.py (>=8.1.2)", "pycmd", "pathlib", "modernize (>=0.5)", "pylint"] +docs = ["sphinx (>=1.6)", "sphinx-bootstrap-theme (>=0.6)"] + [[package]] category = "dev" description = "The uncompromising code formatter." @@ -52,13 +73,16 @@ regex = "*" toml = ">=0.9.4" typed-ast = ">=1.4.0" +[package.extras] +d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] + [[package]] category = "main" description = "Foreign Function Interface for Python calling C code." name = "cffi" optional = false python-versions = "*" -version = "1.13.2" +version = "1.14.0" [package.dependencies] pycparser = "*" @@ -74,7 +98,6 @@ version = "7.0" [[package]] category = "main" description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\"" name = "colorama" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -92,6 +115,13 @@ version = "2.8" cffi = ">=1.8,<1.11.3 || >1.11.3" six = ">=1.4.1" +[package.extras] +docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0)", "sphinx-rtd-theme"] +docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] +idna = ["idna (>=2.1)"] +pep8test = ["flake8", "flake8-import-order", "pep8-naming"] +test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] + [[package]] category = "dev" description = "Discover and load entry points from installed packages." @@ -114,6 +144,14 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.5.0,<2.6.0" pyflakes = ">=2.1.0,<2.2.0" +[[package]] +category = "dev" +description = "Clean single-source support for Python 3 and 2" +name = "future" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "0.18.2" + [[package]] category = "main" description = "Read metadata from Python packages" @@ -121,11 +159,15 @@ marker = "python_version < \"3.8\"" name = "importlib-metadata" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "1.3.0" +version = "1.5.0" [package.dependencies] zipp = ">=0.5" +[package.extras] +docs = ["sphinx", "rst.linker"] +testing = ["packaging", "importlib-resources"] + [[package]] category = "main" description = "Low-level, pure Python DBus protocol wrapper." @@ -135,17 +177,23 @@ optional = false python-versions = ">=3.5" version = "0.4.2" +[package.extras] +dev = ["testpath"] + [[package]] category = "dev" description = "A very fast and expressive template engine." name = "jinja2" optional = false -python-versions = "*" -version = "2.10.3" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.11.1" [package.dependencies] MarkupSafe = ">=0.23" +[package.extras] +i18n = ["Babel (>=0.8)"] + [[package]] category = "main" description = "Store and access your passwords safely." @@ -162,6 +210,10 @@ secretstorage = "*" python = "<3.8" version = "*" +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-black-multipy", "pytest-cov"] + [[package]] category = "dev" description = "Python LiveReload is an awesome tool for web developers" @@ -174,17 +226,39 @@ version = "2.6.1" six = "*" tornado = "*" +[[package]] +category = "dev" +description = "A Python implementation of Lunr.js" +name = "lunr" +optional = false +python-versions = "*" +version = "0.5.6" + +[package.dependencies] +future = ">=0.16.0" +six = ">=1.11.0" + +[package.dependencies.nltk] +optional = true +version = ">=3.2.5" + +[package.extras] +languages = ["nltk (>=3.2.5)"] + [[package]] category = "dev" description = "Python implementation of Markdown." name = "markdown" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "3.1.1" +python-versions = ">=3.5" +version = "3.2.1" [package.dependencies] setuptools = ">=36" +[package.extras] +testing = ["coverage", "pyyaml"] + [[package]] category = "dev" description = "Safely add untrusted strings to HTML/XML markup." @@ -206,25 +280,39 @@ category = "dev" description = "Project documentation with Markdown." name = "mkdocs" optional = false -python-versions = ">=2.7.9,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" -version = "1.0.4" +python-versions = ">=3.5" +version = "1.1" [package.dependencies] -Jinja2 = ">=2.7.1" -Markdown = ">=2.3.1" +Jinja2 = ">=2.10.1" +Markdown = ">=3.2.1" PyYAML = ">=3.10" click = ">=3.3" livereload = ">=2.5.1" tornado = ">=5.0" +[package.dependencies.lunr] +extras = ["languages"] +version = "0.5.6" + [[package]] -category = "main" -description = "More routines for operating on iterables, beyond itertools" -marker = "python_version < \"3.8\"" -name = "more-itertools" +category = "dev" +description = "Natural Language Toolkit" +name = "nltk" optional = false -python-versions = ">=3.5" -version = "8.0.2" +python-versions = "*" +version = "3.4.5" + +[package.dependencies] +six = "*" + +[package.extras] +all = ["pyparsing", "scikit-learn", "python-crfsuite", "matplotlib", "scipy", "gensim", "requests", "twython", "numpy"] +corenlp = ["requests"] +machine_learning = ["gensim", "numpy", "python-crfsuite", "scikit-learn", "scipy"] +plot = ["matplotlib"] +tgrep = ["pyparsing"] +twitter = ["twython"] [[package]] category = "dev" @@ -246,6 +334,10 @@ version = "0.5.2" parse = ">=1.8.4" six = ">=1.11" +[package.extras] +develop = ["coverage (>=4.4)", "pytest (>=3.2)", "pytest-cov", "tox (>=2.8)"] +docs = ["sphinx (>=1.2)"] + [[package]] category = "main" description = "Parse human-readable date/time text." @@ -262,6 +354,12 @@ optional = false python-versions = "*" version = "1.7.2" +[package.extras] +argon2 = ["argon2-cffi (>=18.2.0)"] +bcrypt = ["bcrypt (>=3.1.0)"] +build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.0)"] +totp = ["cryptography"] + [[package]] category = "dev" description = "Utility library for gitignore style pattern matching of file paths." @@ -335,8 +433,8 @@ category = "main" description = "YAML parser and emitter for Python" name = "pyyaml" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "5.2" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "5.3" [[package]] category = "dev" @@ -344,7 +442,7 @@ description = "Alternative regular expression module, to replace re." name = "regex" optional = false python-versions = "*" -version = "2019.12.20" +version = "2020.2.20" [[package]] category = "main" @@ -353,19 +451,27 @@ marker = "sys_platform == \"linux\"" name = "secretstorage" optional = false python-versions = ">=3.5" -version = "3.1.1" +version = "3.1.2" [package.dependencies] cryptography = "*" -jeepney = "*" +jeepney = ">=0.4.2" [[package]] category = "main" description = "Python 2 and 3 compatibility utilities" name = "six" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "1.13.0" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.14.0" + +[[package]] +category = "main" +description = "textwrap from Python 3.6 backport (plus a few tweaks)" +name = "textwrap3" +optional = false +python-versions = "*" +version = "0.9.2" [[package]] category = "dev" @@ -389,7 +495,7 @@ description = "a fork of Python 2 and 3 ast modules with type comment support" name = "typed-ast" optional = false python-versions = "*" -version = "1.4.0" +version = "1.4.1" [[package]] category = "main" @@ -408,56 +514,318 @@ description = "Backport of pathlib-compatible object wrapper for zip files" marker = "python_version < \"3.8\"" name = "zipp" optional = false -python-versions = ">=2.7" -version = "0.6.0" +python-versions = ">=3.6" +version = "3.0.0" -[package.dependencies] -more-itertools = "*" +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] +testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "98e23837423d5d8621f14cbe592d209ef98e1926b7a3f94e0f88bb6be908aae8" +content-hash = "98c4d0d25bb309075ca86c1ed3ed0d46b0fd1dad66510f8fe95b0ad350065df5" python-versions = ">=3.6.0, <3.9.0" -[metadata.hashes] -appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"] -asteval = ["5d64e18b8a72c2c7ae8f9b70d1f80b68bbcaa98c1c0d7047c35489d03209bc86"] -attrs = ["08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"] -behave = ["b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86", "ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c"] -black = ["1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b", "c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"] -cffi = ["0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42", "0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04", "135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5", "19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54", "2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba", "291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57", "2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396", "2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12", "32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97", "3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43", "415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db", "42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3", "4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b", "4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579", "599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346", "5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159", "5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652", "62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e", "6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a", "6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506", "71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f", "74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d", "7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c", "7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20", "7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858", "8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc", "aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a", "ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3", "d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e", "d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410", "dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25", "e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b", "fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d"] -click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] -colorama = ["7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"] -cryptography = ["02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c", "1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595", "369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad", "3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651", "44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2", "4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff", "58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d", "6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42", "7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d", "73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e", "7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912", "90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793", "971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13", "a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7", "b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0", "b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879", "d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f", "de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9", "df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2", "ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf", "fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8"] -entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"] -flake8 = ["45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", "49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"] -importlib-metadata = ["073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45", "d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f"] -jeepney = ["0ba6d8c597e9bef1ebd18aaec595f942a264e25c1a48f164d46120eacaa2e9bb", "6f45dce1125cf6c58a1c88123d3831f36a789f9204fbad3172eac15f8ccd08d0"] -jinja2 = ["74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", "9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"] -keyring = ["9b80469783d3f6106bce1d389c6b8b20c8d4d739943b1b8cd0ddc2a45d065f9d", "ee3d35b7f1ac3cb69e9a1e4323534649d3ab2fea402738a77e4250c152970fed"] -livereload = ["78d55f2c268a8823ba499305dcac64e28ddeb9a92571e12d543cd304faf5817b", "89254f78d7529d7ea0a3417d224c34287ebfe266b05e67e51facaf82c27f0f66"] -markdown = ["2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a", "56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"] -markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"] -mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"] -mkdocs = ["17d34329aad75d5de604b9ed4e31df3a4d235afefdc46ce7b1964fddb2e1e939", "8cc8b38325456b9e942c981a209eaeb1e9f3f77b493ad755bfef889b9c8d356a"] -more-itertools = ["b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d", "c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564"] -parse = ["95a4f4469e37c57b5e924629ac99926f28bee7da59515dc5b8078c4c3e779249"] -parse-type = ["089a471b06327103865dfec2dd844230c3c658a4a1b5b4c8b6c16c8f77577f9e", "7f690b18d35048c15438d6d0571f9045cffbec5907e0b1ccf006f889e3a38c0b"] -parsedatetime = ["3b835fc54e472c17ef447be37458b400e3fefdf14bb1ffdedb5d2c853acf4ba1", "d2e9ddb1e463de871d32088a3f3cea3dc8282b1b2800e081bd0ef86900451667"] -passlib = ["68c35c98a7968850e17f1b6892720764cc7eed0ef2b7cb3116a89a28e43fe177", "8d666cef936198bc2ab47ee9b0410c94adf2ba798e5a84bf220be079ae7ab6a8"] -pathspec = ["163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424", "562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"] -pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"] -pycparser = ["a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"] -pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"] -python-dateutil = ["73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", "75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"] -pytz = ["1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", "b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"] -pywin32-ctypes = ["24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", "9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"] -pyxdg = ["1948ff8e2db02156c0cccd2529b43c0cff56ebaa71f5f021bbd755bc1419190e", "fe2928d3f532ed32b39c32a482b54136fe766d19936afc96c8f00645f9da1a06"] -pyyaml = ["0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc", "2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803", "35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc", "38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15", "483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075", "4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd", "7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31", "8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f", "c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c", "e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04", "ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4"] -regex = ["032fdcc03406e1a6485ec09b826eac78732943840c4b29e503b789716f051d8d", "0e6cf1e747f383f52a0964452658c04300a9a01e8a89c55ea22813931b580aa8", "106e25a841921d8259dcef2a42786caae35bc750fb996f830065b3dfaa67b77e", "1768cf42a78a11dae63152685e7a1d90af7a8d71d2d4f6d2387edea53a9e0588", "27d1bd20d334f50b7ef078eba0f0756a640fd25f5f1708d3b5bed18a5d6bced9", "29b20f66f2e044aafba86ecf10a84e611b4667643c42baa004247f5dfef4f90b", "4850c78b53acf664a6578bba0e9ebeaf2807bb476c14ec7e0f936f2015133cae", "57eacd38a5ec40ed7b19a968a9d01c0d977bda55664210be713e750dd7b33540", "724eb24b92fc5fdc1501a1b4df44a68b9c1dda171c8ef8736799e903fb100f63", "77ae8d926f38700432807ba293d768ba9e7652df0cbe76df2843b12f80f68885", "78b3712ec529b2a71731fbb10b907b54d9c53a17ca589b42a578bc1e9a2c82ea", "7bbbdbada3078dc360d4692a9b28479f569db7fc7f304b668787afc9feb38ec8", "8d9ef7f6c403e35e73b7fc3cde9f6decdc43b1cb2ff8d058c53b9084bfcb553e", "a83049eb717ae828ced9cf607845929efcb086a001fc8af93ff15c50012a5716", "adc35d38952e688535980ae2109cad3a109520033642e759f987cf47fe278aa1", "c29a77ad4463f71a506515d9ec3a899ed026b4b015bf43245c919ff36275444b", "cfd31b3300fefa5eecb2fe596c6dee1b91b3a05ece9d5cfd2631afebf6c6fadd", "d3ee0b035816e0520fac928de31b6572106f0d75597f6fa3206969a02baba06f", "d508875793efdf6bab3d47850df8f40d4040ae9928d9d80864c1768d6aeaf8e3", "ef0b828a7e22e58e06a1cceddba7b4665c6af8afeb22a0d8083001330572c147", "faad39fdbe2c2ccda9846cd21581063086330efafa47d87afea4073a08128656"] -secretstorage = ["20c797ae48a4419f66f8d28fc221623f11fc45b6828f96bdb1ad9990acb59f92", "7a119fb52a88e398dbb22a4b3eb39b779bfbace7e4153b7bc6e5954d86282a8a"] -six = ["1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", "30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"] -toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] -tornado = ["349884248c36801afa19e342a77cc4458caca694b0eda633f5878e458a44cb2c", "398e0d35e086ba38a0427c3b37f4337327231942e731edaa6e9fd1865bbd6f60", "4e73ef678b1a859f0cb29e1d895526a20ea64b5ffd510a2307b5998c7df24281", "559bce3d31484b665259f50cd94c5c28b961b09315ccd838f284687245f416e5", "abbe53a39734ef4aba061fca54e30c6b4639d3e1f59653f0da37a0003de148c7", "c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9", "c9399267c926a4e7c418baa5cbe91c7d1cf362d505a1ef898fde44a07c9dd8a5"] -typed-ast = ["1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", "18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", "4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", "838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", "95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", "ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"] -tzlocal = ["4ebeb848845ac898da6519b9b31879cf13b6626f7184c496037b818e238f2c4e"] -zipp = ["3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", "f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"] +[metadata.files] +ansiwrap = [ + {file = "ansiwrap-0.8.4-py2.py3-none-any.whl", hash = "sha256:7b053567c88e1ad9eed030d3ac41b722125e4c1271c8a99ade797faff1f49fb1"}, + {file = "ansiwrap-0.8.4.zip", hash = "sha256:ca0c740734cde59bf919f8ff2c386f74f9a369818cdc60efe94893d01ea8d9b7"}, +] +appdirs = [ + {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, + {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, +] +asteval = [ + {file = "asteval-0.9.18.tar.gz", hash = "sha256:5d64e18b8a72c2c7ae8f9b70d1f80b68bbcaa98c1c0d7047c35489d03209bc86"}, +] +attrs = [ + {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, + {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, +] +behave = [ + {file = "behave-1.2.6-py2.py3-none-any.whl", hash = "sha256:ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c"}, + {file = "behave-1.2.6.tar.gz", hash = "sha256:b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86"}, +] +black = [ + {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, + {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, +] +cffi = [ + {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, + {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, + {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, + {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, + {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, + {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, + {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, + {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, + {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, + {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, + {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, + {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, + {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, + {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, + {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, + {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, + {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, + {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, + {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, + {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, + {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, + {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, + {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, + {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, + {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, + {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, + {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, + {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, +] +click = [ + {file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"}, + {file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"}, +] +colorama = [ + {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, + {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, +] +cryptography = [ + {file = "cryptography-2.8-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8"}, + {file = "cryptography-2.8-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2"}, + {file = "cryptography-2.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad"}, + {file = "cryptography-2.8-cp27-cp27m-win32.whl", hash = "sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2"}, + {file = "cryptography-2.8-cp27-cp27m-win_amd64.whl", hash = "sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912"}, + {file = "cryptography-2.8-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d"}, + {file = "cryptography-2.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42"}, + {file = "cryptography-2.8-cp34-abi3-macosx_10_6_intel.whl", hash = "sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879"}, + {file = "cryptography-2.8-cp34-abi3-manylinux1_x86_64.whl", hash = "sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d"}, + {file = "cryptography-2.8-cp34-abi3-manylinux2010_x86_64.whl", hash = "sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9"}, + {file = "cryptography-2.8-cp34-cp34m-win32.whl", hash = "sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c"}, + {file = "cryptography-2.8-cp34-cp34m-win_amd64.whl", hash = "sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0"}, + {file = "cryptography-2.8-cp35-cp35m-win32.whl", hash = "sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf"}, + {file = "cryptography-2.8-cp35-cp35m-win_amd64.whl", hash = "sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793"}, + {file = "cryptography-2.8-cp36-cp36m-win32.whl", hash = "sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595"}, + {file = "cryptography-2.8-cp36-cp36m-win_amd64.whl", hash = "sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7"}, + {file = "cryptography-2.8-cp37-cp37m-win32.whl", hash = "sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff"}, + {file = "cryptography-2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f"}, + {file = "cryptography-2.8-cp38-cp38-win32.whl", hash = "sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e"}, + {file = "cryptography-2.8-cp38-cp38-win_amd64.whl", hash = "sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13"}, + {file = "cryptography-2.8.tar.gz", hash = "sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651"}, +] +entrypoints = [ + {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"}, + {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, +] +flake8 = [ + {file = "flake8-3.7.9-py2.py3-none-any.whl", hash = "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"}, + {file = "flake8-3.7.9.tar.gz", hash = "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb"}, +] +future = [ + {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, +] +importlib-metadata = [ + {file = "importlib_metadata-1.5.0-py2.py3-none-any.whl", hash = "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b"}, + {file = "importlib_metadata-1.5.0.tar.gz", hash = "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302"}, +] +jeepney = [ + {file = "jeepney-0.4.2-py3-none-any.whl", hash = "sha256:6f45dce1125cf6c58a1c88123d3831f36a789f9204fbad3172eac15f8ccd08d0"}, + {file = "jeepney-0.4.2.tar.gz", hash = "sha256:0ba6d8c597e9bef1ebd18aaec595f942a264e25c1a48f164d46120eacaa2e9bb"}, +] +jinja2 = [ + {file = "Jinja2-2.11.1-py2.py3-none-any.whl", hash = "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49"}, + {file = "Jinja2-2.11.1.tar.gz", hash = "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250"}, +] +keyring = [ + {file = "keyring-19.3.0-py2.py3-none-any.whl", hash = "sha256:9b80469783d3f6106bce1d389c6b8b20c8d4d739943b1b8cd0ddc2a45d065f9d"}, + {file = "keyring-19.3.0.tar.gz", hash = "sha256:ee3d35b7f1ac3cb69e9a1e4323534649d3ab2fea402738a77e4250c152970fed"}, +] +livereload = [ + {file = "livereload-2.6.1-py2.py3-none-any.whl", hash = "sha256:78d55f2c268a8823ba499305dcac64e28ddeb9a92571e12d543cd304faf5817b"}, + {file = "livereload-2.6.1.tar.gz", hash = "sha256:89254f78d7529d7ea0a3417d224c34287ebfe266b05e67e51facaf82c27f0f66"}, +] +lunr = [ + {file = "lunr-0.5.6-py2.py3-none-any.whl", hash = "sha256:1208622930c915a07e6f8e8640474357826bad48534c0f57969b6fca9bffc88e"}, + {file = "lunr-0.5.6.tar.gz", hash = "sha256:7be69d7186f65784a4f2adf81e5c58efd6a9921aa95966babcb1f2f2ada75c20"}, +] +markdown = [ + {file = "Markdown-3.2.1-py2.py3-none-any.whl", hash = "sha256:e4795399163109457d4c5af2183fbe6b60326c17cfdf25ce6e7474c6624f725d"}, + {file = "Markdown-3.2.1.tar.gz", hash = "sha256:90fee683eeabe1a92e149f7ba74e5ccdc81cd397bd6c516d93a8da0ef90b6902"}, +] +markupsafe = [ + {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, + {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, + {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, + {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, + {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, + {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, + {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, + {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +mkdocs = [ + {file = "mkdocs-1.1-py2.py3-none-any.whl", hash = "sha256:1e385a70aea8a9dedb731aea4fd5f3704b2074801c4f96f06b2920999babda8a"}, + {file = "mkdocs-1.1.tar.gz", hash = "sha256:9243291392f59e20b655e4e46210233453faf97787c2cf72176510e868143174"}, +] +nltk = [ + {file = "nltk-3.4.5.win32.exe", hash = "sha256:a08bdb4b8a1c13de16743068d9eb61c8c71c2e5d642e8e08205c528035843f82"}, + {file = "nltk-3.4.5.zip", hash = "sha256:bed45551259aa2101381bbdd5df37d44ca2669c5c3dad72439fa459b29137d94"}, +] +parse = [ + {file = "parse-1.14.0.tar.gz", hash = "sha256:95a4f4469e37c57b5e924629ac99926f28bee7da59515dc5b8078c4c3e779249"}, +] +parse-type = [ + {file = "parse_type-0.5.2-py2.py3-none-any.whl", hash = "sha256:089a471b06327103865dfec2dd844230c3c658a4a1b5b4c8b6c16c8f77577f9e"}, + {file = "parse_type-0.5.2.tar.gz", hash = "sha256:7f690b18d35048c15438d6d0571f9045cffbec5907e0b1ccf006f889e3a38c0b"}, +] +parsedatetime = [ + {file = "parsedatetime-2.5-py2-none-any.whl", hash = "sha256:3b835fc54e472c17ef447be37458b400e3fefdf14bb1ffdedb5d2c853acf4ba1"}, + {file = "parsedatetime-2.5.tar.gz", hash = "sha256:d2e9ddb1e463de871d32088a3f3cea3dc8282b1b2800e081bd0ef86900451667"}, +] +passlib = [ + {file = "passlib-1.7.2-py2.py3-none-any.whl", hash = "sha256:68c35c98a7968850e17f1b6892720764cc7eed0ef2b7cb3116a89a28e43fe177"}, + {file = "passlib-1.7.2.tar.gz", hash = "sha256:8d666cef936198bc2ab47ee9b0410c94adf2ba798e5a84bf220be079ae7ab6a8"}, +] +pathspec = [ + {file = "pathspec-0.7.0-py2.py3-none-any.whl", hash = "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424"}, + {file = "pathspec-0.7.0.tar.gz", hash = "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"}, +] +pycodestyle = [ + {file = "pycodestyle-2.5.0-py2.py3-none-any.whl", hash = "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56"}, + {file = "pycodestyle-2.5.0.tar.gz", hash = "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"}, +] +pycparser = [ + {file = "pycparser-2.19.tar.gz", hash = "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"}, +] +pyflakes = [ + {file = "pyflakes-2.1.1-py2.py3-none-any.whl", hash = "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0"}, + {file = "pyflakes-2.1.1.tar.gz", hash = "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, + {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, +] +pytz = [ + {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, + {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, +] +pywin32-ctypes = [ + {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, + {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, +] +pyxdg = [ + {file = "pyxdg-0.26-py2.py3-none-any.whl", hash = "sha256:1948ff8e2db02156c0cccd2529b43c0cff56ebaa71f5f021bbd755bc1419190e"}, + {file = "pyxdg-0.26.tar.gz", hash = "sha256:fe2928d3f532ed32b39c32a482b54136fe766d19936afc96c8f00645f9da1a06"}, +] +pyyaml = [ + {file = "PyYAML-5.3-cp27-cp27m-win32.whl", hash = "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d"}, + {file = "PyYAML-5.3-cp27-cp27m-win_amd64.whl", hash = "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6"}, + {file = "PyYAML-5.3-cp35-cp35m-win32.whl", hash = "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e"}, + {file = "PyYAML-5.3-cp35-cp35m-win_amd64.whl", hash = "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689"}, + {file = "PyYAML-5.3-cp36-cp36m-win32.whl", hash = "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994"}, + {file = "PyYAML-5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e"}, + {file = "PyYAML-5.3-cp37-cp37m-win32.whl", hash = "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5"}, + {file = "PyYAML-5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf"}, + {file = "PyYAML-5.3-cp38-cp38-win32.whl", hash = "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811"}, + {file = "PyYAML-5.3-cp38-cp38-win_amd64.whl", hash = "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20"}, + {file = "PyYAML-5.3.tar.gz", hash = "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"}, +] +regex = [ + {file = "regex-2020.2.20-cp27-cp27m-win32.whl", hash = "sha256:99272d6b6a68c7ae4391908fc15f6b8c9a6c345a46b632d7fdb7ef6c883a2bbb"}, + {file = "regex-2020.2.20-cp27-cp27m-win_amd64.whl", hash = "sha256:974535648f31c2b712a6b2595969f8ab370834080e00ab24e5dbb9d19b8bfb74"}, + {file = "regex-2020.2.20-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5de40649d4f88a15c9489ed37f88f053c15400257eeb18425ac7ed0a4e119400"}, + {file = "regex-2020.2.20-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:82469a0c1330a4beb3d42568f82dffa32226ced006e0b063719468dcd40ffdf0"}, + {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d58a4fa7910102500722defbde6e2816b0372a4fcc85c7e239323767c74f5cbc"}, + {file = "regex-2020.2.20-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f1ac2dc65105a53c1c2d72b1d3e98c2464a133b4067a51a3d2477b28449709a0"}, + {file = "regex-2020.2.20-cp36-cp36m-win32.whl", hash = "sha256:8c2b7fa4d72781577ac45ab658da44c7518e6d96e2a50d04ecb0fd8f28b21d69"}, + {file = "regex-2020.2.20-cp36-cp36m-win_amd64.whl", hash = "sha256:269f0c5ff23639316b29f31df199f401e4cb87529eafff0c76828071635d417b"}, + {file = "regex-2020.2.20-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bed7986547ce54d230fd8721aba6fd19459cdc6d315497b98686d0416efaff4e"}, + {file = "regex-2020.2.20-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:046e83a8b160aff37e7034139a336b660b01dbfe58706f9d73f5cdc6b3460242"}, + {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:b33ebcd0222c1d77e61dbcd04a9fd139359bded86803063d3d2d197b796c63ce"}, + {file = "regex-2020.2.20-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bba52d72e16a554d1894a0cc74041da50eea99a8483e591a9edf1025a66843ab"}, + {file = "regex-2020.2.20-cp37-cp37m-win32.whl", hash = "sha256:01b2d70cbaed11f72e57c1cfbaca71b02e3b98f739ce33f5f26f71859ad90431"}, + {file = "regex-2020.2.20-cp37-cp37m-win_amd64.whl", hash = "sha256:113309e819634f499d0006f6200700c8209a2a8bf6bd1bdc863a4d9d6776a5d1"}, + {file = "regex-2020.2.20-cp38-cp38-manylinux1_i686.whl", hash = "sha256:25f4ce26b68425b80a233ce7b6218743c71cf7297dbe02feab1d711a2bf90045"}, + {file = "regex-2020.2.20-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9b64a4cc825ec4df262050c17e18f60252cdd94742b4ba1286bcfe481f1c0f26"}, + {file = "regex-2020.2.20-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:9ff16d994309b26a1cdf666a6309c1ef51ad4f72f99d3392bcd7b7139577a1f2"}, + {file = "regex-2020.2.20-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c7f58a0e0e13fb44623b65b01052dae8e820ed9b8b654bb6296bc9c41f571b70"}, + {file = "regex-2020.2.20-cp38-cp38-win32.whl", hash = "sha256:200539b5124bc4721247a823a47d116a7a23e62cc6695744e3eb5454a8888e6d"}, + {file = "regex-2020.2.20-cp38-cp38-win_amd64.whl", hash = "sha256:7f78f963e62a61e294adb6ff5db901b629ef78cb2a1cfce3cf4eeba80c1c67aa"}, + {file = "regex-2020.2.20.tar.gz", hash = "sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5"}, +] +secretstorage = [ + {file = "SecretStorage-3.1.2-py3-none-any.whl", hash = "sha256:b5ec909dde94d4ae2fa26af7c089036997030f0cf0a5cb372b4cccabd81c143b"}, + {file = "SecretStorage-3.1.2.tar.gz", hash = "sha256:15da8a989b65498e29be338b3b279965f1b8f09b9668bd8010da183024c8bff6"}, +] +six = [ + {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, + {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, +] +textwrap3 = [ + {file = "textwrap3-0.9.2-py2.py3-none-any.whl", hash = "sha256:bf5f4c40faf2a9ff00a9e0791fed5da7415481054cef45bb4a3cfb1f69044ae0"}, + {file = "textwrap3-0.9.2.zip", hash = "sha256:5008eeebdb236f6303dcd68f18b856d355f6197511d952ba74bc75e40e0c3414"}, +] +toml = [ + {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, + {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, + {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, +] +tornado = [ + {file = "tornado-6.0.3-cp35-cp35m-win32.whl", hash = "sha256:c9399267c926a4e7c418baa5cbe91c7d1cf362d505a1ef898fde44a07c9dd8a5"}, + {file = "tornado-6.0.3-cp35-cp35m-win_amd64.whl", hash = "sha256:398e0d35e086ba38a0427c3b37f4337327231942e731edaa6e9fd1865bbd6f60"}, + {file = "tornado-6.0.3-cp36-cp36m-win32.whl", hash = "sha256:4e73ef678b1a859f0cb29e1d895526a20ea64b5ffd510a2307b5998c7df24281"}, + {file = "tornado-6.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:349884248c36801afa19e342a77cc4458caca694b0eda633f5878e458a44cb2c"}, + {file = "tornado-6.0.3-cp37-cp37m-win32.whl", hash = "sha256:559bce3d31484b665259f50cd94c5c28b961b09315ccd838f284687245f416e5"}, + {file = "tornado-6.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:abbe53a39734ef4aba061fca54e30c6b4639d3e1f59653f0da37a0003de148c7"}, + {file = "tornado-6.0.3.tar.gz", hash = "sha256:c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9"}, +] +typed-ast = [ + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, + {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, + {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, + {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, + {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, + {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, + {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, + {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, +] +tzlocal = [ + {file = "tzlocal-1.5.1.tar.gz", hash = "sha256:4ebeb848845ac898da6519b9b31879cf13b6626f7184c496037b818e238f2c4e"}, +] +zipp = [ + {file = "zipp-3.0.0-py3-none-any.whl", hash = "sha256:12248a63bbdf7548f89cb4c7cda4681e537031eda29c02ea29674bc6854460c2"}, + {file = "zipp-3.0.0.tar.gz", hash = "sha256:7c0f8e91abc0dc07a5068f315c52cb30c66bfbc581e5b50704c8a2f6ebae794a"}, +] diff --git a/pyproject.toml b/pyproject.toml index c8c9984e..d1d48168 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,9 +25,10 @@ keyring = "^19.0" pytz = "^2019.1" tzlocal = "^1.5" asteval = "^0.9.14" -colorama = {version = "^0.4.1",platform = "win32"} +colorama = "^0.4.1" python-dateutil = "^2.8" pyyaml = "^5.1" +ansiwrap = "^0.8.4" [tool.poetry.dev-dependencies] behave = "^1.2" From 4d768ae433cff95f19a84b6e89e144a9c0885454 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 29 Feb 2020 14:44:42 -0800 Subject: [PATCH 35/40] fix up tests and related issues --- features/contains.feature | 2 +- features/core.feature | 4 +-- features/steps/core.py | 58 +++------------------------------------ jrnl/util.py | 8 ++---- 4 files changed, 9 insertions(+), 63 deletions(-) diff --git a/features/contains.feature b/features/contains.feature index 5813be20..eac3a464 100644 --- a/features/contains.feature +++ b/features/contains.feature @@ -14,7 +14,7 @@ Feature: Contains Given we use the config "tags.yaml" When we run "jrnl @idea -contains software" Then we should get no error - and the output should contain "software" + And the output should contain "software" Scenario: Searching for a string within AND tag results Given we use the config "tags.yaml" diff --git a/features/core.feature b/features/core.feature index 3c49324c..df214494 100644 --- a/features/core.feature +++ b/features/core.feature @@ -36,12 +36,12 @@ Feature: Basic reading and writing to a journal @skip_win Scenario: Writing an empty entry from the editor Given we use the config "editor.yaml" - When we open the editor and enter "" + When we open the editor and enter nothing Then we should see the message "[Nothing saved to file]" Scenario: Writing an empty entry from the command line Given we use the config "basic.yaml" - When we run "jrnl" and enter "" + When we run "jrnl" and enter nothing Then the output should be """ diff --git a/features/steps/core.py b/features/steps/core.py index e37c0a08..5ff65aad 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -82,18 +82,15 @@ def set_config(context, config_file): cf.write("version: {}".format(__version__)) -@when('we open the editor and enter ""') @when('we open the editor and enter "{text}"') +@when("we open the editor and enter nothing") def open_editor_and_enter(context, text=""): - text = text or context.text + text = text or context.text or "" def _mock_editor_function(command): tmpfile = command[-1] with open(tmpfile, "w+") as f: - if text is not None: - f.write(text) - else: - f.write("") + f.write(text) return tmpfile @@ -119,7 +116,7 @@ def _mock_input(inputs): @when('we run "{command}" and enter') -@when('we run "{command}" and enter ""') +@when('we run "{command}" and enter nothing') @when('we run "{command}" and enter "{inputs}"') def run_with_input(context, command, inputs=""): # create an iterator through all inputs. These inputs will be fed one by one @@ -186,53 +183,6 @@ def no_error(context): assert context.exit_status == 0, context.exit_status -@then("the output should be parsable as json") -def check_output_json(context): - out = context.stdout_capture.getvalue() - assert json.loads(out), out - - -@then('"{field}" in the json output should have {number:d} elements') -@then('"{field}" in the json output should have 1 element') -def check_output_field(context, field, number=1): - out = context.stdout_capture.getvalue() - out_json = json.loads(out) - assert field in out_json, [field, out_json] - assert len(out_json[field]) == number, len(out_json[field]) - - -@then('"{field}" in the json output should not contain "{key}"') -def check_output_field_not_key(context, field, key): - out = context.stdout_capture.getvalue() - out_json = json.loads(out) - assert field in out_json - assert key not in out_json[field] - - -@then('"{field}" in the json output should contain "{key}"') -def check_output_field_key(context, field, key): - out = context.stdout_capture.getvalue() - out_json = json.loads(out) - assert field in out_json - assert key in out_json[field] - - -@then('the json output should contain {path} = "{value}"') -def check_json_output_path(context, path, value): - """ E.g. - the json output should contain entries.0.title = "hello" - """ - out = context.stdout_capture.getvalue() - struct = json.loads(out) - - for node in path.split("."): - try: - struct = struct[int(node)] - except ValueError: - struct = struct[node] - assert struct == value, struct - - def process_ANSI_escapes(text): """Escapes and 'unescapes' a string with ANSI escapes so that behave stdout comparisons work properly. This will render colors, and works with unicode diff --git a/jrnl/util.py b/jrnl/util.py index 9bc6e4b6..d6b796c3 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -226,13 +226,9 @@ def highlight_tags_with_background_color(entry, text, color, is_title=False): if config["highlight"]: # highlight tags if entry.journal.search_tags: text_fragments = [] - for tag in entry.search_tags: + for tag in entry.journal.search_tags: text_fragments.extend( - re.split( - re.compile(re.escape(tag), re.IGNORECASE), - text, - flags=re.UNICODE, - ) + re.split(re.compile(re.escape(tag), re.IGNORECASE), text) ) else: text_fragments = re.split(entry.tag_regex(config["tagsymbols"]), text) From c769b9ef7f543468933d0e326cf453c090dfdc9c Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 29 Feb 2020 15:05:31 -0800 Subject: [PATCH 36/40] upgrade poetry if already installed --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a602f51b..7933134a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ before_install: - date install: - - pip install poetry + - pip install --upgrade poetry - poetry install - poetry run python --version From 4358cb3ac7a1e2602d6cfc365137471f17ef0cf4 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 29 Feb 2020 23:36:24 +0000 Subject: [PATCH 37/40] Updating changelog [ci skip] --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5211462..1b94984d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ **Implemented enhancements:** +- Add support to save journals to multiple files in a directory [\#843](https://github.com/jrnl-org/jrnl/pull/843) ([wren](https://github.com/wren)) - Update YAML exporter to handle Dayone format [\#773](https://github.com/jrnl-org/jrnl/pull/773) ([MinchinWeb](https://github.com/MinchinWeb)) +- Issue \#170 - Add folder based journal [\#458](https://github.com/jrnl-org/jrnl/pull/458) ([notbalanced](https://github.com/notbalanced)) **Fixed bugs:** From fcac1f98af0c6553a4ef48d55bdc67cffcd86512 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 7 Mar 2020 13:01:39 -0800 Subject: [PATCH 38/40] Get rid of test function that was causing windows problems --- features/steps/core.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/features/steps/core.py b/features/steps/core.py index 5ff65aad..26398fc4 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -183,22 +183,11 @@ def no_error(context): assert context.exit_status == 0, context.exit_status -def process_ANSI_escapes(text): - """Escapes and 'unescapes' a string with ANSI escapes so that behave stdout - comparisons work properly. This will render colors, and works with unicode - characters. https://stackoverflow.com/a/57192592 - :param str text: The text to be escaped and unescaped - :return: Colorized / escaped text - :rtype: str - """ - return decode(encode(text, "latin-1", "backslashreplace"), "unicode-escape") - - @then("the output should be") @then('the output should be "{text}"') def check_output(context, text=None): text = (text or context.text).strip().splitlines() - out = process_ANSI_escapes(context.stdout_capture.getvalue().strip()).splitlines() + out = context.stdout_capture.getvalue().strip().splitlines() assert len(text) == len(out), "Output has {} lines (expected: {})".format( len(out), len(text) ) @@ -211,7 +200,7 @@ def check_output(context, text=None): @then('the output should contain "{text}" in the local time') def check_output_time_inline(context, text): - out = process_ANSI_escapes(context.stdout_capture.getvalue()) + out = context.stdout_capture.getvalue() local_tz = tzlocal.get_localzone() date, flag = CALENDAR.parse(text) output_date = time.strftime("%Y-%m-%d %H:%M", date) @@ -223,7 +212,7 @@ def check_output_time_inline(context, text): @then('the output should contain "{text}" or "{text2}"') def check_output_inline(context, text=None, text2=None): text = text or context.text - out = process_ANSI_escapes(context.stdout_capture.getvalue()) + out = context.stdout_capture.getvalue() assert text in out or text2 in out, text or text2 From 6c61372beefa2a0ade985c85bd846c16f377ce20 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 7 Mar 2020 14:11:05 -0800 Subject: [PATCH 39/40] Update stalebot emoji to :pushpin: instead of :star: The pushpin better reflects the functionality (stops the issue from staling). --- .github/stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/stale.yml b/.github/stale.yml index 323b3928..4df56d7b 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -4,8 +4,8 @@ daysUntilStale: 60 daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - - ':star:' - - security + - ':pushpin:' + - critical # Label to use when marking an issue as stale staleLabel: stale # Comment to post when marking an issue as stale. Set to `false` to disable From 8277bc729a31701a7c74dd34d5eff4023dd4e4b0 Mon Sep 17 00:00:00 2001 From: Jrnl Bot Date: Sat, 7 Mar 2020 22:38:24 +0000 Subject: [PATCH 40/40] Updating changelog [ci skip] --- CHANGELOG.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b94984d..f52a2428 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,7 @@ **Implemented enhancements:** -- Add support to save journals to multiple files in a directory [\#843](https://github.com/jrnl-org/jrnl/pull/843) ([wren](https://github.com/wren)) - Update YAML exporter to handle Dayone format [\#773](https://github.com/jrnl-org/jrnl/pull/773) ([MinchinWeb](https://github.com/MinchinWeb)) -- Issue \#170 - Add folder based journal [\#458](https://github.com/jrnl-org/jrnl/pull/458) ([notbalanced](https://github.com/notbalanced)) **Fixed bugs:** @@ -17,7 +15,7 @@ **Build:** -- Stop multipe changelog generators from crashing into each other [\#845](https://github.com/jrnl-org/jrnl/pull/845) ([wren](https://github.com/wren)) +- Stop multiple changelog generators from crashing into each other [\#845](https://github.com/jrnl-org/jrnl/pull/845) ([wren](https://github.com/wren)) - Don't re-run tests on deployment [\#839](https://github.com/jrnl-org/jrnl/pull/839) ([wren](https://github.com/wren)) - Put back build lines in Poetry config [\#838](https://github.com/jrnl-org/jrnl/pull/838) ([wren](https://github.com/wren)) - Restore emoji test [\#837](https://github.com/jrnl-org/jrnl/pull/837) ([micahellison](https://github.com/micahellison)) @@ -28,7 +26,7 @@ **Updated documentation:** - Docs: Fix broken links in recipes.md [\#854](https://github.com/jrnl-org/jrnl/pull/854) ([lrvl](https://github.com/lrvl)) -- docs: Fix fish history instructions. [\#846](https://github.com/jrnl-org/jrnl/pull/846) ([aureooms](https://github.com/aureooms)) +- Fix fish history instructions. [\#846](https://github.com/jrnl-org/jrnl/pull/846) ([aureooms](https://github.com/aureooms)) - Update site description [\#841](https://github.com/jrnl-org/jrnl/pull/841) ([wren](https://github.com/wren)) - Get rid of dumb sex joke [\#840](https://github.com/jrnl-org/jrnl/pull/840) ([wren](https://github.com/wren)) - Updating/clarifying template explanation [\#829](https://github.com/jrnl-org/jrnl/pull/829) ([heymajor](https://github.com/heymajor))