jrnl.install   A
last analyzed

Complexity

Total Complexity 18

Size/Duplication

Total Lines 169
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 116
dl 0
loc 169
rs 10
c 0
b 0
f 0
wmc 18

6 Functions

Rating   Name   Duplication   Size   Complexity  
B load_or_install_jrnl() 0 44 6
A save_config() 0 5 2
A _autocomplete_path() 0 5 2
A install() 0 28 3
A _initialize_autocomplete() 0 8 2
A upgrade_config() 0 12 3
1
#!/usr/bin/env python
2
3
import glob
4
import logging
5
import os
6
import sys
7
8
import xdg.BaseDirectory
9
import yaml
10
11
from . import __version__
12
from .config import load_config
13
from .config import verify_config_colors
14
from .exception import UserAbort
15
from .prompt import yesno
16
from .upgrade import is_old_version
17
18
DEFAULT_CONFIG_NAME = "jrnl.yaml"
19
DEFAULT_JOURNAL_NAME = "journal.txt"
20
DEFAULT_JOURNAL_KEY = "default"
21
XDG_RESOURCE = "jrnl"
22
23
USER_HOME = os.path.expanduser("~")
24
25
CONFIG_PATH = xdg.BaseDirectory.save_config_path(XDG_RESOURCE) or USER_HOME
26
CONFIG_FILE_PATH = os.path.join(CONFIG_PATH, DEFAULT_CONFIG_NAME)
27
CONFIG_FILE_PATH_FALLBACK = os.path.join(USER_HOME, ".jrnl_config")
28
29
JOURNAL_PATH = xdg.BaseDirectory.save_data_path(XDG_RESOURCE) or USER_HOME
30
JOURNAL_FILE_PATH = os.path.join(JOURNAL_PATH, DEFAULT_JOURNAL_NAME)
31
32
33
default_config = {
34
    "version": __version__,
35
    "journals": {"default": JOURNAL_FILE_PATH},
36
    "editor": os.getenv("VISUAL") or os.getenv("EDITOR") or "",
37
    "encrypt": False,
38
    "template": False,
39
    "default_hour": 9,
40
    "default_minute": 0,
41
    "timeformat": "%Y-%m-%d %H:%M",
42
    "tagsymbols": "@",
43
    "highlight": True,
44
    "linewrap": 79,
45
    "indent_character": "|",
46
    "colors": {
47
        "date": "none",
48
        "title": "none",
49
        "body": "none",
50
        "tags": "none",
51
    },
52
}
53
54
55
def upgrade_config(config):
56
    """Checks if there are keys missing in a given config dict, and if so, updates the config file accordingly.
57
    This essentially automatically ports jrnl installations if new config parameters are introduced in later
58
    versions."""
59
    missing_keys = set(default_config).difference(config)
60
    if missing_keys:
61
        for key in missing_keys:
62
            config[key] = default_config[key]
63
        save_config(config)
64
        print(
65
            f"[Configuration updated to newest version at {CONFIG_FILE_PATH}]",
66
            file=sys.stderr,
67
        )
68
69
70
def save_config(config):
71
    config["version"] = __version__
72
    with open(CONFIG_FILE_PATH, "w") as f:
73
        yaml.safe_dump(
74
            config, f, encoding="utf-8", allow_unicode=True, default_flow_style=False
75
        )
76
77
78
def load_or_install_jrnl():
79
    """
80
    If jrnl is already installed, loads and returns a config object.
81
    Else, perform various prompts to install jrnl.
82
    """
83
    config_path = (
84
        CONFIG_FILE_PATH
85
        if os.path.exists(CONFIG_FILE_PATH)
86
        else CONFIG_FILE_PATH_FALLBACK
87
    )
88
    if os.path.exists(config_path):
89
        logging.debug("Reading configuration from file %s", config_path)
90
        config = load_config(config_path)
91
92
        if is_old_version(config_path):
93
            from . import upgrade
94
95
            try:
96
                upgrade.upgrade_jrnl(config_path)
97
            except upgrade.UpgradeValidationException:
98
                print("Aborting upgrade.", file=sys.stderr)
99
                print(
100
                    "Please tell us about this problem at the following URL:",
101
                    file=sys.stderr,
102
                )
103
                print(
104
                    "https://github.com/jrnl-org/jrnl/issues/new?title=UpgradeValidationException",
105
                    file=sys.stderr,
106
                )
107
                print("Exiting.", file=sys.stderr)
108
                sys.exit(1)
109
110
        upgrade_config(config)
111
        verify_config_colors(config)
112
113
    else:
114
        logging.debug("Configuration file not found, installing jrnl...")
115
        try:
116
            config = install()
117
        except KeyboardInterrupt:
118
            raise UserAbort("Installation aborted")
119
120
    logging.debug('Using configuration "%s"', config)
121
    return config
122
123
124
def install():
125
    _initialize_autocomplete()
126
127
    # Where to create the journal?
128
    path_query = f"Path to your journal file (leave blank for {JOURNAL_FILE_PATH}): "
129
    journal_path = os.path.abspath(input(path_query).strip() or JOURNAL_FILE_PATH)
130
    default_config["journals"][DEFAULT_JOURNAL_KEY] = os.path.expanduser(
131
        os.path.expandvars(journal_path)
132
    )
133
134
    # If the folder doesn't exist, create it
135
    path = os.path.split(default_config["journals"][DEFAULT_JOURNAL_KEY])[0]
136
    try:
137
        os.makedirs(path)
138
    except OSError:
139
        pass
140
141
    # Encrypt it?
142
    encrypt = yesno(
143
        "Do you want to encrypt your journal? You can always change this later",
144
        default=False,
145
    )
146
    if encrypt:
147
        default_config["encrypt"] = True
148
        print("Journal will be encrypted.", file=sys.stderr)
149
150
    save_config(default_config)
151
    return default_config
152
153
154
def _initialize_autocomplete():
155
    # readline is not included in Windows Active Python and perhaps some other distributions
156
    if sys.modules.get("readline"):
157
        import readline
158
159
        readline.set_completer_delims(" \t\n;")
160
        readline.parse_and_bind("tab: complete")
161
        readline.set_completer(_autocomplete_path)
162
163
164
def _autocomplete_path(text, state):
165
    expansions = glob.glob(os.path.expanduser(os.path.expandvars(text)) + "*")
166
    expansions = [e + "/" if os.path.isdir(e) else e for e in expansions]
167
    expansions.append(None)
168
    return expansions[state]
169