mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-20 04:58:32 +02:00
Add --delete for interactive removal of entries
- Add inquirer dependency for fancy prompting - Fix some minor style issues Fix #434
This commit is contained in:
parent
ef23d7eabe
commit
c1082b7010
6 changed files with 120 additions and 3 deletions
|
@ -44,6 +44,7 @@ class Journal(object):
|
|||
# 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 @@ class Journal(object):
|
|||
|
||||
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
|
||||
|
|
38
jrnl/cli.py
38
jrnl/cli.py
|
@ -13,11 +13,15 @@ from . import Journal
|
|||
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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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("")
|
||||
|
|
46
poetry.lock
generated
46
poetry.lock
generated
|
@ -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"]
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Reference in a new issue