jrnl/jrnl/config.py
Jonathan Wren bc42f74b2b
Reformat additional messages and finish centralizing exception handling (#1424)
* Update and modularize exception handling

cc #1024 #1141

- Stack traces are no longer shown to users unless the --debug flag is
  being used
- Errors, warnings, and other messages contain color as needed
- Converted error messages to Enum
- Adds print_msg function to centralize output (this should replace all
  other output in other modules)

Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com>

* format with black

* add message to catch-all exception block

* Unskip some tests (#1399)

* remove skip_editor test and tag

* remove useless test

* unskip blank input test

* formatting

* rename test so it doesn't overwrite other test

* unskip some dayone tests that now work

* Bump ipython from 7.28.0 to 7.31.1 (#1401)

Bumps [ipython](https://github.com/ipython/ipython) from 7.28.0 to 7.31.1.
- [Release notes](https://github.com/ipython/ipython/releases)
- [Commits](https://github.com/ipython/ipython/compare/7.28.0...7.31.1)

---
updated-dependencies:
- dependency-name: ipython
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Update changelog [ci skip]

* Bump asteval from 0.9.25 to 0.9.26 (#1400)

Bumps [asteval](https://github.com/newville/asteval) from 0.9.25 to 0.9.26.
- [Release notes](https://github.com/newville/asteval/releases)
- [Commits](https://github.com/newville/asteval/compare/0.9.25...0.9.26)

---
updated-dependencies:
- dependency-name: asteval
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Update changelog [ci skip]

* Bump black from 21.12b0 to 22.1.0 (#1404)

* Bump black from 21.12b0 to 22.1.0

Bumps [black](https://github.com/psf/black) from 21.12b0 to 22.1.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/commits/22.1.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

* Run make format

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com>

* Update changelog [ci skip]

* Add reference documentation to docs site and separate out "Tips and Tricks" and "External Editors" from "Recipes" (#1332)

* First draft of command line reference, mostly pulled from help screen

* Add first draft of config file reference, mostly pulled from advanced.md

* Clean up config file doc for readability

* Add --config-file and remove examples from CLI reference

* Add warning about time zone in timeformat

* More small changes, and adding template config keyword

* Cleaning up and re-ordering config file reference

* Clean up reference and anything else from advanced documentation that can live elsewhere and linking to config file reference wherever config file is mentioned

* Fix syntax highlighting in command line reference, clean up content a bit, include --diagnostic

* Mention version config key

* Apply minor changes suggested in PR review

* Rename "recipes" to "Tips and Tricks", pull "External Editors" out of it into its own page, and redirect old recipes link to tips-and-tricks

* Revert broken mkdocs-redirects usage from last commit

* Update changelog [ci skip]

* Add --co alias for --config-override (#1397)

* Add hash as a default tag symbol (#1398)

* Update changelog [ci skip]

* Increment version to v2.8.4-beta2

* Update changelog [ci skip]

* Increment version to v2.8.4

* Update changelog [ci skip]

* Bump pytest from 6.2.5 to 7.0.0 (#1407)

Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.5 to 7.0.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.2.5...7.0.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Update changelog [ci skip]

* Drop support for Python 3.7 and 3.8 (#1412)

* Remove Python 3.7 and 3.8 from github actions workflows

* Update lockfile after running poetry update a couple times

* Update poetry lock

* Remove Python 3.7 and 3.8 from pyproject.toml and run poetry lock

* Update changelog [ci skip]

* Tidy up git ignore (#1414)

* cleaned gitignore and add comments

* removed colon for readbility

* alphabetize files in sections

Co-authored-by: nelnog <nel.nogales@gmail.com>

* fix behavior that was confusing pytest

* update test to match new message

* whitespace change

* clean up error for manually stopping the inline editor

* udpate error to use new exception handling

* move some exceptions and errors to the new exception handling

* add line breaks to keyboard interrupt so it looks more like other exceptions

* add handling for exceptions that happen earlier in the flow

* add new 'NothingToDelete' error to replace old behavior

* get rid of old exception

* add new exception handling to 'nothing saved to file' errors

* move exception for no editor configured into new handling

* move exception for no alt config to new handling

* get rid of old exception handling for encrypted journal

* Move error for too many wrong passwords into new handling

* fix merge errors

* replace sys.exit call with new exception handling

* replace sys.exit call with new exception handling

* replace sys.exit call with new exception handling

* reformat with black

* clean up old code

* clean up old code

* clean up linting issue

* update uncaught exception for new handling

* update test

* fix mangled lock file

Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jrnl Bot <jrnl.bot@gmail.com>
Co-authored-by: Nelson <35701520+nelnog@users.noreply.github.com>
Co-authored-by: nelnog <nel.nogales@gmail.com>
2022-03-19 12:30:23 -07:00

209 lines
6.1 KiB
Python

import logging
import os
import sys
import colorama
from ruamel.yaml import YAML
import xdg.BaseDirectory
from . import __version__
from jrnl.exception import JrnlException
from jrnl.messages import Message
from jrnl.messages import MsgText
from jrnl.messages import MsgType
from .color import ERROR_COLOR
from .color import RESET_COLOR
from .output import list_journals
# Constants
DEFAULT_CONFIG_NAME = "jrnl.yaml"
XDG_RESOURCE = "jrnl"
DEFAULT_JOURNAL_NAME = "journal.txt"
DEFAULT_JOURNAL_KEY = "default"
YAML_SEPARATOR = ": "
YAML_FILE_ENCODING = "utf-8"
def make_yaml_valid_dict(input: list) -> dict:
"""
Convert a two-element list of configuration key-value pair into a flat dict.
The dict is created through the yaml loader, with the assumption that
"input[0]: input[1]" is valid yaml.
:param input: list of configuration keys in dot-notation and their respective values.
:type input: list
:return: A single level dict of the configuration keys in dot-notation and their respective desired values
:rtype: dict
"""
assert len(input) == 2
# yaml compatible strings are of the form Key:Value
yamlstr = YAML_SEPARATOR.join(input)
runtime_modifications = YAML(typ="safe").load(yamlstr)
return runtime_modifications
def save_config(config, alt_config_path=None):
"""Supply alt_config_path if using an alternate config through --config-file."""
config["version"] = __version__
yaml = YAML(typ="safe")
yaml.default_flow_style = False # prevents collapsing of tree structure
with open(
alt_config_path if alt_config_path else get_config_path(),
"w",
encoding=YAML_FILE_ENCODING,
) as f:
yaml.dump(config, f)
def get_config_path():
try:
config_directory_path = xdg.BaseDirectory.save_config_path(XDG_RESOURCE)
except FileExistsError:
raise JrnlException(
Message(
MsgText.ConfigDirectoryIsFile,
MsgType.ERROR,
{
"config_directory_path": os.path.join(
xdg.BaseDirectory.xdg_config_home, XDG_RESOURCE
)
},
),
)
return os.path.join(
config_directory_path or os.path.expanduser("~"), DEFAULT_CONFIG_NAME
)
def get_default_config():
return {
"version": __version__,
"journals": {"default": get_default_journal_path()},
"editor": os.getenv("VISUAL") or os.getenv("EDITOR") or "",
"encrypt": False,
"template": False,
"default_hour": 9,
"default_minute": 0,
"timeformat": "%Y-%m-%d %H:%M",
"tagsymbols": "#@",
"highlight": True,
"linewrap": 79,
"indent_character": "|",
"colors": {
"date": "none",
"title": "none",
"body": "none",
"tags": "none",
},
}
def get_default_journal_path():
journal_data_path = xdg.BaseDirectory.save_data_path(
XDG_RESOURCE
) or os.path.expanduser("~")
return os.path.join(journal_data_path, DEFAULT_JOURNAL_NAME)
def scope_config(config, journal_name):
if journal_name not in config["journals"]:
return config
config = config.copy()
journal_conf = config["journals"].get(journal_name)
if type(journal_conf) is dict:
# We can override the default config on a by-journal basis
logging.debug(
"Updating configuration with specific journal overrides %s", journal_conf
)
config.update(journal_conf)
else:
# But also just give them a string to point to the journal file
config["journal"] = journal_conf
return config
def verify_config_colors(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 load_config(config_path):
"""Tries to load a config file from YAML."""
with open(config_path, encoding=YAML_FILE_ENCODING) as f:
yaml = YAML(typ="safe")
yaml.allow_duplicate_keys = True
return yaml.load(f)
def is_config_json(config_path):
with open(config_path, "r", encoding="utf-8") as f:
config_file = f.read()
return config_file.strip().startswith("{")
def update_config(config, new_config, scope, force_local=False):
"""Updates a config dict with new values - either global if scope is None
or config['journals'][scope] is just a string pointing to a journal file,
or within the scope"""
if scope and type(config["journals"][scope]) is dict: # Update to journal specific
config["journals"][scope].update(new_config)
elif scope and force_local: # Convert to dict
config["journals"][scope] = {"journal": config["journals"][scope]}
config["journals"][scope].update(new_config)
else:
config.update(new_config)
def get_journal_name(args, config):
args.journal_name = DEFAULT_JOURNAL_KEY
# The first arg might be a journal name
if args.text:
potential_journal_name = args.text[0]
if potential_journal_name[-1] == ":":
potential_journal_name = potential_journal_name[0:-1]
if potential_journal_name in config["journals"]:
args.journal_name = potential_journal_name
args.text = args.text[1:]
if args.journal_name not in config["journals"]:
raise JrnlException(
Message(
MsgText.NoDefaultJournal,
MsgType.ERROR,
{"journals": list_journals(config)},
),
)
logging.debug("Using journal name: %s", args.journal_name)
return args