mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-20 04:58:32 +02:00
Merge develop to on-today-bug-741
This commit is contained in:
commit
f633a93aab
14 changed files with 329 additions and 29 deletions
|
@ -1,24 +1,27 @@
|
|||
Contributing
|
||||
============
|
||||
# Contributing
|
||||
|
||||
If you use jrnl, you can totally make our day by just saying "thanks for the code." It's your chance to make a programmer happy today! If you have a moment, let us know what you use jrnl for and how; it'll help us to make it even better!
|
||||
|
||||
|
||||
Docs & Typos
|
||||
------------
|
||||
# Table of Contents
|
||||
* [Docs and Typos](#docs-and-typos)
|
||||
* [Bugs](#bugs)
|
||||
* [Feature requests and ideas](#feature-requests-and-ideas)
|
||||
* [New programmers and programmers new to python](#new-programmers-and-programmers-new-to-python)
|
||||
* [Developing jrnl](#developing-jrnl)
|
||||
|
||||
|
||||
## Docs and Typos
|
||||
|
||||
If you find a typo or a mistake in the docs, please fix it right away and send a pull request. The Right Way™ to fix the docs is to edit the `docs/*.md` files on the **master** branch. You can see the result if you run `make html` inside the project's root directory, which will open a browser that hot-reloads as you change the docs. This requires [mkdocs](https://www.mkdocs.org) to be installed. The `gh-pages` branch is automatically maintained and updates from `master`; you should never have to edit that.
|
||||
|
||||
Bugs
|
||||
----
|
||||
## Bugs
|
||||
|
||||
Unfortunately, bugs happen. If you found one, please [open a new issue](https://github.com/jrnl-org/jrnl/issues/new) and describe it as well as possible. If you're a programmer with some time, go ahead and send us a pull request! We'll review as quickly as we can.
|
||||
Unfortunately, bugs happen. If you found one, please [open a new issue](https://github.com/jrnl-org/jrnl/issues/new/choose) and describe it as well as possible. If you're a programmer with some time, go ahead and send us a pull request that references the issue! We'll review as quickly as we can.
|
||||
|
||||
## Feature requests and ideas
|
||||
|
||||
Feature requests and ideas
|
||||
--------------------------
|
||||
|
||||
So, you have an idea for a great feature? Awesome! We'd love to hear from you! Please [open a new issue](https://github.com/jrnl-org/jrnl/issues) and describe the goal of the feature, and any relevant use cases. We'll discuss the issue with you, and decide if it's a good fit for the project.
|
||||
So, you have an idea for a great feature? Awesome! We'd love to hear from you! Please [open a new issue](https://github.com/jrnl-org/jrnl/issues/new/choose) and describe the goal of the feature, and any relevant use cases. We'll discuss the issue with you, and decide if it's a good fit for the project.
|
||||
|
||||
When discussing new features, please keep in mind our design goals. jrnl strives to do one thing well. To us, that means:
|
||||
|
||||
|
@ -26,10 +29,17 @@ When discussing new features, please keep in mind our design goals. jrnl strives
|
|||
* have a simple interface
|
||||
* avoid duplicating functionality
|
||||
|
||||
## New programmers and programmers new to python
|
||||
|
||||
A short note for new programmers and programmers new to python
|
||||
--------------------------------------------------------------
|
||||
|
||||
Although jrnl has grown quite a bit since its inception. The overall complexity (for an end-user program) is fairly low, and we hope you'll find the code easy enough to understand.
|
||||
Although jrnl has grown quite a bit since its inception, the overall complexity (for an end-user program) is fairly low, and we hope you'll find the code easy enough to understand.
|
||||
|
||||
If you have a question, please don't hesitate to ask! Python is known for its welcoming community and openness to novice programmers, so feel free to fork the code and play around with it! If you create something you want to share with us, please create a pull request. We never expect pull requests to be perfect, idiomatic, instantly mergeable code. We can work through it together!
|
||||
|
||||
## Developing jrnl
|
||||
|
||||
The jrnl source uses [poetry](https://poetry.eustace.io/) for dependency management. You will need to install it to develop journal.
|
||||
|
||||
* To run tests: `make test` (or `poetry run behave` if on Windows)
|
||||
* To run the source: `poetry install` then `poetry shell` then run `jrnl` with or without arguments as necessary
|
||||
|
||||
For testing, jrnl uses [behave](https://behave.readthedocs.io/).
|
||||
|
|
|
@ -20,6 +20,19 @@ Feature: Basic reading and writing to a journal
|
|||
When we run "jrnl -n 1"
|
||||
Then the output should contain "2013-07-23 09:00 A cold and stormy day."
|
||||
|
||||
Scenario: Writing an empty entry from the editor
|
||||
Given we use the config "editor.yaml"
|
||||
When we open the editor and enter ""
|
||||
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 ""
|
||||
Then the output should be
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
Scenario: Filtering for dates
|
||||
Given we use the config "basic.yaml"
|
||||
When we run "jrnl -on 2013-06-10 --short"
|
||||
|
|
12
features/data/configs/editor.yaml
Normal file
12
features/data/configs/editor.yaml
Normal file
|
@ -0,0 +1,12 @@
|
|||
default_hour: 9
|
||||
default_minute: 0
|
||||
editor: "vim"
|
||||
encrypt: false
|
||||
highlight: true
|
||||
journals:
|
||||
default: features/journals/simple.journal
|
||||
linewrap: 80
|
||||
tagsymbols: "@"
|
||||
template: false
|
||||
timeformat: "%Y-%m-%d %H:%M"
|
||||
indent_character: "|"
|
76
features/dayone.feature
Normal file
76
features/dayone.feature
Normal file
|
@ -0,0 +1,76 @@
|
|||
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'"
|
||||
Then we should get no error
|
||||
and the output should be
|
||||
"""
|
||||
2013-05-17 11:39 This entry has tags!
|
||||
|
||||
2013-06-17 20:38 This entry has a location.
|
||||
|
||||
2013-07-17 11:38 This entry is starred!
|
||||
"""
|
||||
|
||||
# fails when system time is UTC (as on Travis-CI)
|
||||
@skip
|
||||
Scenario: Entries without timezone information will be interpreted as in the current timezone
|
||||
Given we use the config "dayone.yaml"
|
||||
When we run "jrnl -until 'feb 2013'"
|
||||
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."
|
||||
and we run "jrnl -until 1980"
|
||||
Then the output should be
|
||||
"""
|
||||
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"
|
||||
Then the output should be
|
||||
"""
|
||||
@work : 1
|
||||
@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"
|
||||
and we run "jrnl --tags"
|
||||
Then the output should be
|
||||
"""
|
||||
@work : 2
|
||||
@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"
|
||||
Then the output should be
|
||||
"""
|
||||
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"
|
||||
Then we should get no error
|
||||
and the output should be parsable as json
|
||||
and the json output should contain entries.0.uuid = "4BB1F46946AD439996C9B59DE7C4DDC1"
|
31
features/dayone_regressions.feature
Normal file
31
features/dayone_regressions.feature
Normal file
|
@ -0,0 +1,31 @@
|
|||
Feature: Zapped Dayone bugs stay dead!
|
||||
|
||||
# fails when system time is UTC (as on Travis-CI)
|
||||
@skip
|
||||
Scenario: DayOne tag searching should work with tags containing a mixture of upper and lower case.
|
||||
# https://github.com/jrnl-org/jrnl/issues/354
|
||||
Given we use the config "dayone.yaml"
|
||||
When we run "jrnl @plAy"
|
||||
Then the output should contain
|
||||
"""
|
||||
2013-05-17 11:39 This entry has tags!
|
||||
"""
|
||||
|
||||
# fails when system time is UTC (as on Travis-CI)
|
||||
@skip
|
||||
Scenario: Title with an embedded period on DayOne journal
|
||||
Given we use the config "dayone.yaml"
|
||||
When we run "jrnl 04-24-2014: "Ran 6.2 miles today in 1:02:03. I'm feeling sore because I forgot to stretch.""
|
||||
Then we should see the message "Entry added"
|
||||
When we run "jrnl -1"
|
||||
Then the output should be
|
||||
"""
|
||||
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"
|
|
@ -2,6 +2,14 @@ import shutil
|
|||
import os
|
||||
|
||||
|
||||
def before_feature(context, feature):
|
||||
# add "skip" tag
|
||||
# https://stackoverflow.com/a/42721605/4276230
|
||||
if "skip" in feature.tags:
|
||||
feature.skip("Marked with @skip")
|
||||
return
|
||||
|
||||
|
||||
def before_scenario(context, scenario):
|
||||
"""Before each scenario, backup all config and journal test data."""
|
||||
# Clean up in case something went wrong
|
||||
|
@ -22,6 +30,12 @@ def before_scenario(context, scenario):
|
|||
else:
|
||||
shutil.copy2(source, working_dir)
|
||||
|
||||
# add "skip" tag
|
||||
# https://stackoverflow.com/a/42721605/4276230
|
||||
if "skip" in scenario.effective_tags:
|
||||
scenario.skip("Marked with @skip")
|
||||
return
|
||||
|
||||
|
||||
def after_scenario(context, scenario):
|
||||
"""After each scenario, restore all test data and remove working_dirs."""
|
||||
|
|
|
@ -69,3 +69,50 @@ Feature: Zapped bugs should stay dead.
|
|||
Then the output should not contain "Life is good"
|
||||
Then the output should not contain "But I'm better."
|
||||
|
||||
Scenario: Create entry using day of the week as entry date.
|
||||
Given we use the config "basic.yaml"
|
||||
When we run "jrnl monday: This is an entry on a Monday."
|
||||
Then we should see the message "Entry added"
|
||||
When we run "jrnl -1"
|
||||
Then the output should contain "monday at 9am" in the local time
|
||||
Then the output should contain "This is an entry on a Monday."
|
||||
|
||||
Scenario: Create entry using day of the week abbreviations as entry date.
|
||||
Given we use the config "basic.yaml"
|
||||
When we run "jrnl fri: This is an entry on a Friday."
|
||||
Then we should see the message "Entry added"
|
||||
When we run "jrnl -1"
|
||||
Then the output should contain "friday at 9am" in the local time
|
||||
|
||||
Scenario: Displaying entries using -on today should display entries created today.
|
||||
Given we use the config "basic.yaml"
|
||||
When we run "jrnl today: Adding an entry right now."
|
||||
Then we should see the message "Entry added"
|
||||
When we run "jrnl -on today"
|
||||
Then the output should contain "Adding an entry right now."
|
||||
|
||||
Scenario: Displaying entries using -from day should display correct entries
|
||||
Given we use the config "basic.yaml"
|
||||
When we run "jrnl yesterday: This thing happened yesterday"
|
||||
Then we should see the message "Entry added"
|
||||
When we run "jrnl today at 11:59pm: Adding an entry right now."
|
||||
Then we should see the message "Entry added"
|
||||
When we run "jrnl tomorrow: A future entry."
|
||||
Then we should see the message "Entry added"
|
||||
When we run "jrnl -from today"
|
||||
Then the output should contain "Adding an entry right now."
|
||||
Then the output should contain "A future entry."
|
||||
Then the output should not contain "This thing happened yesterday"
|
||||
|
||||
Scenario: Displaying entries using -from and -to day should display correct entries
|
||||
Given we use the config "basic.yaml"
|
||||
When we run "jrnl yesterday: This thing happened yesterday"
|
||||
Then we should see the message "Entry added"
|
||||
When we run "jrnl today at 11:59pm: Adding an entry right now."
|
||||
Then we should see the message "Entry added"
|
||||
When we run "jrnl tomorrow: A future entry."
|
||||
Then we should see the message "Entry added"
|
||||
When we run "jrnl -from yesterday -to today"
|
||||
Then the output should contain "This thing happened yesterday"
|
||||
Then the output should contain "Adding an entry right now."
|
||||
Then the output should not contain "A future entry."
|
||||
|
|
|
@ -5,6 +5,9 @@ from jrnl import cli, install, Journal, util, plugins
|
|||
from jrnl import __version__
|
||||
from dateutil import parser as date_parser
|
||||
from collections import defaultdict
|
||||
try: import parsedatetime.parsedatetime_consts as pdt
|
||||
except ImportError: import parsedatetime as pdt
|
||||
import time
|
||||
import os
|
||||
import json
|
||||
import yaml
|
||||
|
@ -13,6 +16,10 @@ import tzlocal
|
|||
import shlex
|
||||
import sys
|
||||
|
||||
consts = pdt.Constants(usePyICU=False)
|
||||
consts.DOWParseStyle = -1 # Prefers past weekdays
|
||||
CALENDAR = pdt.Calendar(consts)
|
||||
|
||||
|
||||
class TestKeyring(keyring.backend.KeyringBackend):
|
||||
"""A test keyring that just stores its values in a hash"""
|
||||
|
@ -29,6 +36,7 @@ class TestKeyring(keyring.backend.KeyringBackend):
|
|||
def delete_password(self, servicename, username, password):
|
||||
self.keys[servicename][username] = None
|
||||
|
||||
|
||||
# set the keyring for keyring lib
|
||||
keyring.set_keyring(TestKeyring())
|
||||
|
||||
|
@ -66,6 +74,24 @@ 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}"')
|
||||
def open_editor_and_enter(context, text=""):
|
||||
text = (text or context.text)
|
||||
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("")
|
||||
|
||||
return tmpfile
|
||||
|
||||
with patch('subprocess.call', side_effect=_mock_editor_function):
|
||||
run(context, "jrnl")
|
||||
|
||||
|
||||
def _mock_getpass(inputs):
|
||||
def prompt_return(prompt="Password: "):
|
||||
print(prompt)
|
||||
|
@ -82,12 +108,18 @@ def _mock_input(inputs):
|
|||
|
||||
|
||||
@when('we run "{command}" and enter')
|
||||
@when('we run "{command}" and enter ""')
|
||||
@when('we run "{command}" and enter "{inputs1}"')
|
||||
@when('we run "{command}" and enter "{inputs1}" and "{inputs2}"')
|
||||
def run_with_input(context, command, inputs1="", inputs2=""):
|
||||
# create an iterator through all inputs. These inputs will be fed one by one
|
||||
# to the mocked calls for 'input()', 'util.getpass()' and 'sys.stdin.read()'
|
||||
text = iter((inputs1, inputs2)) if inputs1 else iter(context.text.split("\n"))
|
||||
if inputs1:
|
||||
text = iter((inputs1, inputs2))
|
||||
elif context.text:
|
||||
text = iter(context.text.split("\n"))
|
||||
else:
|
||||
text = iter(("", ""))
|
||||
args = ushlex(command)[1:]
|
||||
with patch("builtins.input", side_effect=_mock_input(text)) as mock_input:
|
||||
with patch("jrnl.util.getpass", side_effect=_mock_getpass(text)) as mock_getpass:
|
||||
|
@ -196,9 +228,9 @@ def check_output(context, text=None):
|
|||
def check_output_time_inline(context, text):
|
||||
out = context.stdout_capture.getvalue()
|
||||
local_tz = tzlocal.get_localzone()
|
||||
utc_time = date_parser.parse(text)
|
||||
local_date = utc_time.astimezone(local_tz).strftime("%Y-%m-%d %H:%M")
|
||||
assert local_date in out, local_date
|
||||
date, flag = CALENDAR.parse(text)
|
||||
output_date = time.strftime("%Y-%m-%d %H:%M",date)
|
||||
assert output_date in out, output_date
|
||||
|
||||
|
||||
@then('the output should contain')
|
||||
|
|
|
@ -122,7 +122,8 @@ class Journal:
|
|||
try:
|
||||
new_date = datetime.strptime(date_blob, self.config["timeformat"])
|
||||
except ValueError:
|
||||
new_date = time.parse(date_blob)
|
||||
# Passing in a date that had brackets around it
|
||||
new_date = time.parse(date_blob, bracketed=True)
|
||||
|
||||
if new_date:
|
||||
if entries:
|
||||
|
|
|
@ -176,7 +176,7 @@ def run(manual_args=None):
|
|||
|
||||
log.debug('Using journal "%s"', journal_name)
|
||||
mode_compose, mode_export, mode_import = guess_mode(args, config)
|
||||
|
||||
|
||||
# How to quit writing?
|
||||
if "win32" in sys.platform:
|
||||
_exit_multiline_code = "on a blank line, press Ctrl+Z and then Enter"
|
||||
|
@ -206,7 +206,7 @@ def run(manual_args=None):
|
|||
if raw:
|
||||
args.text = [raw]
|
||||
else:
|
||||
mode_compose = False
|
||||
sys.exit()
|
||||
|
||||
# This is where we finally open the journal!
|
||||
try:
|
||||
|
|
|
@ -9,8 +9,9 @@ from .tag_exporter import TagExporter
|
|||
from .xml_exporter import XMLExporter
|
||||
from .yaml_exporter import YAMLExporter
|
||||
from .template_exporter import __all__ as template_exporters
|
||||
from .fancy_exporter import FancyExporter
|
||||
|
||||
__exporters =[JSONExporter, MarkdownExporter, TagExporter, TextExporter, XMLExporter, YAMLExporter] + template_exporters
|
||||
__exporters =[JSONExporter, MarkdownExporter, TagExporter, TextExporter, XMLExporter, YAMLExporter, FancyExporter] + template_exporters
|
||||
__importers =[JRNLImporter]
|
||||
|
||||
__exporter_types = {name: plugin for plugin in __exporters for name in plugin.names}
|
||||
|
|
56
jrnl/plugins/fancy_exporter.py
Normal file
56
jrnl/plugins/fancy_exporter.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
from __future__ import absolute_import, unicode_literals, print_function
|
||||
from .text_exporter import TextExporter
|
||||
from textwrap import TextWrapper
|
||||
|
||||
|
||||
class FancyExporter(TextExporter):
|
||||
"""This Exporter can convert entries and journals into text with unicode box drawing characters."""
|
||||
names = ["fancy", "boxed"]
|
||||
extension = "txt"
|
||||
|
||||
border_a="┎"
|
||||
border_b="─"
|
||||
border_c="╮"
|
||||
border_d="╘"
|
||||
border_e="═"
|
||||
border_f="╕"
|
||||
border_g="┃"
|
||||
border_h="│"
|
||||
border_i="┠"
|
||||
border_j="╌"
|
||||
border_k="┤"
|
||||
border_l="┖"
|
||||
border_m="┘"
|
||||
|
||||
@classmethod
|
||||
def export_entry(cls, entry):
|
||||
"""Returns a fancy unicode representation of a single entry."""
|
||||
date_str = entry.date.strftime(entry.journal.config['timeformat'])
|
||||
linewrap = entry.journal.config['linewrap'] or 78
|
||||
initial_linewrap = linewrap - len(date_str) - 2
|
||||
body_linewrap = linewrap - 2
|
||||
card = [cls.border_a + cls.border_b*(initial_linewrap) + cls.border_c + date_str]
|
||||
w = TextWrapper(width=initial_linewrap, initial_indent=cls.border_g+' ', subsequent_indent=cls.border_g+' ')
|
||||
title_lines = w.wrap(entry.title)
|
||||
card.append(title_lines[0].ljust(initial_linewrap+1) + cls.border_d + cls.border_e*(len(date_str)-1) + cls.border_f)
|
||||
w.width = body_linewrap
|
||||
if len(title_lines) > 1:
|
||||
for line in w.wrap(' '.join([title_line[len(w.subsequent_indent):]
|
||||
for title_line in title_lines[1:]])):
|
||||
card.append(line.ljust(body_linewrap+1) + cls.border_h)
|
||||
if entry.body:
|
||||
card.append(cls.border_i + cls.border_j*body_linewrap + cls.border_k)
|
||||
for line in entry.body.splitlines():
|
||||
body_lines = w.wrap(line) or [cls.border_g]
|
||||
for body_line in body_lines:
|
||||
card.append(body_line.ljust(body_linewrap+1) + cls.border_h)
|
||||
card.append(cls.border_l + cls.border_b*body_linewrap + cls.border_m)
|
||||
return "\n".join(card)
|
||||
|
||||
@classmethod
|
||||
def export_journal(cls, journal):
|
||||
"""Returns a unicode representation of an entire journal."""
|
||||
return "\n".join(cls.export_entry(entry) for entry in journal)
|
14
jrnl/time.py
14
jrnl/time.py
|
@ -12,15 +12,16 @@ consts.DOWParseStyle = -1 # "Monday" will be either today or the last Monday
|
|||
CALENDAR = pdt.Calendar(consts)
|
||||
|
||||
|
||||
def parse(date_str, inclusive=False, default_hour=None, default_minute=None):
|
||||
def parse(date_str, inclusive=False, default_hour=None, default_minute=None, bracketed=False):
|
||||
"""Parses a string containing a fuzzy date and returns a datetime.datetime object"""
|
||||
if not date_str:
|
||||
return None
|
||||
elif isinstance(date_str, datetime):
|
||||
return date_str
|
||||
|
||||
# Don't try to parse anything with 6 or less characters. It's probably a markdown footnote
|
||||
if len(date_str) <= 6:
|
||||
# Don't try to parse anything with 6 or less characters and was parsed from the existing journal.
|
||||
# It's probably a markdown footnote
|
||||
if len(date_str) <= 6 and bracketed:
|
||||
return None
|
||||
|
||||
default_date = DEFAULT_FUTURE if inclusive else DEFAULT_PAST
|
||||
|
@ -51,8 +52,11 @@ def parse(date_str, inclusive=False, default_hour=None, default_minute=None):
|
|||
except TypeError:
|
||||
return None
|
||||
|
||||
if flag == 1: # Date found, but no time. Use the default time.
|
||||
date = datetime(*date[:3], hour=default_hour or 0, minute=default_minute or 0)
|
||||
if flag is 1: # Date found, but no time. Use the default time.
|
||||
date = datetime(*date[:3],
|
||||
hour=23 if inclusive else default_hour or 0,
|
||||
minute=59 if inclusive else default_minute or 0,
|
||||
second=59 if inclusive else 0)
|
||||
else:
|
||||
date = datetime(*date[:6])
|
||||
|
||||
|
|
|
@ -62,7 +62,10 @@ def get_password(validator, keychain=None, max_attempts=3):
|
|||
|
||||
def get_keychain(journal_name):
|
||||
import keyring
|
||||
return keyring.get_password('jrnl', journal_name)
|
||||
try:
|
||||
return keyring.get_password('jrnl', journal_name)
|
||||
except RuntimeError:
|
||||
return ""
|
||||
|
||||
|
||||
def set_keychain(journal_name, password):
|
||||
|
|
Loading…
Add table
Reference in a new issue