From c1082b70106c82990c10c643e2c38d7062bb0c2f Mon Sep 17 00:00:00 2001 From: Aaron Lichtman Date: Tue, 29 Oct 2019 03:17:57 +0100 Subject: [PATCH 1/7] Add --delete for interactive removal of entries - Add inquirer dependency for fancy prompting - Fix some minor style issues Fix #434 --- jrnl/Journal.py | 22 +++++++++++++++++++++- jrnl/cli.py | 38 +++++++++++++++++++++++++++++++++++++- jrnl/time.py | 7 +++++++ jrnl/util.py | 9 +++++++++ poetry.lock | 46 +++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 6 files changed, 120 insertions(+), 3 deletions(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 72fe94b18..e8e8b9ef2 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -44,6 +44,7 @@ def __init__(self, name='default', **kwargs): # Set up date parser self.search_tags = None # Store tags we're highlighting self.name = name + self.entries = [] def __len__(self): """Returns the number of entries""" @@ -219,12 +220,31 @@ def filter(self, tags=[], start_date=None, end_date=None, starred=False, strict= self.entries = result + def remove_entries_by_title_and_time(self, entries_for_removal: list): + """ + Removes entries from the journal based on their titles and time. Times will + be datetime objects, however, they'll have the seconds stripped from them. + :param entries_for_removal: List of tuples in format of [(date, title), ... ] + """ + # Removal algorithm optimized using the fact that all entries for removal are in sorted + # order and the entries in the journal are also in sorted order. + cleaned_entries = [] + entries_for_removal_idx = 0 + for entry in self.entries: + if entries_for_removal_idx < len(entries_for_removal) and \ + time.from_same_minute(entry.date, entries_for_removal[entries_for_removal_idx][0]) and \ + entry.title == entries_for_removal[entries_for_removal_idx][1]: + entries_for_removal_idx += 1 + else: + cleaned_entries.append(entry) + + self.entries = cleaned_entries + def new_entry(self, raw, date=None, sort=True): """Constructs a new entry from some raw text input. If a date is given, it will parse and use this, otherwise scan for a date in the input first.""" raw = raw.replace('\\n ', '\n').replace('\\n', '\n') - starred = False # Split raw text into title and body sep = re.search("\n|[\?!.]+ +\n?", raw) first_line = raw[:sep.end()].strip() if sep else raw diff --git a/jrnl/cli.py b/jrnl/cli.py index 65a535169..b8062b1be 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -13,11 +13,15 @@ from . import util from . import install from . import plugins +from . import time from .util import ERROR_COLOR, RESET_COLOR, UserAbort import jrnl import argparse import sys import logging +import inquirer +from pprint import pprint +from inquirer.themes import GreenPassion log = logging.getLogger(__name__) logging.getLogger("keyring.backend").setLevel(logging.ERROR) @@ -51,6 +55,7 @@ def parse_args(args=None): exporting.add_argument('--encrypt', metavar='FILENAME', dest='encrypt', help='Encrypts your existing journal with a new password', nargs='?', default=False, const=None) exporting.add_argument('--decrypt', metavar='FILENAME', dest='decrypt', help='Decrypts your journal and stores it in plain text', nargs='?', default=False, const=None) exporting.add_argument('--edit', dest='edit', help='Opens your editor to edit the selected entries.', action="store_true") + exporting.add_argument('--delete', dest='delete', action="store_true", help='Opens an interactive interface for deleting entries.') return parser.parse_args(args) @@ -64,7 +69,7 @@ def guess_mode(args, config): compose = False export = False import_ = True - elif args.decrypt is not False or args.encrypt is not False or args.export is not False or any((args.short, args.tags, args.edit)): + elif args.decrypt is not False or args.encrypt is not False or args.export is not False or any((args.short, args.tags, args.edit, args.delete)): compose = False export = True elif any((args.start_date, args.end_date, args.on_date, args.limit, args.strict, args.starred)): @@ -294,3 +299,34 @@ def run(manual_args=None): journal.entries += other_entries journal.sort() journal.write() + + elif args.delete: + # Display all journal entry titles in a list and let user select one or more of them + questions = [ + inquirer.Checkbox('entries_to_delete', + message="Which entries would you like to delete? (Use arrow keys to select, Enter to confirm)", + choices=journal.pprint(short=True).split("\n"), + ), + ] + raw_entries_to_delete = inquirer.prompt(questions, theme=GreenPassion())["entries_to_delete"] + + # Confirm deletion + util.pretty_print_entries(raw_entries_to_delete) + + confirmation = "Are you sure you'd like to delete " + if len(raw_entries_to_delete) == 0: + return + elif len(raw_entries_to_delete) == 1: + confirmation += "this entry?" + else: + confirmation += "these entries?" + + if not util.yesno(confirmation): + return + + # Actually delete them + # The best we can do seems to be matching time stamps and title + entries_to_delete = [(time.parse(" ".join(entry.split()[:2])), " ".join(entry.split()[2:])) + for entry in raw_entries_to_delete] + journal.remove_entries_by_title_and_time(entries_to_delete) + journal.write() diff --git a/jrnl/time.py b/jrnl/time.py index 9ff125aa9..c9526f09d 100644 --- a/jrnl/time.py +++ b/jrnl/time.py @@ -63,3 +63,10 @@ def parse(date_str, inclusive=False, default_hour=None, default_minute=None): if dt.days < -28 and not year_present: date = date.replace(date.year - 1) return date + + +def from_same_minute(time1: datetime, time2: datetime) -> bool: + """Compares two datetimes, disregarding the seconds. Returns true if the + two datetimes are on the same day, during the same hour and minute.""" + delta = abs(time1 - time2) + return delta.days == 0 and delta.seconds <= 60 and time1.min == time2.min diff --git a/jrnl/util.py b/jrnl/util.py index bc36ba9b4..bb0d50ac0 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -217,3 +217,12 @@ def split_title(text): if not punkt: return text, "" return text[:punkt.end()].strip(), text[punkt.end():].strip() + + +def pretty_print_entries(entries): + """Similar to Entry.pprint(short=True), except this function takes a + list of strings representing Entry objects instead of the actual objects + :param entries: List of strings in format of "DATE TITLE" """ + for entry in entries: + print("->", entry) + print("") diff --git a/poetry.lock b/poetry.lock index e772b2d26..d74c50e9c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -61,6 +61,17 @@ attrs = ">=17.4.0" click = ">=6.5" toml = ">=0.9.4" +[[package]] +category = "main" +description = "A thin, practical wrapper around terminal coloring, styling, and positioning" +name = "blessings" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.7" + +[package.dependencies] +six = "*" + [[package]] category = "main" description = "Foreign Function Interface for Python calling C code." @@ -132,6 +143,19 @@ optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" version = "0.17.1" +[[package]] +category = "main" +description = "Collection of common interactive command line user interfaces, based on Inquirer.js" +name = "inquirer" +optional = false +python-versions = "*" +version = "2.6.3" + +[package.dependencies] +blessings = "1.7" +python-editor = "1.0.4" +readchar = "2.0.1" + [[package]] category = "main" description = "Low-level, pure Python DBus protocol wrapper." @@ -302,6 +326,14 @@ version = "2.8.0" [package.dependencies] six = ">=1.5" +[[package]] +category = "main" +description = "Programmatically open an editor, capture the result." +name = "python-editor" +optional = false +python-versions = "*" +version = "1.0.4" + [[package]] category = "main" description = "World timezone definitions, modern and historical" @@ -335,6 +367,14 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "5.1.2" +[[package]] +category = "main" +description = "Utilities to read single characters and key-strokes" +name = "readchar" +optional = false +python-versions = "*" +version = "2.0.1" + [[package]] category = "main" description = "Python bindings to FreeDesktop.org Secret Service API" @@ -384,7 +424,7 @@ version = "1.5.1" pytz = "*" [metadata] -content-hash = "9896cf59c7552b6ad95219ee5555c7445a3fab39c2e4f4c6f3d991a36635e44b" +content-hash = "9b06cddcd0d267708105dedf49dcebbebdae59608feadd002bc2808b3ededd1b" python-versions = "^3.7" [metadata.hashes] @@ -394,6 +434,7 @@ asteval = ["7c81fee6707a7a28e8beae891b858535a7e61f9ce275a0a4cf5f428fbc934cb8"] attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"] behave = ["b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86", "ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c"] black = ["817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", "e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"] +blessings = ["98e5854d805f50a5b58ac2333411b0482516a8210f23f43308baeb58d77c157d", "b1fdd7e7a675295630f9ae71527a8ebc10bfefa236b3d6aa4932ee4462c17ba3", "caad5211e7ba5afe04367cdd4cfc68fa886e2e08f6f35e76b7387d2109ccea6e"] cffi = ["041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774", "046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d", "066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90", "066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b", "2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63", "300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45", "34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25", "46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3", "4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b", "4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647", "4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016", "50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4", "55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb", "5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753", "59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7", "73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9", "a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f", "a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8", "a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f", "a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc", "ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42", "b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3", "d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909", "d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45", "dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d", "e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512", "e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff", "ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201"] click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"] @@ -401,6 +442,7 @@ cryptography = ["24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8 entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"] flake8 = ["19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", "8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"] future = ["67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8"] +inquirer = ["5f6e5dcbc881f43554b6fdfea245e417c6ed05c930cdb6e09b5df7357c288e06", "c77fd8c3c053e1b4aa7ac1e0300cbdec5fe887e144d7bdb40f9f97f96a0eb909"] jeepney = ["6089412a5de162c04747f0220f6b2223b8ba660acd041e52a76426ca550e3c70", "f6f8b1428403b4afad04b6b82f9ab9fc426c253d7504c9031c41712a2c01dc74"] jinja2 = ["065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"] keyring = ["1b74595f7439e4581a11d4f9a12790ac34addce64ca389c86272ff465f5e0b90", "afbfe7bc9bdba69d25c551b0c738adde533d87e0b51ad6bbe332cbea19ad8476"] @@ -418,10 +460,12 @@ pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56 pycparser = ["a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"] pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"] python-dateutil = ["7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", "c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"] +python-editor = ["1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d", "51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b", "5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8", "c3da2053dbab6b29c94e43c486ff67206eafbe7eb52dbec7390b5e2fb05aac77", "ea87e17f6ec459e780e4221f295411462e0d0810858e055fc514684350a2f522"] pytz = ["303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", "d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141"] pywin32-ctypes = ["24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", "9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"] pyxdg = ["1948ff8e2db02156c0cccd2529b43c0cff56ebaa71f5f021bbd755bc1419190e", "fe2928d3f532ed32b39c32a482b54136fe766d19936afc96c8f00645f9da1a06"] pyyaml = ["0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", "01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", "5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", "5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", "7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", "7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", "87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", "9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", "a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", "b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", "b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", "bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", "f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"] +readchar = ["3ac34aab28563bc895f73233d5c08b28f951ca190d5850b8d4bec973132a8dca", "ed00b7a49bb12f345319d9fa393f289f03670310ada2beb55e8c3f017c648f1e"] secretstorage = ["20c797ae48a4419f66f8d28fc221623f11fc45b6828f96bdb1ad9990acb59f92", "7a119fb52a88e398dbb22a4b3eb39b779bfbace7e4153b7bc6e5954d86282a8a"] six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] diff --git a/pyproject.toml b/pyproject.toml index 1b84acde0..46df90eec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ asteval = "^0.9.14" colorama = {version = "^0.4.1",platform = "win32"} python-dateutil = "^2.8" pyyaml = "^5.1" +inquirer = "^2.6" [tool.poetry.dev-dependencies] behave = "^1.2" From 6d366035e8f9506ef0508d112ae24116bb236bb0 Mon Sep 17 00:00:00 2001 From: Aaron Lichtman Date: Tue, 29 Oct 2019 03:58:58 +0100 Subject: [PATCH 2/7] Use PyInquirer instead of inquirer for Windows compatibility --- jrnl/cli.py | 16 ++++----- jrnl/util.py | 18 ++++++++++ poetry.lock | 90 ++++++++++++++++++++++++++++---------------------- pyproject.toml | 2 +- 4 files changed, 75 insertions(+), 51 deletions(-) diff --git a/jrnl/cli.py b/jrnl/cli.py index b8062b1be..fc802ff5b 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -19,9 +19,6 @@ import argparse import sys import logging -import inquirer -from pprint import pprint -from inquirer.themes import GreenPassion log = logging.getLogger(__name__) logging.getLogger("keyring.backend").setLevel(logging.ERROR) @@ -302,13 +299,12 @@ def run(manual_args=None): elif args.delete: # Display all journal entry titles in a list and let user select one or more of them - questions = [ - inquirer.Checkbox('entries_to_delete', - message="Which entries would you like to delete? (Use arrow keys to select, Enter to confirm)", - choices=journal.pprint(short=True).split("\n"), - ), - ] - raw_entries_to_delete = inquirer.prompt(questions, theme=GreenPassion())["entries_to_delete"] + entries = journal.pprint(short=True).split("\n") + raw_entries_to_delete = util.prompt_checklist( + name="entries_to_delete", + choices=entries, + message="Which entries would you like to delete? (Use arrow keys to select, Enter to confirm)" + ) # Confirm deletion util.pretty_print_entries(raw_entries_to_delete) diff --git a/jrnl/util.py b/jrnl/util.py index bb0d50ac0..c3f6f8463 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -18,6 +18,7 @@ import unicodedata import shlex import logging +from PyInquirer import prompt as selection_prompt log = logging.getLogger(__name__) @@ -226,3 +227,20 @@ def pretty_print_entries(entries): for entry in entries: print("->", entry) print("") + + +def prompt_checklist(name, choices, message): + formatted_choices = [] + for choice in choices: + formatted_choices.append({"name": choice}) + + questions = [ + { + 'type' : 'checkbox', + 'qmark' : '?', + 'message' : message, + 'name' : name, + 'choices' : formatted_choices + } + ] + return selection_prompt(questions)[name] diff --git a/poetry.lock b/poetry.lock index d74c50e9c..424402df0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -61,17 +61,6 @@ attrs = ">=17.4.0" click = ">=6.5" toml = ">=0.9.4" -[[package]] -category = "main" -description = "A thin, practical wrapper around terminal coloring, styling, and positioning" -name = "blessings" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.7" - -[package.dependencies] -six = "*" - [[package]] category = "main" description = "Foreign Function Interface for Python calling C code." @@ -143,19 +132,6 @@ optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" version = "0.17.1" -[[package]] -category = "main" -description = "Collection of common interactive command line user interfaces, based on Inquirer.js" -name = "inquirer" -optional = false -python-versions = "*" -version = "2.6.3" - -[package.dependencies] -blessings = "1.7" -python-editor = "1.0.4" -readchar = "2.0.1" - [[package]] category = "main" description = "Low-level, pure Python DBus protocol wrapper." @@ -291,6 +267,18 @@ optional = false python-versions = "*" version = "1.7.1" +[[package]] +category = "main" +description = "Library for building powerful interactive command lines in Python" +name = "prompt-toolkit" +optional = false +python-versions = "*" +version = "1.0.14" + +[package.dependencies] +six = ">=1.9.0" +wcwidth = "*" + [[package]] category = "dev" description = "Python style guide checker" @@ -315,6 +303,27 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "2.1.1" +[[package]] +category = "main" +description = "Pygments is a syntax highlighting package written in Python." +name = "pygments" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.4.2" + +[[package]] +category = "main" +description = "A Python module for collection of common interactive command line user interfaces, based on Inquirer.js" +name = "pyinquirer" +optional = false +python-versions = "*" +version = "1.0.3" + +[package.dependencies] +Pygments = ">=2.2.0" +prompt_toolkit = "1.0.14" +regex = ">=2016.11.21" + [[package]] category = "main" description = "Extensions to the standard Python datetime module" @@ -326,14 +335,6 @@ version = "2.8.0" [package.dependencies] six = ">=1.5" -[[package]] -category = "main" -description = "Programmatically open an editor, capture the result." -name = "python-editor" -optional = false -python-versions = "*" -version = "1.0.4" - [[package]] category = "main" description = "World timezone definitions, modern and historical" @@ -369,11 +370,11 @@ version = "5.1.2" [[package]] category = "main" -description = "Utilities to read single characters and key-strokes" -name = "readchar" +description = "Alternative regular expression module, to replace re." +name = "regex" optional = false python-versions = "*" -version = "2.0.1" +version = "2019.08.19" [[package]] category = "main" @@ -423,8 +424,16 @@ version = "1.5.1" [package.dependencies] pytz = "*" +[[package]] +category = "main" +description = "Measures number of Terminal column cells of wide-character codes" +name = "wcwidth" +optional = false +python-versions = "*" +version = "0.1.7" + [metadata] -content-hash = "9b06cddcd0d267708105dedf49dcebbebdae59608feadd002bc2808b3ededd1b" +content-hash = "ca469ebf9978a0b3b79552e0d83ceff0ff4739ee2666d0605692251bc4fdc82f" python-versions = "^3.7" [metadata.hashes] @@ -434,7 +443,6 @@ asteval = ["7c81fee6707a7a28e8beae891b858535a7e61f9ce275a0a4cf5f428fbc934cb8"] attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"] behave = ["b9662327aa53294c1351b0a9c369093ccec1d21026f050c3bd9b3e5cccf81a86", "ebda1a6c9e5bfe95c5f9f0a2794e01c7098b3dde86c10a95d8621c5907ff6f1c"] black = ["817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", "e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"] -blessings = ["98e5854d805f50a5b58ac2333411b0482516a8210f23f43308baeb58d77c157d", "b1fdd7e7a675295630f9ae71527a8ebc10bfefa236b3d6aa4932ee4462c17ba3", "caad5211e7ba5afe04367cdd4cfc68fa886e2e08f6f35e76b7387d2109ccea6e"] cffi = ["041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774", "046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d", "066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90", "066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b", "2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63", "300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45", "34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25", "46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3", "4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b", "4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647", "4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016", "50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4", "55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb", "5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753", "59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7", "73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9", "a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f", "a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8", "a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f", "a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc", "ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42", "b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3", "d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909", "d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45", "dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d", "e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512", "e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff", "ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201"] click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"] colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"] @@ -442,7 +450,6 @@ cryptography = ["24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8 entrypoints = ["589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", "c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"] flake8 = ["19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", "8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696"] future = ["67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8"] -inquirer = ["5f6e5dcbc881f43554b6fdfea245e417c6ed05c930cdb6e09b5df7357c288e06", "c77fd8c3c053e1b4aa7ac1e0300cbdec5fe887e144d7bdb40f9f97f96a0eb909"] jeepney = ["6089412a5de162c04747f0220f6b2223b8ba660acd041e52a76426ca550e3c70", "f6f8b1428403b4afad04b6b82f9ab9fc426c253d7504c9031c41712a2c01dc74"] jinja2 = ["065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"] keyring = ["1b74595f7439e4581a11d4f9a12790ac34addce64ca389c86272ff465f5e0b90", "afbfe7bc9bdba69d25c551b0c738adde533d87e0b51ad6bbe332cbea19ad8476"] @@ -456,18 +463,21 @@ parse = ["1b68657434d371e5156048ca4a0c5aea5afc6ca59a2fea4dd1a575354f617142"] parse-type = ["6e906a66f340252e4c324914a60d417d33a4bea01292ea9bbf68b4fc123be8c9", "f596bdc75d3dd93036fbfe3d04127da9f6df0c26c36e01e76da85adef4336b3c"] parsedatetime = ["3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b", "9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094"] passlib = ["3d948f64138c25633613f303bcc471126eae67c04d5e3f6b7b8ce6242f8653e0", "43526aea08fa32c6b6dbbbe9963c4c767285b78147b7437597f992812f69d280"] +prompt-toolkit = ["7281b5199235adaef6980942840c43753e4ab20dfe41338da634fb41c194f9d8", "82c7f8e07d7a0411ff5367a5a8ff520f0112b9179f3e599ee8ad2ad9b943d911", "cc66413b1b4b17021675d9f2d15d57e640b06ddfd99bb724c73484126d22622f"] pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"] pycparser = ["a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"] pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"] +pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"] +pyinquirer = ["c9a92d68d7727fbd886a7908c08fd9e9773e5dc211bf5cbf836ba90d366dee51"] python-dateutil = ["7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", "c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"] -python-editor = ["1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d", "51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b", "5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8", "c3da2053dbab6b29c94e43c486ff67206eafbe7eb52dbec7390b5e2fb05aac77", "ea87e17f6ec459e780e4221f295411462e0d0810858e055fc514684350a2f522"] pytz = ["303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", "d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141"] pywin32-ctypes = ["24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", "9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"] pyxdg = ["1948ff8e2db02156c0cccd2529b43c0cff56ebaa71f5f021bbd755bc1419190e", "fe2928d3f532ed32b39c32a482b54136fe766d19936afc96c8f00645f9da1a06"] pyyaml = ["0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", "01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", "5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", "5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", "7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", "7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", "87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", "9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", "a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", "b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", "b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", "bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", "f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"] -readchar = ["3ac34aab28563bc895f73233d5c08b28f951ca190d5850b8d4bec973132a8dca", "ed00b7a49bb12f345319d9fa393f289f03670310ada2beb55e8c3f017c648f1e"] +regex = ["1e9f9bc44ca195baf0040b1938e6801d2f3409661c15fe57f8164c678cfc663f", "2079f83ac094b436a8078988e38af44286c67eb5a259becfc563e8a81cb34a11", "43dbcc90e1bd42d905abc8dac75a64768d4b621c9e01adfcd8a57df1af65793e", "587b62d48ca359d2d4f02d486f1f0aa9a20fbaf23a9d4198c4bed72ab2f6c849", "835ccdcdc612821edf132c20aef3eaaecfb884c9454fdc480d5887562594ac61", "93f6c9da57e704e128d90736430c5c59dd733327882b371b0cae8833106c2a21", "a46f27d267665016acb3ec8c6046ec5eae8cf80befe85ba47f43c6f5ec636dcd", "c5c8999b3a341b21ac2c6ec704cfcccbc50f1fedd61b6a8ee915ca7fd4b0a557", "d4d1829cf97632673aa49f378b0a2c3925acd795148c5ace8ef854217abbee89", "d96479257e8e4d1d7800adb26bf9c5ca5bab1648a1eddcac84d107b73dc68327", "f20f4912daf443220436759858f96fefbfc6c6ba9e67835fd6e4e9b73582791a", "f2b37b5b2c2a9d56d9e88efef200ec09c36c7f323f9d58d0b985a90923df386d", "fe765b809a1f7ce642c2edeee351e7ebd84391640031ba4b60af8d91a9045890"] secretstorage = ["20c797ae48a4419f66f8d28fc221623f11fc45b6828f96bdb1ad9990acb59f92", "7a119fb52a88e398dbb22a4b3eb39b779bfbace7e4153b7bc6e5954d86282a8a"] six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] tornado = ["349884248c36801afa19e342a77cc4458caca694b0eda633f5878e458a44cb2c", "398e0d35e086ba38a0427c3b37f4337327231942e731edaa6e9fd1865bbd6f60", "4e73ef678b1a859f0cb29e1d895526a20ea64b5ffd510a2307b5998c7df24281", "559bce3d31484b665259f50cd94c5c28b961b09315ccd838f284687245f416e5", "abbe53a39734ef4aba061fca54e30c6b4639d3e1f59653f0da37a0003de148c7", "c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9", "c9399267c926a4e7c418baa5cbe91c7d1cf362d505a1ef898fde44a07c9dd8a5"] tzlocal = ["4ebeb848845ac898da6519b9b31879cf13b6626f7184c496037b818e238f2c4e"] +wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"] diff --git a/pyproject.toml b/pyproject.toml index 46df90eec..a497a3bf8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ asteval = "^0.9.14" colorama = {version = "^0.4.1",platform = "win32"} python-dateutil = "^2.8" pyyaml = "^5.1" -inquirer = "^2.6" +PyInquirer = "^1.0" [tool.poetry.dev-dependencies] behave = "^1.2" From f057689459cc699816f59c6c03c8e2e023c1f767 Mon Sep 17 00:00:00 2001 From: Aaron Lichtman Date: Tue, 29 Oct 2019 04:16:48 +0100 Subject: [PATCH 3/7] Add WIP (broken) test --- features/core.feature | 13 +++++++++++++ features/data/configs/deletion.yaml | 12 ++++++++++++ features/data/journals/deletion.journal | 5 +++++ features/environment.py | 3 ++- 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 features/data/configs/deletion.yaml create mode 100644 features/data/journals/deletion.journal diff --git a/features/core.feature b/features/core.feature index ab86da423..477d1bacb 100644 --- a/features/core.feature +++ b/features/core.feature @@ -58,3 +58,16 @@ 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." + # The input for this test is y + Scenario: --delete flag allows deletion of single entry + Given we use the config "deletion.yaml" + When we run "jrnl --delete" + And we type " " + And we type + """ + + y + """ + When we run "jrnl -on 2019-10-29 -s" + Then the output should not contain "2019-10-29 11:11 First entry." + diff --git a/features/data/configs/deletion.yaml b/features/data/configs/deletion.yaml new file mode 100644 index 000000000..d4155260c --- /dev/null +++ b/features/data/configs/deletion.yaml @@ -0,0 +1,12 @@ +default_hour: 9 +default_minute: 0 +editor: "" +encrypt: false +highlight: true +journals: + default: features/journals/deletion.journal +linewrap: 80 +tagsymbols: "@" +template: false +timeformat: "%Y-%m-%d %H:%M" +indent_character: "|" diff --git a/features/data/journals/deletion.journal b/features/data/journals/deletion.journal new file mode 100644 index 000000000..c0fa689d1 --- /dev/null +++ b/features/data/journals/deletion.journal @@ -0,0 +1,5 @@ +[2019-10-29 11:11] First entry. + +[2019-10-29 11:11] Second entry. + +[2019-10-29 11:13] Third entry. \ No newline at end of file diff --git a/features/environment.py b/features/environment.py index 6f9ac5dfb..d7eeef55e 100644 --- a/features/environment.py +++ b/features/environment.py @@ -7,6 +7,7 @@ except ImportError: from cStringIO import StringIO + def before_scenario(context, scenario): """Before each scenario, backup all config and journal test data.""" context.messages = StringIO() @@ -19,7 +20,6 @@ def before_scenario(context, scenario): if os.path.exists(working_dir): shutil.rmtree(working_dir) - for folder in ("configs", "journals"): original = os.path.join("features", "data", folder) working_dir = os.path.join("features", folder) @@ -32,6 +32,7 @@ def before_scenario(context, scenario): else: shutil.copy2(source, working_dir) + def after_scenario(context, scenario): """After each scenario, restore all test data and remove working_dirs.""" context.messages.close() From 16a4821823875c368660e48d87c9f9e6395d83f9 Mon Sep 17 00:00:00 2001 From: Aaron Lichtman Date: Tue, 5 Nov 2019 20:12:41 +0100 Subject: [PATCH 4/7] Change deletion interface to be more basic --- jrnl/Journal.py | 25 +++++++--------------- jrnl/cli.py | 29 +------------------------ jrnl/time.py | 7 ------- jrnl/util.py | 27 ------------------------ poetry.lock | 56 +------------------------------------------------ pyproject.toml | 1 - 6 files changed, 10 insertions(+), 135 deletions(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index e8e8b9ef2..ca4a25e21 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -220,25 +220,16 @@ def filter(self, tags=[], start_date=None, end_date=None, starred=False, strict= self.entries = result - def remove_entries_by_title_and_time(self, entries_for_removal: list): - """ - Removes entries from the journal based on their titles and time. Times will - be datetime objects, however, they'll have the seconds stripped from them. - :param entries_for_removal: List of tuples in format of [(date, title), ... ] - """ - # Removal algorithm optimized using the fact that all entries for removal are in sorted - # order and the entries in the journal are also in sorted order. - cleaned_entries = [] - entries_for_removal_idx = 0 + def prompt_delete_entries(self): + """Prompts for deletion of entries in a journal.""" + print("Confirm each entry you want to delete [N/y]:") + to_delete: List[Entry] = [] for entry in self.entries: - if entries_for_removal_idx < len(entries_for_removal) and \ - time.from_same_minute(entry.date, entries_for_removal[entries_for_removal_idx][0]) and \ - entry.title == entries_for_removal[entries_for_removal_idx][1]: - entries_for_removal_idx += 1 - else: - cleaned_entries.append(entry) + response = input("jrnl: Delete entry '{}'? ".format(entry.pprint(short=True))) + if response == "y": + to_delete.append(entry) - self.entries = cleaned_entries + self.entries = [entry for entry in self.entries if entry not in to_delete] def new_entry(self, raw, date=None, sort=True): """Constructs a new entry from some raw text input. diff --git a/jrnl/cli.py b/jrnl/cli.py index fc802ff5b..d4ff3032e 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -13,7 +13,6 @@ from . import util from . import install from . import plugins -from . import time from .util import ERROR_COLOR, RESET_COLOR, UserAbort import jrnl import argparse @@ -298,31 +297,5 @@ def run(manual_args=None): journal.write() elif args.delete: - # Display all journal entry titles in a list and let user select one or more of them - entries = journal.pprint(short=True).split("\n") - raw_entries_to_delete = util.prompt_checklist( - name="entries_to_delete", - choices=entries, - message="Which entries would you like to delete? (Use arrow keys to select, Enter to confirm)" - ) - - # Confirm deletion - util.pretty_print_entries(raw_entries_to_delete) - - confirmation = "Are you sure you'd like to delete " - if len(raw_entries_to_delete) == 0: - return - elif len(raw_entries_to_delete) == 1: - confirmation += "this entry?" - else: - confirmation += "these entries?" - - if not util.yesno(confirmation): - return - - # Actually delete them - # The best we can do seems to be matching time stamps and title - entries_to_delete = [(time.parse(" ".join(entry.split()[:2])), " ".join(entry.split()[2:])) - for entry in raw_entries_to_delete] - journal.remove_entries_by_title_and_time(entries_to_delete) + journal.prompt_delete_entries() journal.write() diff --git a/jrnl/time.py b/jrnl/time.py index c9526f09d..9ff125aa9 100644 --- a/jrnl/time.py +++ b/jrnl/time.py @@ -63,10 +63,3 @@ def parse(date_str, inclusive=False, default_hour=None, default_minute=None): if dt.days < -28 and not year_present: date = date.replace(date.year - 1) return date - - -def from_same_minute(time1: datetime, time2: datetime) -> bool: - """Compares two datetimes, disregarding the seconds. Returns true if the - two datetimes are on the same day, during the same hour and minute.""" - delta = abs(time1 - time2) - return delta.days == 0 and delta.seconds <= 60 and time1.min == time2.min diff --git a/jrnl/util.py b/jrnl/util.py index c3f6f8463..bc36ba9b4 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -18,7 +18,6 @@ import unicodedata import shlex import logging -from PyInquirer import prompt as selection_prompt log = logging.getLogger(__name__) @@ -218,29 +217,3 @@ def split_title(text): if not punkt: return text, "" return text[:punkt.end()].strip(), text[punkt.end():].strip() - - -def pretty_print_entries(entries): - """Similar to Entry.pprint(short=True), except this function takes a - list of strings representing Entry objects instead of the actual objects - :param entries: List of strings in format of "DATE TITLE" """ - for entry in entries: - print("->", entry) - print("") - - -def prompt_checklist(name, choices, message): - formatted_choices = [] - for choice in choices: - formatted_choices.append({"name": choice}) - - questions = [ - { - 'type' : 'checkbox', - 'qmark' : '?', - 'message' : message, - 'name' : name, - 'choices' : formatted_choices - } - ] - return selection_prompt(questions)[name] diff --git a/poetry.lock b/poetry.lock index 424402df0..e772b2d26 100644 --- a/poetry.lock +++ b/poetry.lock @@ -267,18 +267,6 @@ optional = false python-versions = "*" version = "1.7.1" -[[package]] -category = "main" -description = "Library for building powerful interactive command lines in Python" -name = "prompt-toolkit" -optional = false -python-versions = "*" -version = "1.0.14" - -[package.dependencies] -six = ">=1.9.0" -wcwidth = "*" - [[package]] category = "dev" description = "Python style guide checker" @@ -303,27 +291,6 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "2.1.1" -[[package]] -category = "main" -description = "Pygments is a syntax highlighting package written in Python." -name = "pygments" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.4.2" - -[[package]] -category = "main" -description = "A Python module for collection of common interactive command line user interfaces, based on Inquirer.js" -name = "pyinquirer" -optional = false -python-versions = "*" -version = "1.0.3" - -[package.dependencies] -Pygments = ">=2.2.0" -prompt_toolkit = "1.0.14" -regex = ">=2016.11.21" - [[package]] category = "main" description = "Extensions to the standard Python datetime module" @@ -368,14 +335,6 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "5.1.2" -[[package]] -category = "main" -description = "Alternative regular expression module, to replace re." -name = "regex" -optional = false -python-versions = "*" -version = "2019.08.19" - [[package]] category = "main" description = "Python bindings to FreeDesktop.org Secret Service API" @@ -424,16 +383,8 @@ version = "1.5.1" [package.dependencies] pytz = "*" -[[package]] -category = "main" -description = "Measures number of Terminal column cells of wide-character codes" -name = "wcwidth" -optional = false -python-versions = "*" -version = "0.1.7" - [metadata] -content-hash = "ca469ebf9978a0b3b79552e0d83ceff0ff4739ee2666d0605692251bc4fdc82f" +content-hash = "9896cf59c7552b6ad95219ee5555c7445a3fab39c2e4f4c6f3d991a36635e44b" python-versions = "^3.7" [metadata.hashes] @@ -463,21 +414,16 @@ parse = ["1b68657434d371e5156048ca4a0c5aea5afc6ca59a2fea4dd1a575354f617142"] parse-type = ["6e906a66f340252e4c324914a60d417d33a4bea01292ea9bbf68b4fc123be8c9", "f596bdc75d3dd93036fbfe3d04127da9f6df0c26c36e01e76da85adef4336b3c"] parsedatetime = ["3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b", "9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094"] passlib = ["3d948f64138c25633613f303bcc471126eae67c04d5e3f6b7b8ce6242f8653e0", "43526aea08fa32c6b6dbbbe9963c4c767285b78147b7437597f992812f69d280"] -prompt-toolkit = ["7281b5199235adaef6980942840c43753e4ab20dfe41338da634fb41c194f9d8", "82c7f8e07d7a0411ff5367a5a8ff520f0112b9179f3e599ee8ad2ad9b943d911", "cc66413b1b4b17021675d9f2d15d57e640b06ddfd99bb724c73484126d22622f"] pycodestyle = ["95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", "e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"] pycparser = ["a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"] pyflakes = ["17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", "d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"] -pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"] -pyinquirer = ["c9a92d68d7727fbd886a7908c08fd9e9773e5dc211bf5cbf836ba90d366dee51"] python-dateutil = ["7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", "c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e"] pytz = ["303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", "d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141"] pywin32-ctypes = ["24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942", "9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"] pyxdg = ["1948ff8e2db02156c0cccd2529b43c0cff56ebaa71f5f021bbd755bc1419190e", "fe2928d3f532ed32b39c32a482b54136fe766d19936afc96c8f00645f9da1a06"] pyyaml = ["0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", "01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", "5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", "5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", "7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", "7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", "87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", "9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", "a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", "b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", "b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", "bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", "f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"] -regex = ["1e9f9bc44ca195baf0040b1938e6801d2f3409661c15fe57f8164c678cfc663f", "2079f83ac094b436a8078988e38af44286c67eb5a259becfc563e8a81cb34a11", "43dbcc90e1bd42d905abc8dac75a64768d4b621c9e01adfcd8a57df1af65793e", "587b62d48ca359d2d4f02d486f1f0aa9a20fbaf23a9d4198c4bed72ab2f6c849", "835ccdcdc612821edf132c20aef3eaaecfb884c9454fdc480d5887562594ac61", "93f6c9da57e704e128d90736430c5c59dd733327882b371b0cae8833106c2a21", "a46f27d267665016acb3ec8c6046ec5eae8cf80befe85ba47f43c6f5ec636dcd", "c5c8999b3a341b21ac2c6ec704cfcccbc50f1fedd61b6a8ee915ca7fd4b0a557", "d4d1829cf97632673aa49f378b0a2c3925acd795148c5ace8ef854217abbee89", "d96479257e8e4d1d7800adb26bf9c5ca5bab1648a1eddcac84d107b73dc68327", "f20f4912daf443220436759858f96fefbfc6c6ba9e67835fd6e4e9b73582791a", "f2b37b5b2c2a9d56d9e88efef200ec09c36c7f323f9d58d0b985a90923df386d", "fe765b809a1f7ce642c2edeee351e7ebd84391640031ba4b60af8d91a9045890"] secretstorage = ["20c797ae48a4419f66f8d28fc221623f11fc45b6828f96bdb1ad9990acb59f92", "7a119fb52a88e398dbb22a4b3eb39b779bfbace7e4153b7bc6e5954d86282a8a"] six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"] toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"] tornado = ["349884248c36801afa19e342a77cc4458caca694b0eda633f5878e458a44cb2c", "398e0d35e086ba38a0427c3b37f4337327231942e731edaa6e9fd1865bbd6f60", "4e73ef678b1a859f0cb29e1d895526a20ea64b5ffd510a2307b5998c7df24281", "559bce3d31484b665259f50cd94c5c28b961b09315ccd838f284687245f416e5", "abbe53a39734ef4aba061fca54e30c6b4639d3e1f59653f0da37a0003de148c7", "c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9", "c9399267c926a4e7c418baa5cbe91c7d1cf362d505a1ef898fde44a07c9dd8a5"] tzlocal = ["4ebeb848845ac898da6519b9b31879cf13b6626f7184c496037b818e238f2c4e"] -wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"] diff --git a/pyproject.toml b/pyproject.toml index a497a3bf8..1b84acde0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,6 @@ asteval = "^0.9.14" colorama = {version = "^0.4.1",platform = "win32"} python-dateutil = "^2.8" pyyaml = "^5.1" -PyInquirer = "^1.0" [tool.poetry.dev-dependencies] behave = "^1.2" From 2291859634c8757eadcb70e97d1e8178672814c0 Mon Sep 17 00:00:00 2001 From: Aaron Lichtman Date: Mon, 16 Dec 2019 04:38:00 +0100 Subject: [PATCH 5/7] Update environment.py --- features/environment.py | 1 - 1 file changed, 1 deletion(-) diff --git a/features/environment.py b/features/environment.py index 6f9757a2e..8ba781acf 100644 --- a/features/environment.py +++ b/features/environment.py @@ -10,7 +10,6 @@ def before_feature(context, feature): return - def before_scenario(context, scenario): """Before each scenario, backup all config and journal test data.""" # Clean up in case something went wrong From 9bbc38b7ced44f34f02b242b12a85c306177b62e Mon Sep 17 00:00:00 2001 From: dbxnr Date: Wed, 12 Feb 2020 05:15:47 +0000 Subject: [PATCH 6/7] fixup alichtman's implementation --- features/core.feature | 14 -------------- features/delete.feature | 20 ++++++++++++++++++++ jrnl/Journal.py | 29 +++++++++++++++++++---------- jrnl/cli.py | 7 ++++++- jrnl/util.py | 2 +- 5 files changed, 46 insertions(+), 26 deletions(-) create mode 100644 features/delete.feature diff --git a/features/core.feature b/features/core.feature index e9059f1a1..7cf7455ea 100644 --- a/features/core.feature +++ b/features/core.feature @@ -71,17 +71,3 @@ Feature: Basic reading and writing to a journal Given we use the config "basic.yaml" When we run "jrnl -on 2013-06-10 -s" Then the output should be "2013-06-10 15:40 Life is good." - - # The input for this test is y - Scenario: --delete flag allows deletion of single entry - Given we use the config "deletion.yaml" - When we run "jrnl --delete" - And we type " " - And we type - """ - - y - """ - When we run "jrnl -on 2019-10-29 -s" - Then the output should not contain "2019-10-29 11:11 First entry." - diff --git a/features/delete.feature b/features/delete.feature new file mode 100644 index 000000000..df6a10555 --- /dev/null +++ b/features/delete.feature @@ -0,0 +1,20 @@ +Feature: Delete entries from journal + + Scenario: --delete flag allows deletion of single entry + Given we use the config "deletion.yaml" + When we run "jrnl -n 1" + Then the output should contain + """ + 2019-10-29 11:13 Third entry. + """ + When we run "jrnl --delete" and enter + """ + N + N + Y + """ + When we run "jrnl -n 1" + Then the output should contain + """ + 2019-10-29 11:11 Second entry. + """ diff --git a/jrnl/Journal.py b/jrnl/Journal.py index a5a13b424..aec3e3082 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -1,13 +1,15 @@ #!/usr/bin/env python -from . import Entry -from . import util -from . import time +import logging import os import sys import re + from datetime import datetime -import logging +from itertools import filterfalse +from jrnl import Entry +from jrnl import util +from jrnl import time log = logging.getLogger(__name__) @@ -111,7 +113,8 @@ def _to_text(self): def _load(self, filename): raise NotImplementedError - def _store(self, filename, text): + @classmethod + def _store(filename, text): raise NotImplementedError def _parse(self, journal_txt): @@ -249,20 +252,26 @@ def filter( def prompt_delete_entries(self): """Prompts for deletion of entries in a journal.""" - print("Confirm each entry you want to delete [N/y]:") - to_delete: List[Entry] = [] + + to_delete = [] + + def ask_delete(entry): + return util.yesno( + f"Delete entry '{entry.pprint(short=True)}'?", default=False, + ) + for entry in self.entries: - response = input("jrnl: Delete entry '{}'? ".format(entry.pprint(short=True))) - if response == "y": + if ask_delete(entry): to_delete.append(entry) self.entries = [entry for entry in self.entries if entry not in to_delete] + self.write() def new_entry(self, raw, date=None, sort=True): """Constructs a new entry from some raw text input. If a date is given, it will parse and use this, otherwise scan for a date in the input first.""" - raw = raw.replace('\\n ', '\n').replace('\\n', '\n') + raw = raw.replace("\\n ", "\n").replace("\\n", "\n") # Split raw text into title and body sep = re.search(r"\n|[?!.]+ +\n?", raw) first_line = raw[: sep.end()].strip() if sep else raw diff --git a/jrnl/cli.py b/jrnl/cli.py index 0c18de4e7..bc878b8ef 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -173,7 +173,12 @@ def parse_args(args=None): action="store_true", ) - exporting.add_argument('--delete', dest='delete', action="store_true", help='Opens an interactive interface for deleting entries.') + exporting.add_argument( + "--delete", + dest="delete", + action="store_true", + help="Opens an interactive interface for deleting entries.", + ) return parser.parse_args(args) diff --git a/jrnl/util.py b/jrnl/util.py index add70ff30..978f63eda 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -112,7 +112,7 @@ def set_keychain(journal_name, password): def yesno(prompt, default=True): prompt = f"{prompt.strip()} {'[Y/n]' if default else '[y/N]'} " response = input(prompt) - return {"y": True, "n": False}.get(response.lower(), default) + return {"y": True, "n": False}.get(response.lower().strip(), default) def load_config(config_path): From 44b61dd5856ab1435100161780112138820637be Mon Sep 17 00:00:00 2001 From: dbxnr Date: Thu, 13 Feb 2020 17:56:34 +0000 Subject: [PATCH 7/7] cleanup imports --- jrnl/Journal.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index aec3e3082..448289467 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -6,10 +6,7 @@ import re from datetime import datetime -from itertools import filterfalse -from jrnl import Entry -from jrnl import util -from jrnl import time +from jrnl import Entry, util, time log = logging.getLogger(__name__)